[self::DATA_TYPE_BYTE, 0], self::DATA_AIR => [self::DATA_TYPE_SHORT, 300], self::DATA_NAMETAG => [self::DATA_TYPE_STRING, ""], self::DATA_SHOW_NAMETAG => [self::DATA_TYPE_BYTE, 1], self::DATA_SILENT => [self::DATA_TYPE_BYTE, 0], self::DATA_NO_AI => [self::DATA_TYPE_BYTE, 0], ]; public $passenger = null; public $vehicle = null; /** @var Chunk */ public $chunk; protected $lastDamageCause = null; /** @var Block[] */ private $blocksAround = []; public $lastX = null; public $lastY = null; public $lastZ = null; public $motionX; public $motionY; public $motionZ; /** @var Vector3 */ public $temporalVector; public $lastMotionX; public $lastMotionY; public $lastMotionZ; public $lastYaw; public $lastPitch; /** @var AxisAlignedBB */ public $boundingBox; public $onGround; public $inBlock = false; public $positionChanged; public $motionChanged; public $deadTicks = 0; protected $age = 0; public $height; public $eyeHeight = null; public $width; public $length; /** @var int */ private $health = 20; private $maxHealth = 20; protected $ySize = 0; protected $stepHeight = 0; public $keepMovement = false; public $fallDistance = 0; public $ticksLived = 0; public $lastUpdate; public $maxFireTicks; public $fireTicks = 0; public $namedtag; public $canCollide = true; protected $isStatic = false; public $isCollided = false; public $isCollidedHorizontally = false; public $isCollidedVertically = false; public $noDamageTicks; protected $justCreated; protected $fireProof; private $invulnerable; protected $gravity; protected $drag; /** @var Server */ protected $server; public $closed = false; /** @var \pocketmine\event\TimingsHandler */ protected $timings; protected $isPlayer = false; public function __construct(FullChunk $chunk, Compound $nbt){ if($chunk === null or $chunk->getProvider() === null){ throw new ChunkException("Invalid garbage Chunk given to Entity"); } $this->timings = Timings::getEntityTimings($this); $this->isPlayer = $this instanceof Player; $this->temporalVector = new Vector3(); if($this->eyeHeight === null){ $this->eyeHeight = $this->height / 2 + 0.1; } $this->id = Entity::$entityCount++; $this->justCreated = true; $this->namedtag = $nbt; $this->chunk = $chunk; $this->setLevel($chunk->getProvider()->getLevel()); $this->server = $chunk->getProvider()->getLevel()->getServer(); $this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0); $this->setPositionAndRotation( $this->temporalVector->setComponents( $this->namedtag["Pos"][0], $this->namedtag["Pos"][1], $this->namedtag["Pos"][2] ), $this->namedtag->Rotation[0], $this->namedtag->Rotation[1] ); $this->setMotion($this->temporalVector->setComponents($this->namedtag["Motion"][0], $this->namedtag["Motion"][1], $this->namedtag["Motion"][2])); if(!isset($this->namedtag->FallDistance)){ $this->namedtag->FallDistance = new Float("FallDistance", 0); } $this->fallDistance = $this->namedtag["FallDistance"]; if(!isset($this->namedtag->Fire)){ $this->namedtag->Fire = new Short("Fire", 0); } $this->fireTicks = $this->namedtag["Fire"]; if(!isset($this->namedtag->Air)){ $this->namedtag->Air = new Short("Air", 300); } $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, $this->namedtag["Air"]); if(!isset($this->namedtag->OnGround)){ $this->namedtag->OnGround = new Byte("OnGround", 0); } $this->onGround = $this->namedtag["OnGround"] > 0 ? true : false; if(!isset($this->namedtag->Invulnerable)){ $this->namedtag->Invulnerable = new Byte("Invulnerable", 0); } $this->invulnerable = $this->namedtag["Invulnerable"] > 0 ? true : false; $this->chunk->addEntity($this); $this->level->addEntity($this); $this->initEntity(); $this->lastUpdate = $this->server->getTick(); $this->server->getPluginManager()->callEvent(new EntitySpawnEvent($this)); $this->scheduleUpdate(); } /** * @return string */ public function getNameTag(){ return $this->getDataProperty(self::DATA_NAMETAG); } /** * @return bool */ public function isNameTagVisible(){ return $this->getDataProperty(self::DATA_SHOW_NAMETAG) > 0; } /** * @param string $name */ public function setNameTag($name){ $this->setDataProperty(self::DATA_NAMETAG, self::DATA_TYPE_STRING, $name); } /** * @param bool $value */ public function setNameTagVisible($value = true){ $this->setDataProperty(self::DATA_SHOW_NAMETAG, self::DATA_TYPE_BYTE, $value ? 1 : 0); } /** * @return Effect[] */ public function getEffects(){ return $this->effects; } public function removeAllEffects(){ foreach($this->effects as $effect){ $this->removeEffect($effect->getId()); } } public function removeEffect($effectId){ if(isset($this->effects[$effectId])){ $effect = $this->effects[$effectId]; unset($this->effects[$effectId]); $effect->remove($this); $this->recalculateEffectColor(); } } public function getEffect($effectId){ return isset($this->effects[$effectId]) ? $this->effects[$effectId] : null; } public function hasEffect($effectId){ return isset($this->effects[$effectId]); } public function addEffect(Effect $effect){ if(isset($this->effects[$effect->getId()])){ $oldEffect = $this->effects[$effect->getId()]; if( abs($effect->getAmplifier()) <= ($oldEffect->getAmplifier()) or (abs($effect->getAmplifier()) === abs($oldEffect->getAmplifier()) and $effect->getDuration() < $oldEffect->getDuration()) ){ return; } $effect->add($this, true); }else{ $effect->add($this, false); } $this->effects[$effect->getId()] = $effect; $this->recalculateEffectColor(); if($effect->getId() === Effect::HEALTH_BOOST){ $this->setHealth($this->getHealth() + 4 * ($effect->getAmplifier() + 1)); } } protected function recalculateEffectColor(){ $color = [0, 0, 0]; //RGB $count = 0; $ambient = true; foreach($this->effects as $effect){ if($effect->isVisible()){ $c = $effect->getColor(); $color[0] += $c[0] * ($effect->getAmplifier() + 1); $color[1] += $c[1] * ($effect->getAmplifier() + 1); $color[2] += $c[2] * ($effect->getAmplifier() + 1); $count += $effect->getAmplifier() + 1; if(!$effect->isAmbient()){ $ambient = false; } } } if($count > 0){ $r = ($color[0] / $count) & 0xff; $g = ($color[1] / $count) & 0xff; $b = ($color[2] / $count) & 0xff; $this->setDataProperty(Entity::DATA_POTION_COLOR, Entity::DATA_TYPE_INT, ($r << 16) + ($g << 8) + $b); $this->setDataProperty(Entity::DATA_POTION_AMBIENT, Entity::DATA_TYPE_BYTE, $ambient ? 1 : 0); }else{ $this->setDataProperty(Entity::DATA_POTION_COLOR, Entity::DATA_TYPE_INT, 0); $this->setDataProperty(Entity::DATA_POTION_AMBIENT, Entity::DATA_TYPE_BYTE, 0); } } /** * @param int|string $type * @param FullChunk $chunk * @param Compound $nbt * @param $args * * @return Entity */ public static function createEntity($type, FullChunk $chunk, Compound $nbt, ...$args){ if(isset(self::$knownEntities[$type])){ $class = self::$knownEntities[$type]; return new $class($chunk, $nbt, ...$args); } return null; } public static function registerEntity($className, $force = false){ $class = new \ReflectionClass($className); if(is_a($className, Entity::class, true) and !$class->isAbstract()){ if($className::NETWORK_ID !== -1){ self::$knownEntities[$className::NETWORK_ID] = $className; }elseif(!$force){ return false; } self::$knownEntities[$class->getShortName()] = $className; self::$shortNames[$className] = $class->getShortName(); return true; } return false; } /** * Returns the short save name * * @return string */ public function getSaveId(){ return self::$shortNames[static::class]; } public function saveNBT(){ if(!($this instanceof Player)){ $this->namedtag->id = new String("id", $this->getSaveId()); $this->namedtag->CustomName = new String("CustomName", $this->getNameTag()); $this->namedtag->CustomNameVisible = new String("CustomNameVisible", $this->isNameTagVisible()); } $this->namedtag->Pos = new Enum("Pos", [ new Double(0, $this->x), new Double(1, $this->y), new Double(2, $this->z) ]); $this->namedtag->Motion = new Enum("Motion", [ new Double(0, $this->motionX), new Double(1, $this->motionY), new Double(2, $this->motionZ) ]); $this->namedtag->Rotation = new Enum("Rotation", [ new Float(0, $this->yaw), new Float(1, $this->pitch) ]); $this->namedtag->FallDistance = new Float("FallDistance", $this->fallDistance); $this->namedtag->Fire = new Short("Fire", $this->fireTicks); $this->namedtag->Air = new Short("Air", $this->getDataProperty(self::DATA_AIR)); $this->namedtag->OnGround = new Byte("OnGround", $this->onGround == true ? 1 : 0); $this->namedtag->Invulnerable = new Byte("Invulnerable", $this->invulnerable == true ? 1 : 0); if(count($this->effects) > 0){ $effects = []; foreach($this->effects as $effect){ $effects[$effect->getId()] = new Compound($effect->getId(), [ "Id" => new Byte("Id", $effect->getId()), "Amplifier" => new Byte("Amplifier", $effect->getAmplifier()), "Duration" => new Int("Duration", $effect->getDuration()), "Ambient" => new Byte("Ambient", 0), "ShowParticles" => new Byte("ShowParticles", $effect->isVisible() ? 1 : 0) ]); } $this->namedtag->ActiveEffects = new Enum("ActiveEffects", $effects); }else{ unset($this->namedtag->ActiveEffects); } } protected function initEntity(){ if(isset($this->namedtag->ActiveEffects)){ foreach($this->namedtag->ActiveEffects->getValue() as $e){ $effect = Effect::getEffect($e["Id"]); if($effect === null){ continue; } $effect->setAmplifier($e["Amplifier"])->setDuration($e["Duration"])->setVisible($e["ShowParticles"] > 0); $this->addEffect($effect); } } if(isset($this->namedtag->CustomName)){ $this->setNameTag($this->namedtag["CustomName"]); $this->setNameTagVisible($this->namedtag["CustomNameVisible"] > 0); } $this->scheduleUpdate(); } /** * @return Player[] */ public function getViewers(){ return $this->hasSpawned; } /** * @param Player $player */ public function spawnTo(Player $player){ if(!isset($this->hasSpawned[$player->getId()]) and isset($player->usedChunks[Level::chunkHash($this->chunk->getX(), $this->chunk->getZ())])){ $this->hasSpawned[$player->getId()] = $player; } } public function sendPotionEffects(Player $player){ foreach($this->effects as $effect){ $pk = new MobEffectPacket(); $pk->eid = $this->getId(); $pk->effectId = $effect->getId(); $pk->amplifier = $effect->getAmplifier(); $pk->particles = $effect->isVisible(); $pk->duration = $effect->getDuration(); $pk->eventId = MobEffectPacket::EVENT_ADD; $player->dataPacket($pk->setChannel(Network::CHANNEL_WORLD_EVENTS)); } } /** * @deprecated */ public function sendMetadata($player){ $this->sendData($player); } /** * @param Player[]|Player $player * @param array $data Properly formatted entity data, defaults to everything */ public function sendData($player, array $data = null){ if(!is_array($player)){ $player = [$player]; } $pk = new SetEntityDataPacket(); $pk->eid = $this->id; $pk->metadata = $data === null ? $this->dataProperties : $data; Server::broadcastPacket($player, $pk->setChannel(Network::CHANNEL_WORLD_EVENTS)); } /** * @param Player $player */ public function despawnFrom(Player $player){ if(isset($this->hasSpawned[$player->getId()])){ $pk = new RemoveEntityPacket(); $pk->eid = $this->id; $player->dataPacket($pk->setChannel(Network::CHANNEL_ENTITY_SPAWNING)); unset($this->hasSpawned[$player->getId()]); } } /** * @param float $damage * @param EntityDamageEvent $source * */ public function attack($damage, EntityDamageEvent $source){ if($this->hasEffect(Effect::FIRE_RESISTANCE) and $source->getCause() === EntityDamageEvent::CAUSE_FIRE and $source->getCause() === EntityDamageEvent::CAUSE_FIRE_TICK and $source->getCause() === EntityDamageEvent::CAUSE_LAVA){ $source->setCancelled(); } $this->server->getPluginManager()->callEvent($source); if($source->isCancelled()){ return; } $this->setLastDamageCause($source); $this->setHealth($this->getHealth() - $source->getFinalDamage()); } /** * @param float $amount * @param EntityRegainHealthEvent $source * */ public function heal($amount, EntityRegainHealthEvent $source){ $this->server->getPluginManager()->callEvent($source); if($source->isCancelled()){ return; } $this->setHealth($this->getHealth() + $source->getAmount()); } /** * @return int */ public function getHealth(){ return $this->health; } public function isAlive(){ return $this->health > 0; } /** * Sets the health of the Entity. This won't send any update to the players * * @param int $amount */ public function setHealth($amount){ $amount = (int) $amount; if($amount === $this->health){ return; } if($amount <= 0){ if($this->isAlive()){ $this->kill(); } }elseif($amount <= $this->getMaxHealth() or $amount < $this->health){ $this->health = (int) $amount; }else{ $this->health = $this->getMaxHealth(); } } /** * @param EntityDamageEvent $type */ public function setLastDamageCause(EntityDamageEvent $type){ $this->lastDamageCause = $type; } /** * @return EntityDamageEvent|null */ public function getLastDamageCause(){ return $this->lastDamageCause; } /** * @return int */ public function getMaxHealth(){ return $this->maxHealth + ($this->hasEffect(Effect::HEALTH_BOOST) ? 4 * ($this->getEffect(Effect::HEALTH_BOOST)->getAmplifier() + 1) : 0); } /** * @param int $amount */ public function setMaxHealth($amount){ $this->maxHealth = (int) $amount; } public function canCollideWith(Entity $entity){ return !$this->justCreated and $entity !== $this; } protected function checkObstruction($x, $y, $z){ $i = Math::floorFloat($x); $j = Math::floorFloat($y); $k = Math::floorFloat($z); $diffX = $x - $i; $diffY = $y - $j; $diffZ = $z - $k; if(Block::$solid[$this->level->getBlockIdAt($i, $j, $k)]){ $flag = !Block::$solid[$this->level->getBlockIdAt($i - 1, $j, $k)]; $flag1 = !Block::$solid[$this->level->getBlockIdAt($i + 1, $j, $k)]; $flag2 = !Block::$solid[$this->level->getBlockIdAt($i, $j - 1, $k)]; $flag3 = !Block::$solid[$this->level->getBlockIdAt($i, $j + 1, $k)]; $flag4 = !Block::$solid[$this->level->getBlockIdAt($i, $j, $k - 1)]; $flag5 = !Block::$solid[$this->level->getBlockIdAt($i, $j, $k + 1)]; $direction = -1; $limit = 9999; if($flag){ $limit = $diffX; $direction = 0; } if($flag1 and 1 - $diffX < $limit){ $limit = 1 - $diffX; $direction = 1; } if($flag2 and $diffY < $limit){ $limit = $diffY; $direction = 2; } if($flag3 and 1 - $diffY < $limit){ $limit = 1 - $diffY; $direction = 3; } if($flag4 and $diffZ < $limit){ $limit = $diffZ; $direction = 4; } if($flag5 and 1 - $diffZ < $limit){ $direction = 5; } $force = lcg_value() * 0.2 + 0.1; if($direction === 0){ $this->motionX = -$force; return true; } if($direction === 1){ $this->motionX = $force; return true; } if($direction === 2){ $this->motionY = -$force; return true; } if($direction === 3){ $this->motionY = $force; return true; } if($direction === 4){ $this->motionZ = -$force; return true; } if($direction === 5){ $this->motionY = $force; return true; } } return false; } public function entityBaseTick($tickDiff = 1){ Timings::$timerEntityBaseTick->startTiming(); //TODO: check vehicles $this->blocksAround = null; $this->justCreated = false; if(!$this->isAlive()){ $this->removeAllEffects(); $this->despawnFromAll(); if(!$this->isPlayer){ $this->close(); } Timings::$timerEntityBaseTick->stopTiming(); return false; } if(count($this->effects) > 0){ foreach($this->effects as $effect){ if($effect->canTick()){ $effect->applyEffect($this); } $effect->setDuration($effect->getDuration() - $tickDiff); if($effect->getDuration() <= 0){ $this->removeEffect($effect->getId()); } } } $hasUpdate = false; $this->checkBlockCollision(); if($this->y <= -16 and $this->isAlive()){ $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_VOID, 10); $this->attack($ev->getFinalDamage(), $ev); $hasUpdate = true; } if($this->fireTicks > 0){ if($this->fireProof){ $this->fireTicks -= 4 * $tickDiff; if($this->fireTicks < 0){ $this->fireTicks = 0; } }else{ if(!$this->hasEffect(Effect::FIRE_RESISTANCE) and ($this->fireTicks % 20) === 0 or $tickDiff > 20){ $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_FIRE_TICK, 1); $this->attack($ev->getFinalDamage(), $ev); } $this->fireTicks -= $tickDiff; } if($this->fireTicks <= 0){ $this->extinguish(); }else{ $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ONFIRE, true); $hasUpdate = true; } } if($this->noDamageTicks > 0){ $this->noDamageTicks -= $tickDiff; if($this->noDamageTicks < 0){ $this->noDamageTicks = 0; } } $this->age += $tickDiff; $this->ticksLived += $tickDiff; Timings::$timerEntityBaseTick->stopTiming(); return $hasUpdate; } protected function updateMovement(){ $diffPosition = ($this->x - $this->lastX) ** 2 + ($this->y - $this->lastY) ** 2 + ($this->z - $this->lastZ) ** 2; $diffRotation = ($this->yaw - $this->lastYaw) ** 2 + ($this->pitch - $this->lastPitch) ** 2; $diffMotion = ($this->motionX - $this->lastMotionX) ** 2 + ($this->motionY - $this->lastMotionY) ** 2 + ($this->motionZ - $this->lastMotionZ) ** 2; if($diffPosition > 0.04 or $diffRotation > 2.25 and ($diffMotion > 0.0001 and $this->getMotion()->lengthSquared() <= 0.00001)){ //0.2 ** 2, 1.5 ** 2 $this->lastX = $this->x; $this->lastY = $this->y; $this->lastZ = $this->z; $this->lastYaw = $this->yaw; $this->lastPitch = $this->pitch; foreach($this->hasSpawned as $player){ $player->addEntityMovement($this->id, $this->x, $this->y + $this->getEyeHeight(), $this->z, $this->yaw, $this->pitch, $this->yaw); } } if($diffMotion > 0.0025 or ($diffMotion > 0.0001 and $this->getMotion()->lengthSquared() <= 0.0001)){ //0.05 ** 2 $this->lastMotionX = $this->motionX; $this->lastMotionY = $this->motionY; $this->lastMotionZ = $this->motionZ; foreach($this->hasSpawned as $player){ $player->addEntityMotion($this->id, $this->motionX, $this->motionY, $this->motionZ); } } } /** * @return Vector3 */ public function getDirectionVector(){ $y = -sin(deg2rad($this->pitch)); $xz = cos(deg2rad($this->pitch)); $x = -$xz * sin(deg2rad($this->yaw)); $z = $xz * cos(deg2rad($this->yaw)); return $this->temporalVector->setComponents($x, $y, $z)->normalize(); } public function getDirectionPlane(){ return (new Vector2(-cos(deg2rad($this->yaw) - M_PI_2), -sin(deg2rad($this->yaw) - M_PI_2)))->normalize(); } public function onUpdate($currentTick){ if($this->closed){ return false; } if(!$this->isAlive()){ ++$this->deadTicks; if($this->deadTicks >= 10){ $this->despawnFromAll(); if(!$this->isPlayer){ $this->close(); } } return $this->deadTicks < 10; } $tickDiff = $currentTick - $this->lastUpdate; if($tickDiff <= 0){ return false; } $this->lastUpdate = $currentTick; $this->timings->startTiming(); $hasUpdate = $this->entityBaseTick($tickDiff); $this->updateMovement(); $this->timings->stopTiming(); //if($this->isStatic()) return $hasUpdate; //return !($this instanceof Player); } public final function scheduleUpdate(){ $this->level->updateEntities[$this->id] = $this; } public function isOnFire(){ return $this->fireTicks > 0; } public function setOnFire($seconds){ $ticks = $seconds * 20; if($ticks > $this->fireTicks){ $this->fireTicks = $ticks; } } public function getDirection(){ $rotation = ($this->yaw - 90) % 360; if($rotation < 0){ $rotation += 360.0; } if((0 <= $rotation and $rotation < 45) or (315 <= $rotation and $rotation < 360)){ return 2; //North }elseif(45 <= $rotation and $rotation < 135){ return 3; //East }elseif(135 <= $rotation and $rotation < 225){ return 0; //South }elseif(225 <= $rotation and $rotation < 315){ return 1; //West }else{ return null; } } public function extinguish(){ $this->fireTicks = 0; $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ONFIRE, false); } public function canTriggerWalking(){ return true; } public function resetFallDistance(){ $this->fallDistance = 0; } protected function updateFallState($distanceThisTick, $onGround){ if($onGround === true){ if($this->fallDistance > 0){ if($this instanceof Living){ $this->fall($this->fallDistance); } $this->resetFallDistance(); } }elseif($distanceThisTick < 0){ $this->fallDistance -= $distanceThisTick; } } public function getBoundingBox(){ return $this->boundingBox; } public function fall($fallDistance){ $damage = floor($fallDistance - 3 - ($this->hasEffect(Effect::JUMP) ? $this->getEffect(Effect::JUMP)->getAmplifier() + 1 : 0)); if($damage > 0){ $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_FALL, $damage); $this->attack($ev->getFinalDamage(), $ev); } } public function handleLavaMovement(){ //TODO } public function getEyeHeight(){ return $this->eyeHeight; } public function moveFlying(){ //TODO } public function onCollideWithPlayer(Human $entityPlayer){ } protected function switchLevel(Level $targetLevel){ if($this->closed){ return false; } if($this->isValid()){ $this->server->getPluginManager()->callEvent($ev = new EntityLevelChangeEvent($this, $this->level, $targetLevel)); if($ev->isCancelled()){ return false; } $this->level->removeEntity($this); if($this->chunk !== null){ $this->chunk->removeEntity($this); } $this->despawnFromAll(); } $this->setLevel($targetLevel); $this->level->addEntity($this); if($this instanceof Player){ $this->usedChunks = []; $pk = new SetTimePacket(); $pk->time = $this->level->getTime(); $pk->started = $this->level->stopTime == false; $this->dataPacket($pk->setChannel(Network::CHANNEL_WORLD_EVENTS)); } $this->chunk = null; return true; } public function getPosition(){ return new Position($this->x, $this->y, $this->z, $this->level); } public function getLocation(){ return new Location($this->x, $this->y, $this->z, $this->yaw, $this->pitch, $this->level); } public function isInsideOfWater(){ $block = $this->level->getBlock($this->temporalVector->setComponents(Math::floorFloat($this->x), Math::floorFloat($y = ($this->y + $this->getEyeHeight())), Math::floorFloat($this->z))); if($block instanceof Water){ $f = ($block->y + 1) - ($block->getFluidHeightPercent() - 0.1111111); return $y < $f; } return false; } public function isInsideOfSolid(){ $block = $this->level->getBlock($this->temporalVector->setComponents(Math::floorFloat($this->x), Math::floorFloat($y = ($this->y + $this->getEyeHeight())), Math::floorFloat($this->z))); $bb = $block->getBoundingBox(); if($bb !== null and $block->isSolid() and !$block->isTransparent() and $bb->intersectsWith($this->getBoundingBox())){ return true; } return false; } public function fastMove($dx, $dy, $dz){ if($dx == 0 and $dz == 0 and $dy == 0){ return true; } Timings::$entityMoveTimer->startTiming(); $newBB = $this->boundingBox->getOffsetBoundingBox($dx, $dy, $dz); $list = $this->level->getCollisionCubes($this, $newBB, false); if(count($list) === 0){ $this->boundingBox = $newBB; } $this->x = ($this->boundingBox->minX + $this->boundingBox->maxX) / 2; $this->y = $this->boundingBox->minY - $this->ySize; $this->z = ($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2; $this->checkChunks(); if(!$this->onGround or $dy != 0){ $bb = clone $this->boundingBox; $bb->minY -= 0.75; $this->onGround = false; if(count($this->level->getCollisionBlocks($bb)) > 0){ $this->onGround = true; } } $this->isCollided = $this->onGround; $this->updateFallState($dy, $this->onGround); Timings::$entityMoveTimer->stopTiming(); return true; } public function move($dx, $dy, $dz){ if($dx == 0 and $dz == 0 and $dy == 0){ return true; } if($this->keepMovement){ $this->boundingBox->offset($dx, $dy, $dz); $this->setPosition($this->temporalVector->setComponents(($this->boundingBox->minX + $this->boundingBox->maxX) / 2, $this->boundingBox->minY, ($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2)); $this->onGround = $this->isPlayer ? true : false; return true; }else{ Timings::$entityMoveTimer->startTiming(); $this->ySize *= 0.4; /* if($this->isColliding){ //With cobweb? $this->isColliding = false; $dx *= 0.25; $dy *= 0.05; $dz *= 0.25; $this->motionX = 0; $this->motionY = 0; $this->motionZ = 0; } */ $movX = $dx; $movY = $dy; $movZ = $dz; $axisalignedbb = clone $this->boundingBox; /*$sneakFlag = $this->onGround and $this instanceof Player; if($sneakFlag){ for($mov = 0.05; $dx != 0.0 and count($this->level->getCollisionCubes($this, $this->boundingBox->getOffsetBoundingBox($dx, -1, 0))) === 0; $movX = $dx){ if($dx < $mov and $dx >= -$mov){ $dx = 0; }elseif($dx > 0){ $dx -= $mov; }else{ $dx += $mov; } } for(; $dz != 0.0 and count($this->level->getCollisionCubes($this, $this->boundingBox->getOffsetBoundingBox(0, -1, $dz))) === 0; $movZ = $dz){ if($dz < $mov and $dz >= -$mov){ $dz = 0; }elseif($dz > 0){ $dz -= $mov; }else{ $dz += $mov; } } //TODO: big messy loop }*/ $list = $this->level->getCollisionCubes($this, $this->boundingBox->addCoord($dx, $dy, $dz), false); foreach($list as $bb){ $dy = $bb->calculateYOffset($this->boundingBox, $dy); } $this->boundingBox->offset(0, $dy, 0); $fallingFlag = ($this->onGround or ($dy != $movY and $movY < 0)); foreach($list as $bb){ $dx = $bb->calculateXOffset($this->boundingBox, $dx); } $this->boundingBox->offset($dx, 0, 0); foreach($list as $bb){ $dz = $bb->calculateZOffset($this->boundingBox, $dz); } $this->boundingBox->offset(0, 0, $dz); if($this->stepHeight > 0 and $fallingFlag and $this->ySize < 0.05 and ($movX != $dx or $movZ != $dz)){ $cx = $dx; $cy = $dy; $cz = $dz; $dx = $movX; $dy = $this->stepHeight; $dz = $movZ; $axisalignedbb1 = clone $this->boundingBox; $this->boundingBox->setBB($axisalignedbb); $list = $this->level->getCollisionCubes($this, $this->boundingBox->addCoord($dx, $dy, $dz), false); foreach($list as $bb){ $dy = $bb->calculateYOffset($this->boundingBox, $dy); } $this->boundingBox->offset(0, $dy, 0); foreach($list as $bb){ $dx = $bb->calculateXOffset($this->boundingBox, $dx); } $this->boundingBox->offset($dx, 0, 0); foreach($list as $bb){ $dz = $bb->calculateZOffset($this->boundingBox, $dz); } $this->boundingBox->offset(0, 0, $dz); if(($cx ** 2 + $cz ** 2) >= ($dx ** 2 + $dz ** 2)){ $dx = $cx; $dy = $cy; $dz = $cz; $this->boundingBox->setBB($axisalignedbb1); }else{ $this->ySize += 0.5; } } $this->x = ($this->boundingBox->minX + $this->boundingBox->maxX) / 2; $this->y = $this->boundingBox->minY - $this->ySize; $this->z = ($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2; $this->checkChunks(); $this->checkGroundState($movX, $movY, $movZ, $dx, $dy, $dz); $this->updateFallState($dy, $this->onGround); if($movX != $dx){ $this->motionX = 0; } if($movY != $dy){ $this->motionY = 0; } if($movZ != $dz){ $this->motionZ = 0; } //TODO: vehicle collision events (first we need to spawn them!) Timings::$entityMoveTimer->stopTiming(); return true; } } protected function checkGroundState($movX, $movY, $movZ, $dx, $dy, $dz){ $this->isCollidedVertically = $movY != $dy; $this->isCollidedHorizontally = ($movX != $dx or $movZ != $dz); $this->isCollided = ($this->isCollidedHorizontally or $this->isCollidedVertically); $this->onGround = ($movY != $dy and $movY < 0); } public function getBlocksAround(){ if($this->blocksAround === null){ $minX = Math::floorFloat($this->boundingBox->minX); $minY = Math::floorFloat($this->boundingBox->minY); $minZ = Math::floorFloat($this->boundingBox->minZ); $maxX = Math::ceilFloat($this->boundingBox->maxX); $maxY = Math::ceilFloat($this->boundingBox->maxY); $maxZ = Math::ceilFloat($this->boundingBox->maxZ); $this->blocksAround = []; for($z = $minZ; $z <= $maxZ; ++$z){ for($x = $minX; $x <= $maxX; ++$x){ for($y = $minY; $y <= $maxY; ++$y){ $block = $this->level->getBlock($this->temporalVector->setComponents($x, $y, $z)); if($block->hasEntityCollision()){ $this->blocksAround[] = $block; } } } } } return $this->blocksAround; } protected function checkBlockCollision(){ $vector = new Vector3(0, 0, 0); foreach($this->getBlocksAround() as $block){ $block->onEntityCollide($this); $block->addVelocityToEntity($this, $vector); } if($vector->lengthSquared() > 0){ $vector = $vector->normalize(); $d = 0.014; $this->motionX += $vector->x * $d; $this->motionY += $vector->y * $d; $this->motionZ += $vector->z * $d; } } public function setPositionAndRotation(Vector3 $pos, $yaw, $pitch){ if($this->setPosition($pos) === true){ $this->setRotation($yaw, $pitch); return true; } return false; } public function setRotation($yaw, $pitch){ $this->yaw = $yaw; $this->pitch = $pitch; $this->scheduleUpdate(); } protected function checkChunks(){ if($this->chunk === null or ($this->chunk->getX() !== ($this->x >> 4) or $this->chunk->getZ() !== ($this->z >> 4))){ if($this->chunk !== null){ $this->chunk->removeEntity($this); } $this->chunk = $this->level->getChunk($this->x >> 4, $this->z >> 4, true); if(!$this->justCreated){ $newChunk = $this->level->getChunkPlayers($this->x >> 4, $this->z >> 4); foreach($this->hasSpawned as $player){ if(!isset($newChunk[$player->getLoaderId()])){ $this->despawnFrom($player); }else{ unset($newChunk[$player->getLoaderId()]); } } foreach($newChunk as $player){ $this->spawnTo($player); } } if($this->chunk === null){ return; } $this->chunk->addEntity($this); } } public function setPosition(Vector3 $pos){ if($this->closed){ return false; } if($pos instanceof Position and $pos->level !== null and $pos->level !== $this->level){ if($this->switchLevel($pos->getLevel()) === false){ return false; } } $this->x = $pos->x; $this->y = $pos->y; $this->z = $pos->z; $radius = $this->width / 2; $this->boundingBox->setBounds($pos->x - $radius, $pos->y, $pos->z - $radius, $pos->x + $radius, $pos->y + $this->height, $pos->z + $radius); $this->checkChunks(); return true; } public function getMotion(){ return new Vector3($this->motionX, $this->motionY, $this->motionZ); } public function setMotion(Vector3 $motion){ if(!$this->justCreated){ $this->server->getPluginManager()->callEvent($ev = new EntityMotionEvent($this, $motion)); if($ev->isCancelled()){ return false; } } $this->motionX = $motion->x; $this->motionY = $motion->y; $this->motionZ = $motion->z; if(!$this->justCreated){ $this->updateMovement(); } return true; } public function isOnGround(){ return $this->onGround === true; } public function kill(){ $this->health = 0; $this->scheduleUpdate(); } /** * @param Vector3|Position|Location $pos * @param float $yaw * @param float $pitch * * @return bool */ public function teleport(Vector3 $pos, $yaw = null, $pitch = null){ if($pos instanceof Location){ $yaw = $yaw === null ? $pos->yaw : $yaw; $pitch = $pitch === null ? $pos->pitch : $pitch; } $from = Position::fromObject($this, $this->level); $to = Position::fromObject($pos, $pos instanceof Position ? $pos->getLevel() : $this->level); $this->server->getPluginManager()->callEvent($ev = new EntityTeleportEvent($this, $from, $to)); if($ev->isCancelled()){ return false; } $this->ySize = 0; $pos = $ev->getTo(); $this->setMotion($this->temporalVector->setComponents(0, 0, 0)); if($this->setPositionAndRotation($pos, $yaw === null ? $this->yaw : $yaw, $pitch === null ? $this->pitch : $pitch) !== false){ $this->resetFallDistance(); $this->onGround = true; $this->lastX = $this->x; $this->lastY = $this->y; $this->lastZ = $this->z; $this->lastYaw = $this->yaw; $this->lastPitch = $this->pitch; foreach($this->hasSpawned as $player){ $player->addEntityMovement($this->getId(), $this->x, $this->y + $this->getEyeHeight(), $this->z, $this->yaw, $this->pitch, $this->yaw); } return true; } return false; } public function getId(){ return $this->id; } public function respawnToAll(){ foreach($this->hasSpawned as $key => $player){ unset($this->hasSpawned[$key]); $this->spawnTo($player); } } public function spawnToAll(){ if($this->chunk === null){ return; } foreach($this->level->getChunkPlayers($this->chunk->getX(), $this->chunk->getZ()) as $player){ if($player->isOnline()){ $this->spawnTo($player); } } } public function despawnFromAll(){ foreach($this->hasSpawned as $player){ $this->despawnFrom($player); } } public function close(){ if(!$this->closed){ $this->server->getPluginManager()->callEvent(new EntityDespawnEvent($this)); $this->closed = true; $this->despawnFromAll(); if($this->chunk !== null){ $this->chunk->removeEntity($this); } if($this->level !== null){ $this->level->removeEntity($this); } } } /** * @param int $id * @param int $type * @param mixed $value * * @return bool */ public function setDataProperty($id, $type, $value){ if($this->getDataProperty($id) !== $value){ $this->dataProperties[$id] = [$type, $value]; $this->sendData($this->hasSpawned, [$id => $this->dataProperties[$id]]); return true; } return false; } /** * @param int $id * * @return mixed */ public function getDataProperty($id){ return isset($this->dataProperties[$id]) ? $this->dataProperties[$id][1] : null; } /** * @param int $id * * @return int */ public function getDataPropertyType($id){ return isset($this->dataProperties[$id]) ? $this->dataProperties[$id][0] : null; } /** * @param int $propertyId; * @param int $id * @param bool $value */ public function setDataFlag($propertyId, $id, $value = true, $type = self::DATA_TYPE_BYTE){ if($this->getDataFlag($propertyId, $id) !== $value){ $flags = (int) $this->getDataProperty($propertyId); $flags ^= 1 << $id; $this->setDataProperty($propertyId, $type, $flags); } } /** * @param int $propertyId * @param int $id * * @return bool */ public function getDataFlag($propertyId, $id){ return (((int) $this->getDataProperty($propertyId)) & (1 << $id)) > 0; } public function __destruct(){ $this->close(); } public function setMetadata($metadataKey, MetadataValue $metadataValue){ $this->server->getEntityMetadata()->setMetadata($this, $metadataKey, $metadataValue); } public function getMetadata($metadataKey){ return $this->server->getEntityMetadata()->getMetadata($this, $metadataKey); } public function hasMetadata($metadataKey){ return $this->server->getEntityMetadata()->hasMetadata($this, $metadataKey); } public function removeMetadata($metadataKey, Plugin $plugin){ $this->server->getEntityMetadata()->removeMetadata($this, $metadataKey, $plugin); } public function __toString(){ return (new \ReflectionClass($this))->getShortName() . "(" . $this->getId() . ")"; } }