diff --git a/src/pocketmine/entity/Living.php b/src/pocketmine/entity/Living.php index ef1ad6f9c..6b39edd69 100644 --- a/src/pocketmine/entity/Living.php +++ b/src/pocketmine/entity/Living.php @@ -22,6 +22,7 @@ namespace pocketmine\entity; +use pocketmine\block\Block; use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityDeathEvent; @@ -32,6 +33,7 @@ use pocketmine\math\Vector3; use pocketmine\nbt\tag\Short; use pocketmine\network\protocol\EntityEventPacket; use pocketmine\Server; +use pocketmine\utils\BlockIterator; abstract class Living extends Entity implements Damageable{ @@ -168,4 +170,63 @@ abstract class Living extends Entity implements Damageable{ public function getDrops(){ return []; } + + /** + * @param int $maxDistance + * @param int $maxLength + * @param array $transparent + * + * @return Block[] + */ + public function getLineOfSight($maxDistance, $maxLength = 0, array $transparent = []){ + if($maxDistance > 120){ + $maxDistance = 120; + } + + if(count($transparent) === 0){ + $transparent = null; + } + + $blocks = []; + $itr = new BlockIterator($this->level, $this->getPosition(), $this->getEyeHeight(), $maxDistance); + + while($itr->valid()){ + $itr->next(); + $block = $itr->current(); + $blocks[] = $block; + + if($maxLength !== 0 and count($blocks) > $maxLength){ + array_shift($blocks); + } + + $id = $block->getID(); + + if($transparent === null){ + if($id !== 0){ + break; + } + }else{ + if(!isset($transparent[$id])){ + break; + } + } + } + + return $blocks; + } + + /** + * @param int $maxDistance + * @param array $transparent + * + * @return Block + */ + public function getTargetBlock($maxDistance, array $transparent = []){ + $block = array_shift($this->getLineOfSight($maxDistance, 1, $transparent)); + if($block instanceof Block){ + return $block; + } + + return null; + } } diff --git a/src/pocketmine/math/Vector3.php b/src/pocketmine/math/Vector3.php index f22924ab9..dcea259c7 100644 --- a/src/pocketmine/math/Vector3.php +++ b/src/pocketmine/math/Vector3.php @@ -26,6 +26,14 @@ namespace pocketmine\math; * If this class is modified, remember to modify the PHP C extension. */ class Vector3{ + + const SIDE_DOWN = 0; + const SIDE_UP = 1; + const SIDE_NORTH = 2; + const SIDE_SOUTH = 3; + const SIDE_WEST = 4; + const SIDE_EAST = 5; + public $x; public $y; public $z; @@ -136,23 +144,42 @@ class Vector3{ public function getSide($side, $step = 1){ switch((int) $side){ - case 0: + case self::SIDE_DOWN: return new Vector3($this->x, $this->y - $step, $this->z); - case 1: + case self::SIDE_UP: return new Vector3($this->x, $this->y + $step, $this->z); - case 2: + case self::SIDE_NORTH: return new Vector3($this->x, $this->y, $this->z - $step); - case 3: + case self::SIDE_SOUTH: return new Vector3($this->x, $this->y, $this->z + $step); - case 4: + case self::SIDE_WEST: return new Vector3($this->x - $step, $this->y, $this->z); - case 5: + case self::SIDE_EAST: return new Vector3($this->x + $step, $this->y, $this->z); default: return $this; } } + public static function getOppositeSide($side){ + switch((int) $side){ + case self::SIDE_DOWN: + return self::SIDE_UP; + case self::SIDE_UP: + return self::SIDE_DOWN; + case self::SIDE_NORTH: + return self::SIDE_SOUTH; + case self::SIDE_SOUTH: + return self::SIDE_NORTH; + case self::SIDE_WEST: + return self::SIDE_EAST; + case self::SIDE_EAST: + return self::SIDE_WEST; + default: + return -1; + } + } + public function distance(Vector3 $pos){ return sqrt($this->distanceSquared($pos)); } diff --git a/src/pocketmine/utils/BlockIterator.php b/src/pocketmine/utils/BlockIterator.php new file mode 100644 index 000000000..2e2197829 --- /dev/null +++ b/src/pocketmine/utils/BlockIterator.php @@ -0,0 +1,290 @@ +level = $level; + $this->maxDistance = (int) $maxDistance; + + $startClone = clone $start; + $startClone->y += $yOffset; + + $this->currentDistance = 0; + + $mainDirection = 0; + $secondDirection = 0; + $thirdDirection = 0; + + $mainPosition = 0; + $secondPosition = 0; + $thirdPosition = 0; + + $startBlock = $this->level->getBlock($startClone->floor()); + + if($this->getXLength($direction) > $mainDirection){ + $this->mainFace = $this->getXFace($direction); + $mainDirection = $this->getXLength($direction); + $mainPosition = $this->getXPosition($direction, $startClone, $startBlock); + + $this->secondFace = $this->getYFace($direction); + $secondDirection = $this->getYLength($direction); + $secondPosition = $this->getYPosition($direction, $startClone, $startBlock); + + $this->thirdFace = $this->getZFace($direction); + $thirdDirection = $this->getZLength($direction); + $thirdPosition = $this->getZPosition($direction, $startClone, $startBlock); + } + if($this->getYLength($direction) > $mainDirection){ + $this->mainFace = $this->getYFace($direction); + $mainDirection = $this->getYLength($direction); + $mainPosition = $this->getYPosition($direction, $startClone, $startBlock); + + $this->secondFace = $this->getZFace($direction); + $secondDirection = $this->getZLength($direction); + $secondPosition = $this->getZPosition($direction, $startClone, $startBlock); + + $this->thirdFace = $this->getXFace($direction); + $thirdDirection = $this->getXLength($direction); + $thirdPosition = $this->getXPosition($direction, $startClone, $startBlock); + } + if($this->getZLength($direction) > $mainDirection){ + $this->mainFace = $this->getZFace($direction); + $mainDirection = $this->getZLength($direction); + $mainPosition = $this->getZPosition($direction, $startClone, $startBlock); + + $this->secondFace = $this->getXFace($direction); + $secondDirection = $this->getXLength($direction); + $secondPosition = $this->getXPosition($direction, $startClone, $startBlock); + + $this->thirdFace = $this->getYFace($direction); + $thirdDirection = $this->getYLength($direction); + $thirdPosition = $this->getYPosition($direction, $startClone, $startBlock); + } + + $d = $mainPosition / $mainDirection; + $secondd = $secondPosition - $secondDirection * $d; + $thirdd = $thirdPosition - $thirdDirection * $d; + + $this->secondError = floor($secondd * self::$gridSize); + $this->secondStep = round($secondDirection / $mainDirection * self::$gridSize); + $this->thirdError = floor($thirdd * self::$gridSize); + $this->thirdStep = round($thirdDirection / $mainDirection * self::$gridSize); + + if($this->secondError + $this->secondStep <= 0){ + $this->secondError = -$this->secondStep + 1; + } + + if($this->thirdError + $this->thirdStep <= 0){ + $this->thirdError = -$this->thirdStep + 1; + } + + $lastBlock = $startBlock->getSide(Vector3::getOppositeSide($this->mainFace)); + + if($this->secondError < 0){ + $this->secondError += self::$gridSize; + $lastBlock = $lastBlock->getSide(Vector3::getOppositeSide($this->secondFace)); + } + + if($this->thirdError < 0){ + $this->thirdError += self::$gridSize; + $lastBlock = $lastBlock->getSide(Vector3::getOppositeSide($this->thirdFace)); + } + + $this->secondError -= self::$gridSize; + $this->thirdError -= self::$gridSize; + + $this->blockQueue[0] = $lastBlock; + + $this->currentBlock = -1; + + $this->scan(); + + $startBlockFound = false; + + for($cnt = $this->currentBlock; $cnt >= 0; --$cnt){ + if($this->blockEquals($this->blockQueue[$cnt], $startBlock)){ + $this->currentBlock = $cnt; + $startBlockFound = true; + break; + } + } + + if(!$startBlockFound){ + throw new \Exception("Start block missed in BlockIterator"); + } + + $this->maxDistanceInt = round($maxDistance / (sqrt($mainDirection ** 2 + $secondDirection ** 2 + $thirdDirection ** 2) / $mainDirection)); + } + + private function blockEquals(Block $a, Block $b){ + return $a->x === $b->x and $a->y === $b->y and $a->z === $b->z; + } + + private function getXFace(Vector3 $direction){ + return (($direction->x) > 0) ? Vector3::SIDE_EAST : Vector3::SIDE_WEST; + } + + private function getYFace(Vector3 $direction){ + return (($direction->y) > 0) ? Vector3::SIDE_UP : Vector3::SIDE_DOWN; + } + + private function getZFace(Vector3 $direction){ + return (($direction->z) > 0) ? Vector3::SIDE_SOUTH : Vector3::SIDE_NORTH; + } + + private function getXLength(Vector3 $direction){ + return abs($direction->x); + } + + private function getYLength(Vector3 $direction){ + return abs($direction->y); + } + + private function getZLength(Vector3 $direction){ + return abs($direction->z); + } + + private function getPosition($direction, $position, $blockPosition){ + return $direction > 0 ? ($position - $blockPosition) : ($blockPosition + 1 - $position); + } + + private function getXPosition(Vector3 $direction, Vector3 $position, Block $block){ + return $this->getPosition($direction->x, $position->x, $block->x); + } + + private function getYPosition(Vector3 $direction, Vector3 $position, Block $block){ + return $this->getPosition($direction->y, $position->y, $block->y); + } + + private function getZPosition(Vector3 $direction, Vector3 $position, Block $block){ + return $this->getPosition($direction->z, $position->z, $block->z); + } + + public function next(){ + $this->scan(); + + if($this->currentBlock <= -1){ + throw new \OutOfBoundsException; + }else{ + --$this->currentBlock; + } + } + + public function current(){ + return $this->blockQueue[$this->currentBlock]; + } + + public function rewind(){ + + } + + public function key(){ + return $this->currentBlock; + } + + public function valid(){ + $this->scan(); + return $this->currentBlock !== -1; + } + + private function scan(){ + if($this->currentBlock >= 0){ + return; + } + + if($this->maxDistance !== 0 and $this->currentDistance > $this->maxDistanceInt){ + $this->end = true; + return; + } + + if($this->end){ + return; + } + + ++$this->currentDistance; + + $this->secondError += $this->secondStep; + $this->thirdError += $this->thirdStep; + + if($this->secondError > 0 and $this->thirdError > 0){ + $this->blockQueue[2] = $this->blockQueue[0]->getSide($this->mainFace); + + if($this->secondStep * $this->thirdError < $this->thirdStep * $this->secondError){ + $this->blockQueue[1] = $this->blockQueue[2]->getSide($this->secondFace); + $this->blockQueue[0] = $this->blockQueue[1]->getSide($this->thirdFace); + }else{ + $this->blockQueue[1] = $this->blockQueue[2]->getSide($this->thirdFace); + $this->blockQueue[0] = $this->blockQueue[1]->getSide($this->secondFace); + } + + $this->thirdError -= self::$gridSize; + $this->secondError -= self::$gridSize; + $this->currentBlock = 2; + }elseif($this->secondError > 0){ + $this->blockQueue[1] = $this->blockQueue[0]->getSide($this->mainFace); + $this->blockQueue[0] = $this->blockQueue[1]->getSide($this->thirdFace); + $this->secondError -= self::$gridSize; + $this->currentBlock = 1; + }elseif($this->thirdError > 0){ + $this->blockQueue[1] = $this->blockQueue[0]->getSide($this->mainFace); + $this->blockQueue[0] = $this->blockQueue[1]->getSide($this->secondFace); + $this->thirdError -= self::$gridSize; + $this->currentBlock = 1; + }else{ + $this->blockQueue[0] = $this->blockQueue[0]->getSide($this->mainFace); + $this->currentBlock = 0; + } + } +} \ No newline at end of file