85 public const MOTION_THRESHOLD = 0.00001;
86 protected const STEP_CLIP_MULTIPLIER = 0.4;
88 private const TAG_FIRE =
"Fire";
89 private const TAG_ON_GROUND =
"OnGround";
90 private const TAG_FALL_DISTANCE =
"FallDistance";
91 private const TAG_CUSTOM_NAME =
"CustomName";
92 private const TAG_CUSTOM_NAME_VISIBLE =
"CustomNameVisible";
93 public const TAG_POS =
"Pos";
94 public const TAG_MOTION =
"Motion";
95 public const TAG_ROTATION =
"Rotation";
97 private static int $entityCount = 1;
103 return self::$entityCount++;
110 protected array $hasSpawned = [];
119 protected ?array $blocksAround =
null;
125 protected bool $forceMovementUpdate =
false;
126 private bool $checkBlockIntersectionsNextTick =
true;
129 public bool $onGround =
false;
133 private float $health = 20.0;
134 private int $maxHealth = 20;
136 protected float $ySize = 0.0;
137 protected float $stepHeight = 0.0;
138 public bool $keepMovement =
false;
140 public float $fallDistance = 0.0;
141 public int $ticksLived = 0;
142 public int $lastUpdate;
143 protected int $fireTicks = 0;
145 private bool $savedWithChunk =
true;
147 public bool $isCollided =
false;
148 public bool $isCollidedHorizontally =
false;
149 public bool $isCollidedVertically =
false;
151 public int $noDamageTicks = 0;
152 protected bool $justCreated =
true;
156 protected float $gravity;
157 protected float $drag;
158 protected bool $gravityEnabled =
true;
162 protected bool $closed =
false;
163 private bool $closeInFlight =
false;
164 private bool $needsDespawn =
false;
168 protected bool $networkPropertiesDirty =
false;
170 protected string $nameTag =
"";
171 protected bool $nameTagVisible =
true;
172 protected bool $alwaysShowNameTag =
false;
173 protected string $scoreTag =
"";
174 protected float $scale = 1.0;
176 protected bool $canClimb =
false;
177 protected bool $canClimbWalls =
false;
178 protected bool $noClientPredictions =
false;
179 protected bool $invisible =
false;
180 protected bool $silent =
false;
182 protected ?
int $ownerId =
null;
183 protected ?
int $targetId =
null;
185 private bool $constructorCalled =
false;
188 if($this->constructorCalled){
189 throw new \LogicException(
"Attempted to call constructor for an Entity multiple times");
191 $this->constructorCalled =
true;
192 Utils::checkLocationNotInfOrNaN($location);
194 $this->timings = Timings::getEntityTimings($this);
196 $this->size = $this->getInitialSizeInfo();
197 $this->drag = $this->getInitialDragMultiplier();
198 $this->gravity = $this->getInitialGravity();
200 $this->
id = self::nextRuntimeId();
205 $this->boundingBox =
new AxisAlignedBB(0, 0, 0, 0, 0, 0);
206 $this->recalculateBoundingBox();
211 $this->motion = Vector3::zero();
214 $this->resetLastMovements();
216 $this->networkProperties =
new EntityMetadataCollection();
218 $this->attributeMap =
new AttributeMap();
219 $this->addAttributes();
221 $this->initEntity($nbt ??
new CompoundTag());
223 $this->getWorld()->addEntity($this);
225 $this->lastUpdate = $this->
server->getTick();
227 $this->scheduleUpdate();
230 abstract protected function getInitialSizeInfo() : EntitySizeInfo;
247 public function getNameTag() : string{
248 return $this->nameTag;
251 public function isNameTagVisible() : bool{
252 return $this->nameTagVisible;
255 public function isNameTagAlwaysVisible() : bool{
256 return $this->alwaysShowNameTag;
267 public function setNameTag(
string $name) : void{
268 $this->nameTag = $name;
269 $this->networkPropertiesDirty =
true;
272 public function setNameTagVisible(
bool $value =
true) : void{
273 $this->nameTagVisible = $value;
274 $this->networkPropertiesDirty =
true;
277 public function setNameTagAlwaysVisible(
bool $value =
true) : void{
278 $this->alwaysShowNameTag = $value;
279 $this->networkPropertiesDirty =
true;
282 public function getScoreTag() : ?string{
283 return $this->scoreTag;
286 public function setScoreTag(
string $score) : void{
287 $this->scoreTag = $score;
288 $this->networkPropertiesDirty =
true;
291 public function getScale() : float{
295 public function setScale(
float $value) : void{
297 throw new \InvalidArgumentException(
"Scale must be greater than 0");
299 $this->scale = $value;
300 $this->setSize($this->getInitialSizeInfo()->scale($value));
303 public function getBoundingBox() : AxisAlignedBB{
304 return $this->boundingBox;
307 protected function recalculateBoundingBox() : void{
308 $halfWidth = $this->size->getWidth() / 2;
310 $this->boundingBox =
new AxisAlignedBB(
311 $this->location->x - $halfWidth,
312 $this->location->y + $this->ySize,
313 $this->location->z - $halfWidth,
314 $this->location->x + $halfWidth,
315 $this->location->y + $this->size->getHeight() + $this->ySize,
316 $this->location->z + $halfWidth
320 public function getSize() : EntitySizeInfo{
324 protected function setSize(EntitySizeInfo $size) : void{
326 $this->recalculateBoundingBox();
327 $this->networkPropertiesDirty =
true;
335 return $this->noClientPredictions;
347 $this->noClientPredictions = $value;
348 $this->networkPropertiesDirty =
true;
351 public function isInvisible() : bool{
352 return $this->invisible;
355 public function setInvisible(
bool $value =
true) : void{
356 $this->invisible = $value;
357 $this->networkPropertiesDirty =
true;
360 public function isSilent() : bool{
361 return $this->silent;
364 public function setSilent(
bool $value =
true) : void{
365 $this->silent = $value;
366 $this->networkPropertiesDirty =
true;
373 return $this->canClimb;
380 $this->canClimb = $value;
381 $this->networkPropertiesDirty =
true;
388 return $this->canClimbWalls;
395 $this->canClimbWalls = $value;
396 $this->networkPropertiesDirty =
true;
403 return $this->ownerId;
410 return $this->ownerId !== null ? $this->
server->getWorldManager()->findEntity($this->ownerId) : null;
420 $this->ownerId =
null;
421 }elseif($owner->closed){
422 throw new \InvalidArgumentException(
"Supplied owning entity is garbage and cannot be used");
424 $this->ownerId = $owner->getId();
426 $this->networkPropertiesDirty =
true;
433 return $this->targetId;
441 return $this->targetId !== null ? $this->
server->getWorldManager()->findEntity($this->targetId) : null;
450 if($target === null){
451 $this->targetId =
null;
452 }elseif($target->closed){
453 throw new \InvalidArgumentException(
"Supplied target entity is garbage and cannot be used");
455 $this->targetId = $target->getId();
457 $this->networkPropertiesDirty =
true;
464 return $this->savedWithChunk;
472 $this->savedWithChunk = $value;
477 ->setTag(self::TAG_POS, new
ListTag([
482 ->setTag(self::TAG_MOTION, new
ListTag([
487 ->setTag(self::TAG_ROTATION, new
ListTag([
489 new
FloatTag($this->location->pitch)
492 if(!($this instanceof
Player)){
493 EntityFactory::getInstance()->injectSaveId(get_class($this), $nbt);
495 if($this->getNameTag() !==
""){
496 $nbt->setString(self::TAG_CUSTOM_NAME, $this->getNameTag());
497 $nbt->setByte(self::TAG_CUSTOM_NAME_VISIBLE, $this->isNameTagVisible() ? 1 : 0);
501 $nbt->
setFloat(self::TAG_FALL_DISTANCE, $this->fallDistance);
502 $nbt->setShort(self::TAG_FIRE, $this->fireTicks);
503 $nbt->setByte(self::TAG_ON_GROUND, $this->onGround ? 1 : 0);
505 $nbt->setLong(VersionInfo::TAG_WORLD_DATA_VERSION, VersionInfo::WORLD_DATA_VERSION);
510 protected function initEntity(CompoundTag $nbt) : void{
511 $this->fireTicks = $nbt->getShort(self::TAG_FIRE, 0);
513 $this->onGround = $nbt->getByte(self::TAG_ON_GROUND, 0) !== 0;
515 $this->fallDistance = $nbt->getFloat(self::TAG_FALL_DISTANCE, 0.0);
517 if(($customNameTag = $nbt->getTag(self::TAG_CUSTOM_NAME)) instanceof StringTag){
518 $this->setNameTag($customNameTag->getValue());
520 if(($customNameVisibleTag = $nbt->getTag(self::TAG_CUSTOM_NAME_VISIBLE)) instanceof StringTag){
522 $this->setNameTagVisible($customNameVisibleTag->getValue() !==
"");
524 $this->setNameTagVisible($nbt->getByte(self::TAG_CUSTOM_NAME_VISIBLE, 1) !== 0);
529 protected function addAttributes() : void{
533 public function attack(EntityDamageEvent $source) : void{
534 if($this->isFireProof() && (
535 $source->getCause() === EntityDamageEvent::CAUSE_FIRE ||
536 $source->getCause() === EntityDamageEvent::CAUSE_FIRE_TICK ||
537 $source->getCause() === EntityDamageEvent::CAUSE_LAVA
543 if($source->isCancelled()){
547 $this->setLastDamageCause($source);
549 $this->setHealth($this->getHealth() - $source->getFinalDamage());
552 public function heal(EntityRegainHealthEvent $source) : void{
554 if($source->isCancelled()){
558 $this->setHealth($this->getHealth() + $source->getAmount());
561 public function kill() : void{
562 if($this->isAlive()){
565 $this->scheduleUpdate();
579 protected function onDeathUpdate(int $tickDiff) : bool{
583 public function isAlive() : bool{
584 return $this->health > 0;
587 public function getHealth() : float{
588 return $this->health;
595 if($amount === $this->health){
600 if($this->isAlive()){
601 if(!$this->justCreated){
607 }elseif($amount <= $this->getMaxHealth() || $amount < $this->health){
608 $this->health = $amount;
610 $this->health = $this->getMaxHealth();
614 public function getMaxHealth() : int{
615 return $this->maxHealth;
618 public function setMaxHealth(
int $amount) : void{
619 $this->maxHealth = $amount;
622 public function setLastDamageCause(EntityDamageEvent $type) : void{
623 $this->lastDamageCause = $type;
626 public function getLastDamageCause() : ?EntityDamageEvent{
627 return $this->lastDamageCause;
630 public function getAttributeMap() : AttributeMap{
631 return $this->attributeMap;
634 public function getNetworkProperties() : EntityMetadataCollection{
635 return $this->networkProperties;
638 protected function entityBaseTick(
int $tickDiff = 1) : bool{
641 if($this->justCreated){
642 $this->justCreated =
false;
643 if(!$this->isAlive()){
648 $changedProperties = $this->getDirtyNetworkData();
649 if(count($changedProperties) > 0){
650 $this->sendData(
null, $changedProperties);
651 $this->networkProperties->clearDirtyProperties();
656 if($this->checkBlockIntersectionsNextTick){
657 $this->checkBlockIntersections();
659 $this->checkBlockIntersectionsNextTick =
true;
661 if($this->location->y <= World::Y_MIN - 16 && $this->isAlive()){
662 $ev =
new EntityDamageEvent($this, EntityDamageEvent::CAUSE_VOID, 10);
667 if($this->isOnFire() && $this->doOnFireTick($tickDiff)){
671 if($this->noDamageTicks > 0){
672 $this->noDamageTicks -= $tickDiff;
673 if($this->noDamageTicks < 0){
674 $this->noDamageTicks = 0;
678 $this->ticksLived += $tickDiff;
683 public function isOnFire() : bool{
684 return $this->fireTicks > 0;
687 public function setOnFire(
int $seconds) : void{
688 $ticks = $seconds * 20;
689 if($ticks > $this->getFireTicks()){
690 $this->setFireTicks($ticks);
692 $this->networkPropertiesDirty =
true;
695 public function getFireTicks() : int{
696 return $this->fireTicks;
703 if($fireTicks < 0 || $fireTicks > 0x7fff){
704 throw new \InvalidArgumentException(
"Fire ticks must be in range 0 ... " . 0x7fff .
", got $fireTicks");
706 if(!$this->isFireProof()){
707 $this->fireTicks = $fireTicks;
708 $this->networkPropertiesDirty =
true;
712 public function extinguish() : void{
713 $this->fireTicks = 0;
714 $this->networkPropertiesDirty =
true;
717 public function isFireProof() : bool{
721 protected function doOnFireTick(
int $tickDiff = 1) : bool{
722 if($this->isFireProof() && $this->isOnFire()){
727 $this->fireTicks -= $tickDiff;
729 if(($this->fireTicks % 20 === 0) || $tickDiff > 20){
730 $this->dealFireDamage();
733 if(!$this->isOnFire()){
750 public function canCollideWith(
Entity $entity) : bool{
751 return !$this->justCreated && $entity !== $this;
754 public function canBeCollidedWith() : bool{
755 return $this->isAlive();
758 protected function updateMovement(
bool $teleport =
false) : void{
759 $diffPosition = $this->location->distanceSquared($this->lastLocation);
760 $diffRotation = ($this->location->yaw - $this->lastLocation->yaw) ** 2 + ($this->location->pitch - $this->lastLocation->pitch) ** 2;
762 $diffMotion = $this->motion->subtractVector($this->lastMotion)->lengthSquared();
764 $still = $this->motion->lengthSquared() === 0.0;
765 $wasStill = $this->lastMotion->lengthSquared() === 0.0;
766 if($wasStill !== $still){
768 $this->setNoClientPredictions($still);
771 if($teleport || $diffPosition > 0.0001 || $diffRotation > 1.0 || (!$wasStill && $still)){
772 $this->lastLocation = $this->location->asLocation();
774 $this->broadcastMovement($teleport);
777 if($diffMotion > 0.0025 || $wasStill !== $still){
778 $this->lastMotion = clone $this->motion;
780 $this->broadcastMotion();
784 public function getOffsetPosition(Vector3 $vector3) : Vector3{
788 protected function broadcastMovement(
bool $teleport =
false) : void{
789 NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [MoveActorAbsolutePacket::create(
791 $this->getOffsetPosition($this->location),
792 $this->location->pitch,
793 $this->location->yaw,
794 $this->location->yaw,
801 ($this->onGround ? MoveActorAbsolutePacket::FLAG_GROUND : 0)
806 protected function broadcastMotion() : void{
807 NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [SetActorMotionPacket::create($this->id, $this->getMotion(), tick: 0)]);
810 public function getGravity() : float{
811 return $this->gravity;
814 public function setGravity(
float $gravity) : void{
815 Utils::checkFloatNotInfOrNaN(
"gravity", $gravity);
816 $this->gravity = $gravity;
819 public function hasGravity() : bool{
820 return $this->gravityEnabled;
823 public function setHasGravity(
bool $v =
true) : void{
824 $this->gravityEnabled = $v;
827 protected function applyDragBeforeGravity() : bool{
831 protected function tryChangeMovement() : void{
832 $friction = 1 - $this->drag;
834 $mY = $this->motion->y;
836 if($this->applyDragBeforeGravity()){
840 if($this->gravityEnabled){
841 $mY -= $this->gravity;
844 if(!$this->applyDragBeforeGravity()){
849 $friction *= $this->getWorld()->getBlockAt((
int) floor($this->location->x), (
int) floor($this->location->y - 1), (
int) floor($this->location->z))->getFrictionFactor();
852 $this->motion =
new Vector3($this->motion->x * $friction, $mY, $this->motion->z * $friction);
855 protected function checkObstruction(
float $x,
float $y,
float $z) : bool{
856 $world = $this->getWorld();
857 if(count($world->getBlockCollisionBoxes($this->boundingBox)) === 0){
861 $floorX = (
int) floor($x);
862 $floorY = (int) floor($y);
863 $floorZ = (int) floor($z);
865 $diffX = $x - $floorX;
866 $diffY = $y - $floorY;
867 $diffZ = $z - $floorZ;
869 if($world->getBlockAt($floorX, $floorY, $floorZ)->isSolid()){
870 $westNonSolid = !$world->getBlockAt($floorX - 1, $floorY, $floorZ)->isSolid();
871 $eastNonSolid = !$world->getBlockAt($floorX + 1, $floorY, $floorZ)->isSolid();
872 $downNonSolid = !$world->getBlockAt($floorX, $floorY - 1, $floorZ)->isSolid();
873 $upNonSolid = !$world->getBlockAt($floorX, $floorY + 1, $floorZ)->isSolid();
874 $northNonSolid = !$world->getBlockAt($floorX, $floorY, $floorZ - 1)->isSolid();
875 $southNonSolid = !$world->getBlockAt($floorX, $floorY, $floorZ + 1)->isSolid();
882 $direction = Facing::WEST;
885 if($eastNonSolid && 1 - $diffX < $limit){
887 $direction = Facing::EAST;
890 if($downNonSolid && $diffY < $limit){
892 $direction = Facing::DOWN;
895 if($upNonSolid && 1 - $diffY < $limit){
897 $direction = Facing::UP;
900 if($northNonSolid && $diffZ < $limit){
902 $direction = Facing::NORTH;
905 if($southNonSolid && 1 - $diffZ < $limit){
906 $direction = Facing::SOUTH;
909 if($direction === -1){
913 $force = Utils::getRandomFloat() * 0.2 + 0.1;
915 $this->motion = match($direction){
916 Facing::WEST => $this->motion->withComponents(-$force,
null,
null),
917 Facing::EAST => $this->motion->withComponents($force,
null,
null),
918 Facing::DOWN => $this->motion->withComponents(
null, -$force,
null),
919 Facing::UP => $this->motion->withComponents(
null, $force,
null),
920 Facing::NORTH => $this->motion->withComponents(
null,
null, -$force),
921 Facing::SOUTH => $this->motion->withComponents(
null,
null, $force),
929 public function getHorizontalFacing() : int{
930 $angle = fmod($this->location->yaw, 360);
935 if((0 <= $angle && $angle < 45) || (315 <= $angle && $angle < 360)){
936 return Facing::SOUTH;
938 if(45 <= $angle && $angle < 135){
941 if(135 <= $angle && $angle < 225){
942 return Facing::NORTH;
948 public function getDirectionVector() : Vector3{
949 $y = -sin(deg2rad($this->location->pitch));
950 $xz = cos(deg2rad($this->location->pitch));
951 $x = -$xz * sin(deg2rad($this->location->yaw));
952 $z = $xz * cos(deg2rad($this->location->yaw));
954 return (
new Vector3($x, $y, $z))->normalize();
957 public function getDirectionPlane() : Vector2{
958 return (new Vector2(-cos(deg2rad($this->location->yaw) - M_PI_2), -sin(deg2rad($this->location->yaw) - M_PI_2)))->normalize();
969 public function onUpdate(
int $currentTick) : bool{
974 $tickDiff = $currentTick - $this->lastUpdate;
976 if(!$this->justCreated){
977 $this->
server->getLogger()->debug(
"Expected tick difference of at least 1, got $tickDiff for " . get_class($this));
983 $this->lastUpdate = $currentTick;
985 if($this->justCreated){
986 $this->onFirstUpdate($currentTick);
989 if(!$this->isAlive()){
990 if($this->onDeathUpdate($tickDiff)){
991 $this->flagForDespawn();
997 $this->timings->startTiming();
999 if($this->hasMovementUpdate()){
1000 $this->tryChangeMovement();
1002 $this->motion = $this->motion->withComponents(
1003 abs($this->motion->x) <= self::MOTION_THRESHOLD ? 0 :
null,
1004 abs($this->motion->y) <= self::MOTION_THRESHOLD ? 0 :
null,
1005 abs($this->motion->z) <= self::MOTION_THRESHOLD ? 0 :
null
1008 if(floatval($this->motion->x) !== 0.0 || floatval($this->motion->y) !== 0.0 || floatval($this->motion->z) !== 0.0){
1009 $this->move($this->motion->x, $this->motion->y, $this->motion->z);
1012 $this->forceMovementUpdate =
false;
1015 $this->updateMovement();
1017 Timings::$entityBaseTick->startTiming();
1018 $hasUpdate = $this->entityBaseTick($tickDiff);
1019 Timings::$entityBaseTick->stopTiming();
1021 $this->timings->stopTiming();
1023 return ($hasUpdate || $this->hasMovementUpdate());
1026 final public function scheduleUpdate() : void{
1028 throw new \LogicException(
"Cannot schedule update on garbage entity " . get_class($this));
1030 $this->getWorld()->updateEntities[$this->id] = $this;
1033 public function onNearbyBlockChange() : void{
1034 $this->setForceMovementUpdate();
1035 $this->scheduleUpdate();
1043 $this->scheduleUpdate();
1051 $this->forceMovementUpdate = $value;
1053 $this->blocksAround =
null;
1061 $this->forceMovementUpdate ||
1062 floatval($this->motion->x) !== 0.0 ||
1063 floatval($this->motion->y) !== 0.0 ||
1064 floatval($this->motion->z) !== 0.0 ||
1069 public function getFallDistance() : float{ return $this->fallDistance; }
1071 public function setFallDistance(
float $fallDistance) : void{
1072 $this->fallDistance = $fallDistance;
1075 public function resetFallDistance() : void{
1076 $this->fallDistance = 0.0;
1079 protected function updateFallState(
float $distanceThisTick,
bool $onGround) : ?float{
1080 if($distanceThisTick < $this->fallDistance){
1083 $this->fallDistance -= $distanceThisTick;
1087 $this->fallDistance = 0;
1089 if($onGround && $this->fallDistance > 0){
1090 $newVerticalVelocity = $this->onHitGround();
1091 $this->resetFallDistance();
1092 return $newVerticalVelocity;
1104 public function getEyeHeight() : float{
1105 return $this->size->getEyeHeight();
1108 public function getEyePos() : Vector3{
1109 return new Vector3($this->location->x, $this->location->y + $this->getEyeHeight(), $this->location->z);
1112 public function onCollideWithPlayer(Player $player) : void{
1123 public function isUnderwater() : bool{
1124 $block = $this->getWorld()->getBlockAt((int) floor($this->location->x), $blockY = (int) floor($y = ($this->location->y + $this->getEyeHeight())), (int) floor($this->location->z));
1126 if($block instanceof
Water){
1127 $f = ($blockY + 1) - ($block->getFluidHeightPercent() - 0.1111111);
1134 public function isInsideOfSolid() : bool{
1135 $block = $this->getWorld()->getBlockAt((int) floor($this->location->x), (int) floor($y = ($this->location->y + $this->getEyeHeight())), (int) floor($this->location->z));
1137 return $block->isSolid() && !$block->isTransparent() && $block->collidesWithBB($this->getBoundingBox());
1140 protected function move(
float $dx,
float $dy,
float $dz) : void{
1141 $this->blocksAround = null;
1143 Timings::$entityMove->startTiming();
1144 Timings::$entityMoveCollision->startTiming();
1150 if($this->keepMovement){
1151 $this->boundingBox->offset($dx, $dy, $dz);
1153 $this->ySize *= self::STEP_CLIP_MULTIPLIER;
1155 $moveBB = clone $this->boundingBox;
1157 assert(abs($dx) <= 20 && abs($dy) <= 20 && abs($dz) <= 20,
"Movement distance is excessive: dx=$dx, dy=$dy, dz=$dz");
1159 $list = $this->getWorld()->getBlockCollisionBoxes($moveBB->addCoord($dx, $dy, $dz));
1161 foreach($list as $bb){
1162 $dy = $bb->calculateYOffset($moveBB, $dy);
1165 $moveBB->offset(0, $dy, 0);
1167 $fallingFlag = ($this->onGround || ($dy !== $wantedY && $wantedY < 0));
1169 foreach($list as $bb){
1170 $dx = $bb->calculateXOffset($moveBB, $dx);
1173 $moveBB->offset($dx, 0, 0);
1175 foreach($list as $bb){
1176 $dz = $bb->calculateZOffset($moveBB, $dz);
1179 $moveBB->offset(0, 0, $dz);
1181 if($this->stepHeight > 0 && $fallingFlag && ($wantedX !== $dx || $wantedZ !== $dz)){
1186 $dy = $this->stepHeight;
1189 $stepBB = clone $this->boundingBox;
1191 $list = $this->getWorld()->getBlockCollisionBoxes($stepBB->addCoord($dx, $dy, $dz));
1192 foreach($list as $bb){
1193 $dy = $bb->calculateYOffset($stepBB, $dy);
1196 $stepBB->offset(0, $dy, 0);
1198 foreach($list as $bb){
1199 $dx = $bb->calculateXOffset($stepBB, $dx);
1202 $stepBB->offset($dx, 0, 0);
1204 foreach($list as $bb){
1205 $dz = $bb->calculateZOffset($stepBB, $dz);
1208 $stepBB->offset(0, 0, $dz);
1211 foreach($list as $bb){
1212 $reverseDY = $bb->calculateYOffset($stepBB, $reverseDY);
1215 $stepBB->offset(0, $reverseDY, 0);
1217 if(($cx ** 2 + $cz ** 2) >= ($dx ** 2 + $dz ** 2)){
1223 $this->ySize += $dy;
1227 $this->boundingBox = $moveBB;
1229 Timings::$entityMoveCollision->stopTiming();
1231 $this->location =
new Location(
1232 ($this->boundingBox->minX + $this->boundingBox->maxX) / 2,
1233 $this->boundingBox->minY - $this->ySize,
1234 ($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2,
1235 $this->location->world,
1236 $this->location->yaw,
1237 $this->location->pitch
1240 $this->getWorld()->onEntityMoved($this);
1241 $this->checkBlockIntersections();
1242 $this->checkGroundState($wantedX, $wantedY, $wantedZ, $dx, $dy, $dz);
1243 $postFallVerticalVelocity = $this->updateFallState($dy, $this->onGround);
1245 $this->motion = $this->motion->withComponents(
1246 $wantedX !== $dx ? 0 :
null,
1247 $postFallVerticalVelocity ?? ($wantedY !== $dy ? 0 :
null),
1248 $wantedZ !== $dz ? 0 :
null
1253 Timings::$entityMove->stopTiming();
1256 protected function checkGroundState(
float $wantedX,
float $wantedY,
float $wantedZ,
float $dx,
float $dy,
float $dz) : void{
1257 $this->isCollidedVertically = $wantedY !== $dy;
1258 $this->isCollidedHorizontally = ($wantedX !== $dx || $wantedZ !== $dz);
1259 $this->isCollided = ($this->isCollidedHorizontally || $this->isCollidedVertically);
1260 $this->onGround = ($wantedY !== $dy && $wantedY < 0);
1269 $minX = (int) floor($this->boundingBox->minX + $inset);
1270 $minY = (int) floor($this->boundingBox->minY + $inset);
1271 $minZ = (int) floor($this->boundingBox->minZ + $inset);
1272 $maxX = (int) floor($this->boundingBox->maxX - $inset);
1273 $maxY = (int) floor($this->boundingBox->maxY - $inset);
1274 $maxZ = (int) floor($this->boundingBox->maxZ - $inset);
1276 $world = $this->getWorld();
1278 for($z = $minZ; $z <= $maxZ; ++$z){
1279 for($x = $minX; $x <= $maxX; ++$x){
1280 for($y = $minY; $y <= $maxY; ++$y){
1281 yield $world->getBlockAt($x, $y, $z);
1291 if($this->blocksAround === null){
1292 $this->blocksAround = [];
1295 foreach($this->getBlocksIntersected($inset) as $block){
1296 if($block->hasEntityCollision()){
1297 $this->blocksAround[] = $block;
1302 return $this->blocksAround;
1312 protected function checkBlockIntersections() : void{
1313 $this->checkBlockIntersectionsNextTick = false;
1316 foreach($this->getBlocksAroundWithEntityInsideActions() as $block){
1317 if(!$block->onEntityInside($this)){
1318 $this->blocksAround =
null;
1320 if(($v = $block->addVelocityToEntity($this)) !==
null){
1325 if(count($vectors) > 0){
1326 $vector = Vector3::sum(...$vectors);
1327 if($vector->lengthSquared() > 0){
1329 $this->motion = $this->motion->addVector($vector->normalize()->multiply($d));
1335 return $this->location->asPosition();
1338 public function getLocation() : Location{
1339 return $this->location->asLocation();
1342 public function getWorld() : World{
1343 return $this->location->getWorld();
1346 protected function setPosition(Vector3 $pos) : bool{
1351 $oldWorld = $this->getWorld();
1352 $newWorld = $pos instanceof Position ? $pos->getWorld() : $oldWorld;
1353 if($oldWorld !== $newWorld){
1354 $this->despawnFromAll();
1355 $oldWorld->removeEntity($this);
1358 $this->location = Location::fromObject(
1361 $this->location->yaw,
1362 $this->location->pitch
1365 $this->recalculateBoundingBox();
1367 $this->blocksAround =
null;
1369 if($oldWorld !== $newWorld){
1370 $newWorld->addEntity($this);
1372 $newWorld->onEntityMoved($this);
1378 public function setRotation(
float $yaw,
float $pitch) : void{
1379 Utils::checkFloatNotInfOrNaN(
"yaw", $yaw);
1380 Utils::checkFloatNotInfOrNaN(
"pitch", $pitch);
1381 $this->location->yaw = $yaw;
1382 $this->location->pitch = $pitch;
1383 $this->scheduleUpdate();
1386 protected function setPositionAndRotation(Vector3 $pos,
float $yaw,
float $pitch) : bool{
1387 if($this->setPosition($pos)){
1388 $this->setRotation($yaw, $pitch);
1396 protected function resetLastMovements() : void{
1397 $this->lastLocation = $this->location->asLocation();
1398 $this->lastMotion = clone $this->motion;
1401 public function getMotion() : Vector3{
1402 return clone $this->motion;
1405 public function setMotion(Vector3 $motion) : bool{
1406 Utils::checkVector3NotInfOrNaN($motion);
1407 if(!$this->justCreated){
1408 $ev =
new EntityMotionEvent($this, $motion);
1410 if($ev->isCancelled()){
1415 $this->motion = clone $motion;
1417 if(!$this->justCreated){
1418 $this->updateMovement();
1427 public function addMotion(
float $x,
float $y,
float $z) : void{
1428 Utils::checkFloatNotInfOrNaN(
"x", $x);
1429 Utils::checkFloatNotInfOrNaN(
"y", $y);
1430 Utils::checkFloatNotInfOrNaN(
"z", $z);
1431 $this->motion = $this->motion->add($x, $y, $z);
1434 public function isOnGround() : bool{
1435 return $this->onGround;
1442 Utils::checkVector3NotInfOrNaN($pos);
1444 $yaw = $yaw ?? $pos->yaw;
1445 $pitch = $pitch ?? $pos->pitch;
1448 Utils::checkFloatNotInfOrNaN(
"yaw", $yaw);
1450 if($pitch !==
null){
1451 Utils::checkFloatNotInfOrNaN(
"pitch", $pitch);
1454 $from = $this->location->asPosition();
1455 $to = Position::fromObject($pos, $pos instanceof Position ? $pos->getWorld() : $this->getWorld());
1456 $ev =
new EntityTeleportEvent($this, $from, $to);
1458 if($ev->isCancelled()){
1462 $pos = $ev->getTo();
1464 $this->setMotion(
new Vector3(0, 0, 0));
1465 if($this->setPositionAndRotation($pos, $yaw ?? $this->location->yaw, $pitch ?? $this->location->pitch)){
1466 $this->resetFallDistance();
1467 $this->setForceMovementUpdate();
1469 $this->updateMovement(
true);
1477 public function getId() : int{
1485 return $this->hasSpawned;
1488 abstract public function getNetworkTypeId() : string;
1494 $player->getNetworkSession()->sendDataPacket(
AddActorPacket::create(
1497 $this->getNetworkTypeId(),
1498 $this->location->asVector3(),
1500 $this->location->pitch,
1501 $this->location->yaw,
1502 $this->location->yaw,
1503 $this->location->yaw,
1504 array_map(function(
Attribute $attr) : NetworkAttribute{
1505 return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue(), []);
1506 }, $this->attributeMap->getAll()),
1507 $this->getAllNetworkData(),
1513 public function spawnTo(
Player $player) : void{
1514 $id = spl_object_id($player);
1518 if(!isset($this->hasSpawned[$id]) && $player->getWorld() === $this->getWorld() && $player->
hasReceivedChunk($this->location->getFloorX() >> Chunk::COORD_BIT_SIZE, $this->location->getFloorZ() >> Chunk::COORD_BIT_SIZE)){
1519 $this->hasSpawned[$id] = $player;
1521 $this->sendSpawnPacket($player);
1525 public function spawnToAll() : void{
1529 foreach($this->getWorld()->getViewersForPosition($this->location) as $player){
1530 $this->spawnTo($player);
1534 public function respawnToAll() : void{
1535 foreach($this->hasSpawned as $key => $player){
1536 unset($this->hasSpawned[$key]);
1537 $this->spawnTo($player);
1546 $id = spl_object_id($player);
1547 if(isset($this->hasSpawned[$id])){
1549 $player->getNetworkSession()->getEntityEventBroadcaster()->onEntityRemoved([$player->getNetworkSession()], $this);
1551 unset($this->hasSpawned[$id]);
1562 fn(
EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onEntityRemoved($recipients, $this)
1564 $this->hasSpawned = [];
1578 $this->needsDespawn = true;
1579 $this->scheduleUpdate();
1582 public function isFlaggedForDespawn() : bool{
1583 return $this->needsDespawn;
1590 return $this->closed;
1599 if($this->closeInFlight){
1604 $this->closeInFlight =
true;
1608 $this->closed =
true;
1609 $this->destroyCycles();
1610 $this->closeInFlight =
false;
1619 $this->despawnFromAll();
1620 if($this->location->isValid()){
1621 $this->getWorld()->removeEntity($this);
1632 $this->lastDamageCause = null;
1641 public function sendData(?array $targets, ?array $data =
null) : void{
1642 $targets = $targets ?? $this->hasSpawned;
1643 $data = $data ?? $this->getAllNetworkData();
1645 NetworkBroadcastUtils::broadcastEntityEvent($targets, fn(
EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->
syncActorData($recipients, $this, $data));
1653 if($this->networkPropertiesDirty){
1654 $this->syncNetworkData($this->networkProperties);
1655 $this->networkPropertiesDirty =
false;
1657 return $this->networkProperties->getDirty();
1665 if($this->networkPropertiesDirty){
1666 $this->syncNetworkData($this->networkProperties);
1667 $this->networkPropertiesDirty =
false;
1669 return $this->networkProperties->getAll();
1672 protected function syncNetworkData(EntityMetadataCollection $properties) : void{
1673 $properties->setByte(EntityMetadataProperties::ALWAYS_SHOW_NAMETAG, $this->alwaysShowNameTag ? 1 : 0);
1674 $properties->setFloat(EntityMetadataProperties::BOUNDING_BOX_HEIGHT, $this->size->getHeight() / $this->scale);
1675 $properties->setFloat(EntityMetadataProperties::BOUNDING_BOX_WIDTH, $this->size->getWidth() / $this->scale);
1676 $properties->setFloat(EntityMetadataProperties::SCALE, $this->scale);
1677 $properties->setLong(EntityMetadataProperties::LEAD_HOLDER_EID, -1);
1678 $properties->setLong(EntityMetadataProperties::OWNER_EID, $this->ownerId ?? -1);
1679 $properties->setLong(EntityMetadataProperties::TARGET_EID, $this->targetId ?? 0);
1680 $properties->setString(EntityMetadataProperties::NAMETAG, $this->nameTag);
1681 $properties->setString(EntityMetadataProperties::SCORE_TAG, $this->scoreTag);
1682 $properties->setByte(EntityMetadataProperties::COLOR, 0);
1684 $properties->setGenericFlag(EntityMetadataFlags::AFFECTED_BY_GRAVITY, $this->gravityEnabled);
1685 $properties->setGenericFlag(EntityMetadataFlags::CAN_CLIMB, $this->canClimb);
1686 $properties->setGenericFlag(EntityMetadataFlags::CAN_SHOW_NAMETAG, $this->nameTagVisible);
1687 $properties->setGenericFlag(EntityMetadataFlags::HAS_COLLISION,
true);
1688 $properties->setGenericFlag(EntityMetadataFlags::NO_AI, $this->noClientPredictions);
1689 $properties->setGenericFlag(EntityMetadataFlags::INVISIBLE, $this->invisible);
1690 $properties->setGenericFlag(EntityMetadataFlags::SILENT, $this->silent);
1691 $properties->setGenericFlag(EntityMetadataFlags::ONFIRE, $this->isOnFire());
1692 $properties->setGenericFlag(EntityMetadataFlags::WALLCLIMBING, $this->canClimbWalls);
1708 $this->getWorld()->addSound($this->location->asVector3(), $sound, $targets ?? $this->getViewers());
1712 public function __destruct(){
1716 public function __toString(){
1717 return (
new \ReflectionClass($this))->getShortName() .
"(" . $this->getId() .
")";