id = Entity::$entityCount++; $this->justCreated = true; $this->namedtag = $nbt; $this->setLevel($level, true); //Create a hard reference $this->server = Server::getInstance(); $this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0); $this->setPositionAndRotation(new Vector3( $this->namedtag["Pos"][0], $this->namedtag["Pos"][1], $this->namedtag["Pos"][2]), $this->namedtag->Rotation[0], $this->namedtag->Rotation[1] ); $this->setMotion(new Vector3( $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->airTicks = $this->namedtag["Air"]; if(!isset($this->namedtag->OnGround)){ $this->namedtag->OnGround = new Byte("OnGround", 1); } $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; $index = LevelFormat::getIndex($this->x >> 4, $this->z >> 4); $this->chunkIndex = $index; $this->getLevel()->addEntity($this); $this->initEntity(); $this->getLevel()->chunkEntities[$this->chunkIndex][$this->id] = $this; $this->lastUpdate = $this->spawnTime = microtime(true); $this->justCreated = false; $this->server->getPluginManager()->callEvent(new EntitySpawnEvent($this)); $this->scheduleUpdate(); } public function saveNBT(){ $this->namedtag["Pos"][0] = $this->x; $this->namedtag["Pos"][1] = $this->y; $this->namedtag["Pos"][2] = $this->z; $this->namedtag["Motion"][0] = $this->motionX; $this->namedtag["Motion"][1] = $this->motionY; $this->namedtag["Motion"][2] = $this->motionZ; $this->namedtag["Rotation"][0] = $this->yaw; $this->namedtag["Rotation"][1] = $this->pitch; $this->namedtag["FallDistance"] = $this->fallDistance; $this->namedtag["Fire"] = $this->fireTicks; $this->namedtag["Air"] = $this->airTicks; $this->namedtag["OnGround"] = $this->onGround == true ? 1 : 0; $this->namedtag["Invulnerable"] = $this->invulnerable == true ? 1 : 0; } protected abstract function initEntity(); public function getViewers(){ return $this->hasSpawned; } /** * @param Player $player */ public function spawnTo(Player $player){ if(!isset($this->hasSpawned[$player->getID()]) and $player->chunksLoaded[$this->chunkIndex] !== 0xff){ $this->hasSpawned[$player->getID()] = $player; } } /** * @param Player $player */ public function despawnFrom(Player $player){ if(isset($this->hasSpawned[$player->getID()])){ $pk = new RemoveEntityPacket; $pk->eid = $this->id; $player->dataPacket($pk); unset($this->hasSpawned[$player->getID()]); } } abstract function attack($damage, $source = "generic"); abstract function heal($amount, $source = "generic"); /** * @return int */ public function getHealth(){ return $this->health; } /** * Sets the health of the Entity. This won't send any update to the players * * @param int $amount */ public function setHealth($amount){ if($amount < 0){ $this->health = 0; $this->dead = true; }elseif($amount > $this->getMaxHealth()){ $this->health = $this->getMaxHealth(); }else{ $this->health = (int) $amount; } } /** * @return int */ public function getMaxHealth(){ return $this->maxHealth; } /** * @param int $amount */ public function setMaxHealth($amount){ $this->maxHealth = (int) $amount; $this->health = (int) min($this->health, $this->maxHealth); } protected function checkObstruction($x, $y, $z){ $i = (int) $x; $j = (int) $y; $k = (int) $z; $diffX = $x - $i; $diffY = $y - $j; $diffZ = $z - $k; $start = microtime(true); $list = $this->getLevel()->getCollisionBlocks($this->boundingBox); if(count($list) === 0 and $this->getLevel()->isFullBlock(new Vector3($i, $j, $k))){ return false; }else{ $flag = !$this->getLevel()->isFullBlock(new Vector3($i - 1, $j, $k)); $flag1 = !$this->getLevel()->isFullBlock(new Vector3($i + 1, $j, $k)); //$flag2 = !$this->getLevel()->isFullBlock(new Vector3($i, $j - 1, $k)); $flag3 = !$this->getLevel()->isFullBlock(new Vector3($i, $j + 1, $k)); $flag4 = !$this->getLevel()->isFullBlock(new Vector3($i, $j, $k - 1)); $flag5 = !$this->getLevel()->isFullBlock(new Vector3($i, $j, $k + 1)); $direction = 3; //UP! $limit = 9999; if($flag){ $limit = $diffX; $direction = 0; } if($flag1 and 1 - $diffX < $limit){ $limit = 1 - $diffX; $direction = 1; } 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; } //No direction 2 if($direction === 3){ $this->motionY = $force; return true; } if($direction === 4){ $this->motionZ = -$force; return true; } if($direction === 5){ $this->motionY = $force; } return true; } } public function entityBaseTick(){ //TODO: check vehicles $hasUpdate = false; $this->lastX = $this->x; $this->lastY = $this->y; $this->lastZ = $this->z; $this->lastMotionX = $this->motionX; $this->lastPitch = $this->pitch; $this->lastYaw = $this->yaw; if($this->handleWaterMovement()){ $this->fallDistance = 0; $this->inWater = true; $this->extinguish(); }else{ $this->inWater = false; } if($this->y < -64){ $this->kill(); } if($this->fireTicks > 0){ if($this->fireProof){ $this->fireTicks -= 4; if($this->fireTicks < 0){ $this->fireTicks = 0; } }else{ if(($this->fireTicks % 20) === 0){ $this->attack(1, "onFire"); } --$this->fireTicks; } $hasUpdate = true; } if($this->handleLavaMovement()){ $this->attack(4, "lava"); $this->setOnFire(15); $hasUpdate = true; $this->fallDistance *= 0.5; } ++$this->age; ++$this->ticksLived; } public function updateMovement(){ if($this->x !== $this->lastX or $this->y !== $this->lastY or $this->z !== $this->lastZ or $this->yaw !== $this->lastYaw or $this->pitch !== $this->lastPitch){ $this->lastX = $this->x; $this->lastY = $this->y; $this->lastZ = $this->z; $this->lastYaw = $this->yaw; $this->lastPitch = $this->pitch; if($this instanceof Human){ $pk = new MovePlayerPacket; $pk->eid = $this->id; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->bodyYaw = $this->yaw; }else{ $pk = new MoveEntityPacket_PosRot; $pk->eid = $this->id; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; } $this->server->broadcastPacket($this->hasSpawned, $pk); } if(!($this instanceof Player) and ($this->lastMotionX != $this->motionX or $this->lastMotionY != $this->motionY or $this->lastMotionZ != $this->motionZ)){ $this->motionChanged = false; $pk = new SetEntityMotionPacket; $pk->eid = $this->id; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $this->server->broadcastPacket($this->hasSpawned, $pk); } } public function onUpdate(){ if($this->closed !== false){ return false; } $hasUpdate = $this->entityBaseTick(); $this->updateMovement(); //if($this->isStatic()) return true; //return !($this instanceof Player); } public final function scheduleUpdate(){ Entity::$needUpdate[$this->id] = $this; } 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; } public function canTriggerWalking(){ return true; } protected function updateFallState($distanceThisTick, $onGround){ if($onGround === true){ if($this->fallDistance > 0){ if($this instanceof Living){ //TODO } $this->fall($this->fallDistance); $this->fallDistance = 0; } }elseif($distanceThisTick < 0){ $this->fallDistance -= $distanceThisTick; } } public function getBoundingBox(){ return $this->boundingBox; } public function fall($fallDistance){ //TODO } public function handleWaterMovement(){ //TODO } public function handleLavaMovement(){ //TODO } public function getEyeHeight(){ return 0; } public function moveFlying(){ //TODO } public function onCollideWithPlayer(Human $entityPlayer){ } protected function switchLevel(Level $targetLevel){ if($this->isValid()){ $this->server->getPluginManager()->callEvent($ev = new EntityLevelChangeEvent($this, $this->getLevel(), $targetLevel)); if($ev->isCancelled()){ return false; } $this->getLevel()->removeEntity($this); unset($this->getLevel()->chunkEntities[$this->chunkIndex][$this->id]); $this->despawnFromAll(); if($this instanceof Player){ foreach($this->chunksLoaded as $index => $Yndex){ if($Yndex !== 0xff){ $X = null; $Z = null; LevelFormat::getXZ($index, $X, $Z); foreach($this->getLevel()->getChunkEntities($X, $Z) as $entity){ $entity->despawnFrom($this); } } } $this->getLevel()->freeAllChunks($this); } } $this->setLevel($targetLevel, true); //Hard reference $this->getLevel()->addEntity($this); if($this instanceof Player){ $this->chunksLoaded = []; $pk = new SetTimePacket(); $pk->time = $this->getLevel()->getTime(); $pk->started = $this->getLevel()->stopTime == false; $this->dataPacket($pk); } $this->spawnToAll(); $this->chunkIndex = false; } public function getPosition(){ return new Position($this->x, $this->y, $this->z, $this->level); } public function collision(){ $this->isColliding = true; $this->fallDistance = 0; } /** * Returns the entities near the current one inside the AxisAlignedBB * * @param AxisAlignedBB $bb * * @return Entity[] */ public function getNearbyEntities(AxisAlignedBB $bb){ $nearby = []; $minX = ($bb->minX - 2) >> 4; $maxX = ($bb->maxX + 2) >> 4; $minZ = ($bb->minZ - 2) >> 4; $maxZ = ($bb->maxX + 2) >> 4; for($x = $minX; $x <= $maxX; ++$x){ for($z = $minZ; $z <= $maxZ; ++$z){ if($this->getLevel()->isChunkLoaded($x, $z)){ foreach($this->getLevel()->getChunkEntities($x, $z) as $ent){ if($ent !== $this and $ent->boundingBox->intersectsWith($this->boundingBox)){ $nearby[] = $ent; } } } } } return $nearby; } public function move($dx, $dy, $dz){ //$collision = []; //$this->checkBlockCollision($collision); if($dx == 0 and $dz == 0 and $dy == 0){ return; } $ox = $this->x; $oy = $this->y; $oz = $this->z; if($this->isColliding){ //With an entity $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; /*$sneakFlag = $this->onGround and $this instanceof Player; if($sneakFlag){ for($mov = 0.05; $dx != 0.0 and count($this->getLevel()->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->getLevel()->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 }*/ if(count($this->getLevel()->getCollisionCubes($this, $this->boundingBox->getOffsetBoundingBox(0, $dy, 0))) > 0){ $dy = 0; $dx = 0; $dz = 0; } $fallingFlag = $this->onGround or ($dy != $movY and $movY < 0); if($dx != 0){ if(count($this->getLevel()->getCollisionCubes($this, $this->boundingBox->getOffsetBoundingBox($dx, 0, 0))) > 0){ $dy = 0; $dx = 0; $dz = 0; } } if($dz != 0){ if(count($this->getLevel()->getCollisionCubes($this, $this->boundingBox->getOffsetBoundingBox(0, 0, $dz))) > 0){ $dy = 0; $dx = 0; $dz = 0; } } if($movX != $dx or $movZ != $dz or $fallingFlag){ $cx = $dx; $cy = $dy; $cz = $dz; $dx = $movX; $dy = 0; $dz = $movZ; $oldBB = clone $this->boundingBox; $list = $this->getLevel()->getCollisionCubes($this, $this->boundingBox->getOffsetBoundingBox($movX, $dy, $movZ)); foreach($list as $bb){ $dy = $bb->calculateYOffset($this->boundingBox, $dy); } $this->boundingBox->addCoord(0, $dy, 0); if($movY != $dy){ $dx = 0; $dy = 0; $dz = 0; } foreach($list as $bb){ $dx = $bb->calculateXOffset($this->boundingBox, $dx); } $this->boundingBox->addCoord($dx, 0, 0); if($movX != $dx){ $dx = 0; $dy = 0; $dz = 0; } foreach($list as $bb){ $dz = $bb->calculateZOffset($this->boundingBox, $dz); } $this->boundingBox->addCoord(0, 0, $dz); if($movZ != $dz){ $dx = 0; $dy = 0; $dz = 0; } if($movY != $dy){ $dy = 0; foreach($list as $bb){ $dy = $bb->calculateYOffset($this->boundingBox, $dy); } $this->boundingBox->addCoord(0, $dy, 0); } if($cx * $cx + $cz * $cz > $dx * $dx + $dz * $dz){ $dx = $cx; $dy = $cy; $dz = $cz; $this->boundingBox->setBB($oldBB); } } $this->x = ($this->boundingBox->minX + $this->boundingBox->maxX) / 2; $this->y = $this->boundingBox->minY + $this->height; $this->z = ($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2; $this->onGround = $movY != $dy and $movY < 0; $this->updateFallState($dy, $this->onGround); if($movX != $dx){ $this->motionX = 0; } if($movY != $dy){ $this->motionY = 0; } if($movZ != $dz){ $this->motionZ = 0; } //$this->boundingBox->addCoord($dx, $dy, $dz); //$this->x += $dx; //$this->y += $dy; //$this->z += $dz; $cx = $this->x - $ox; $cy = $this->y - $oy; $cz = $this->z - $oz; //TODO: vehicle collision events (first we need to spawn them!) } /** * @param Block[] $list * * @return Block[] */ protected function checkBlockCollision(&$list = array()){ $minX = floor($this->boundingBox->minX + 0.001); $minY = floor($this->boundingBox->minY + 0.001); $minZ = floor($this->boundingBox->minZ + 0.001); $maxX = floor($this->boundingBox->maxX + 0.001); $maxY = floor($this->boundingBox->maxY + 0.001); $maxZ = floor($this->boundingBox->maxZ + 0.001); for($z = $minZ; $z <= $maxZ; ++$z){ for($x = $minX; $x <= $maxX; ++$x){ for($y = $minY; $y <= $maxY; ++$y){ $this->getLevel()->getBlock(new Vector3($x, $y, $z))->collidesWithBB($this->boundingBox, $list); } } } return $list; } 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(); } public function setPosition(Vector3 $pos){ if($pos instanceof Position and $pos->getLevel() instanceof Level and $pos->getLevel() !== $this->getLevel()){ if($this->switchLevel($pos->getLevel()) === false){ return false; } } if(!$this->justCreated){ $this->server->getPluginManager()->callEvent($ev = new EntityMoveEvent($this, $pos)); if($ev->isCancelled()){ 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); if(($index = LevelFormat::getIndex($this->x >> 4, $this->z >> 4)) !== $this->chunkIndex){ if($this->chunkIndex !== false){ unset($this->getLevel()->chunkEntities[$this->chunkIndex][$this->id]); } $this->chunkIndex = $index; $this->getLevel()->loadChunk($this->x >> 4, $this->z >> 4); if(!$this->justCreated){ $newChunk = $this->getLevel()->getUsingChunk($this->x >> 4, $this->z >> 4); foreach($this->hasSpawned as $player){ if(!isset($newChunk[$player->CID])){ $this->despawnFrom($player); }else{ unset($newChunk[$player->CID]); } } foreach($newChunk as $player){ $this->spawnTo($player); } } $this->getLevel()->chunkEntities[$this->chunkIndex][$this->id] = $this; } $this->scheduleUpdate(); return true; } public function getMotion(){ return new Vector3($this->motionX, $this->motionY, $this->motionZ); } public function setMotion(Vector3 $motion){ if(!$this->justCreated){ Server::getInstance()->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->scheduleUpdate(); } } public function isOnGround(){ return $this->onGround === true; } public function kill(){ $this->dead = true; } public function teleport(Vector3 $pos, $yaw = false, $pitch = false){ $this->setMotion(new Vector3(0, 0, 0)); if($this->setPositionAndRotation($pos, $yaw === false ? $this->yaw : $yaw, $pitch === false ? $this->pitch : $pitch) !== false){ if($this instanceof Player){ $this->airTicks = 300; $this->fallDistance = 0; $this->orderChunks(); $this->getNextChunk(true); $this->forceMovement = $pos; $pk = new MovePlayerPacket; $pk->eid = 0; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->bodyYaw = $this->yaw; $pk->pitch = $this->pitch; $pk->yaw = $this->yaw; $this->dataPacket($pk); } return true; } return false; } public function getID(){ return $this->id; } public function spawnToAll(){ foreach($this->getLevel()->getUsingChunk($this->x >> 4, $this->z >> 4) as $player){ if(isset($player->id) and $player->spawned === true){ $this->spawnTo($player); } } } public function despawnFromAll(){ foreach($this->hasSpawned as $player){ $this->despawnFrom($player); } } public function close(){ if($this->closed === false){ $this->closed = true; unset(Entity::$needUpdate[$this->id]); $this->getLevel()->removeEntity($this); unset($this->getLevel()->chunkEntities[$this->chunkIndex][$this->id]); $this->despawnFromAll(); $this->server->getPluginManager()->callEvent(new EntityDespawnEvent($this)); } } abstract public function getData(); 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); } }