PocketMine-MP 5.23.3 git-4a4572131f27ab967701ceaaf2020cfbe26e375c
Loading...
Searching...
No Matches
Entity.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
27namespace pocketmine\entity;
28
69use function abs;
70use function array_map;
71use function assert;
72use function cos;
73use function count;
74use function deg2rad;
75use function floor;
76use function fmod;
77use function get_class;
78use function sin;
79use function spl_object_id;
80use const M_PI_2;
81
82abstract class Entity{
83
84 public const MOTION_THRESHOLD = 0.00001;
85 protected const STEP_CLIP_MULTIPLIER = 0.4;
86
87 private const TAG_FIRE = "Fire"; //TAG_Short
88 private const TAG_ON_GROUND = "OnGround"; //TAG_Byte
89 private const TAG_FALL_DISTANCE = "FallDistance"; //TAG_Float
90 private const TAG_CUSTOM_NAME = "CustomName"; //TAG_String
91 private const TAG_CUSTOM_NAME_VISIBLE = "CustomNameVisible"; //TAG_Byte
92 public const TAG_POS = "Pos"; //TAG_List<TAG_Double>|TAG_List<TAG_Float>
93 public const TAG_MOTION = "Motion"; //TAG_List<TAG_Double>|TAG_List<TAG_Float>
94 public const TAG_ROTATION = "Rotation"; //TAG_List<TAG_Float>
95
96 private static int $entityCount = 1;
97
101 public static function nextRuntimeId() : int{
102 return self::$entityCount++;
103 }
104
109 protected array $hasSpawned = [];
110
111 protected int $id;
112
113 private EntityMetadataCollection $networkProperties;
114
115 protected ?EntityDamageEvent $lastDamageCause = null;
116
118 protected ?array $blocksAround = null;
119
120 protected Location $location;
121 protected Location $lastLocation;
122 protected Vector3 $motion;
123 protected Vector3 $lastMotion;
124 protected bool $forceMovementUpdate = false;
125 private bool $checkBlockIntersectionsNextTick = true;
126
127 public AxisAlignedBB $boundingBox;
128 public bool $onGround = false;
129
130 public EntitySizeInfo $size;
131
132 private float $health = 20.0;
133 private int $maxHealth = 20;
134
135 protected float $ySize = 0.0;
136 protected float $stepHeight = 0.0;
137 public bool $keepMovement = false;
138
139 public float $fallDistance = 0.0;
140 public int $ticksLived = 0;
141 public int $lastUpdate;
142 protected int $fireTicks = 0;
143
144 private bool $savedWithChunk = true;
145
146 public bool $isCollided = false;
147 public bool $isCollidedHorizontally = false;
148 public bool $isCollidedVertically = false;
149
150 public int $noDamageTicks = 0;
151 protected bool $justCreated = true;
152
153 protected AttributeMap $attributeMap;
154
155 protected float $gravity;
156 protected float $drag;
157 protected bool $gravityEnabled = true;
158
159 protected Server $server;
160
161 protected bool $closed = false;
162 private bool $closeInFlight = false;
163 private bool $needsDespawn = false;
164
165 protected TimingsHandler $timings;
166
167 protected bool $networkPropertiesDirty = false;
168
169 protected string $nameTag = "";
170 protected bool $nameTagVisible = true;
171 protected bool $alwaysShowNameTag = false;
172 protected string $scoreTag = "";
173 protected float $scale = 1.0;
174
175 protected bool $canClimb = false;
176 protected bool $canClimbWalls = false;
177 protected bool $noClientPredictions = false;
178 protected bool $invisible = false;
179 protected bool $silent = false;
180
181 protected ?int $ownerId = null;
182 protected ?int $targetId = null;
183
184 private bool $constructorCalled = false;
185
186 public function __construct(Location $location, ?CompoundTag $nbt = null){
187 if($this->constructorCalled){
188 throw new \LogicException("Attempted to call constructor for an Entity multiple times");
189 }
190 $this->constructorCalled = true;
191 Utils::checkLocationNotInfOrNaN($location);
192
193 $this->timings = Timings::getEntityTimings($this);
194
195 $this->size = $this->getInitialSizeInfo();
196 $this->drag = $this->getInitialDragMultiplier();
197 $this->gravity = $this->getInitialGravity();
198
199 $this->id = self::nextRuntimeId();
200 $this->server = $location->getWorld()->getServer();
201
202 $this->location = $location->asLocation();
203
204 $this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0);
205 $this->recalculateBoundingBox();
206
207 if($nbt !== null){
208 $this->motion = EntityDataHelper::parseVec3($nbt, self::TAG_MOTION, true);
209 }else{
210 $this->motion = Vector3::zero();
211 }
212
213 $this->resetLastMovements();
214
215 $this->networkProperties = new EntityMetadataCollection();
216
217 $this->attributeMap = new AttributeMap();
218 $this->addAttributes();
219
220 $this->initEntity($nbt ?? new CompoundTag());
221
222 $this->getWorld()->addEntity($this);
223
224 $this->lastUpdate = $this->server->getTick();
225
226 $this->scheduleUpdate();
227 }
228
229 abstract protected function getInitialSizeInfo() : EntitySizeInfo;
230
237 abstract protected function getInitialDragMultiplier() : float;
238
244 abstract protected function getInitialGravity() : float;
245
246 public function getNameTag() : string{
247 return $this->nameTag;
248 }
249
250 public function isNameTagVisible() : bool{
251 return $this->nameTagVisible;
252 }
253
254 public function isNameTagAlwaysVisible() : bool{
255 return $this->alwaysShowNameTag;
256 }
257
262 public function canBeRenamed() : bool{
263 return false;
264 }
265
266 public function setNameTag(string $name) : void{
267 $this->nameTag = $name;
268 $this->networkPropertiesDirty = true;
269 }
270
271 public function setNameTagVisible(bool $value = true) : void{
272 $this->nameTagVisible = $value;
273 $this->networkPropertiesDirty = true;
274 }
275
276 public function setNameTagAlwaysVisible(bool $value = true) : void{
277 $this->alwaysShowNameTag = $value;
278 $this->networkPropertiesDirty = true;
279 }
280
281 public function getScoreTag() : ?string{
282 return $this->scoreTag; //TODO: maybe this shouldn't be nullable?
283 }
284
285 public function setScoreTag(string $score) : void{
286 $this->scoreTag = $score;
287 $this->networkPropertiesDirty = true;
288 }
289
290 public function getScale() : float{
291 return $this->scale;
292 }
293
294 public function setScale(float $value) : void{
295 if($value <= 0){
296 throw new \InvalidArgumentException("Scale must be greater than 0");
297 }
298 $this->scale = $value;
299 $this->setSize($this->getInitialSizeInfo()->scale($value));
300 }
301
302 public function getBoundingBox() : AxisAlignedBB{
303 return $this->boundingBox;
304 }
305
306 protected function recalculateBoundingBox() : void{
307 $halfWidth = $this->size->getWidth() / 2;
308
309 $this->boundingBox = new AxisAlignedBB(
310 $this->location->x - $halfWidth,
311 $this->location->y + $this->ySize,
312 $this->location->z - $halfWidth,
313 $this->location->x + $halfWidth,
314 $this->location->y + $this->size->getHeight() + $this->ySize,
315 $this->location->z + $halfWidth
316 );
317 }
318
319 public function getSize() : EntitySizeInfo{
320 return $this->size;
321 }
322
323 protected function setSize(EntitySizeInfo $size) : void{
324 $this->size = $size;
325 $this->recalculateBoundingBox();
326 $this->networkPropertiesDirty = true;
327 }
328
333 public function hasNoClientPredictions() : bool{
334 return $this->noClientPredictions;
335 }
336
345 public function setNoClientPredictions(bool $value = true) : void{
346 $this->noClientPredictions = $value;
347 $this->networkPropertiesDirty = true;
348 }
349
350 public function isInvisible() : bool{
351 return $this->invisible;
352 }
353
354 public function setInvisible(bool $value = true) : void{
355 $this->invisible = $value;
356 $this->networkPropertiesDirty = true;
357 }
358
359 public function isSilent() : bool{
360 return $this->silent;
361 }
362
363 public function setSilent(bool $value = true) : void{
364 $this->silent = $value;
365 $this->networkPropertiesDirty = true;
366 }
367
371 public function canClimb() : bool{
372 return $this->canClimb;
373 }
374
378 public function setCanClimb(bool $value = true) : void{
379 $this->canClimb = $value;
380 $this->networkPropertiesDirty = true;
381 }
382
386 public function canClimbWalls() : bool{
387 return $this->canClimbWalls;
388 }
389
393 public function setCanClimbWalls(bool $value = true) : void{
394 $this->canClimbWalls = $value;
395 $this->networkPropertiesDirty = true;
396 }
397
401 public function getOwningEntityId() : ?int{
402 return $this->ownerId;
403 }
404
408 public function getOwningEntity() : ?Entity{
409 return $this->ownerId !== null ? $this->server->getWorldManager()->findEntity($this->ownerId) : null;
410 }
411
417 public function setOwningEntity(?Entity $owner) : void{
418 if($owner === null){
419 $this->ownerId = null;
420 }elseif($owner->closed){
421 throw new \InvalidArgumentException("Supplied owning entity is garbage and cannot be used");
422 }else{
423 $this->ownerId = $owner->getId();
424 }
425 $this->networkPropertiesDirty = true;
426 }
427
431 public function getTargetEntityId() : ?int{
432 return $this->targetId;
433 }
434
439 public function getTargetEntity() : ?Entity{
440 return $this->targetId !== null ? $this->server->getWorldManager()->findEntity($this->targetId) : null;
441 }
442
448 public function setTargetEntity(?Entity $target) : void{
449 if($target === null){
450 $this->targetId = null;
451 }elseif($target->closed){
452 throw new \InvalidArgumentException("Supplied target entity is garbage and cannot be used");
453 }else{
454 $this->targetId = $target->getId();
455 }
456 $this->networkPropertiesDirty = true;
457 }
458
462 public function canSaveWithChunk() : bool{
463 return $this->savedWithChunk;
464 }
465
470 public function setCanSaveWithChunk(bool $value) : void{
471 $this->savedWithChunk = $value;
472 }
473
474 public function saveNBT() : CompoundTag{
475 $nbt = CompoundTag::create()
476 ->setTag(self::TAG_POS, new ListTag([
477 new DoubleTag($this->location->x),
478 new DoubleTag($this->location->y),
479 new DoubleTag($this->location->z)
480 ]))
481 ->setTag(self::TAG_MOTION, new ListTag([
482 new DoubleTag($this->motion->x),
483 new DoubleTag($this->motion->y),
484 new DoubleTag($this->motion->z)
485 ]))
486 ->setTag(self::TAG_ROTATION, new ListTag([
487 new FloatTag($this->location->yaw),
488 new FloatTag($this->location->pitch)
489 ]));
490
491 if(!($this instanceof Player)){
492 EntityFactory::getInstance()->injectSaveId(get_class($this), $nbt);
493
494 if($this->getNameTag() !== ""){
495 $nbt->setString(self::TAG_CUSTOM_NAME, $this->getNameTag());
496 $nbt->setByte(self::TAG_CUSTOM_NAME_VISIBLE, $this->isNameTagVisible() ? 1 : 0);
497 }
498 }
499
500 $nbt->setFloat(self::TAG_FALL_DISTANCE, $this->fallDistance);
501 $nbt->setShort(self::TAG_FIRE, $this->fireTicks);
502 $nbt->setByte(self::TAG_ON_GROUND, $this->onGround ? 1 : 0);
503
504 $nbt->setLong(VersionInfo::TAG_WORLD_DATA_VERSION, VersionInfo::WORLD_DATA_VERSION);
505
506 return $nbt;
507 }
508
509 protected function initEntity(CompoundTag $nbt) : void{
510 $this->fireTicks = $nbt->getShort(self::TAG_FIRE, 0);
511
512 $this->onGround = $nbt->getByte(self::TAG_ON_GROUND, 0) !== 0;
513
514 $this->fallDistance = $nbt->getFloat(self::TAG_FALL_DISTANCE, 0.0);
515
516 if(($customNameTag = $nbt->getTag(self::TAG_CUSTOM_NAME)) instanceof StringTag){
517 $this->setNameTag($customNameTag->getValue());
518
519 if(($customNameVisibleTag = $nbt->getTag(self::TAG_CUSTOM_NAME_VISIBLE)) instanceof StringTag){
520 //Older versions incorrectly saved this as a string (see 890f72dbf23a77f294169b79590770470041adc4)
521 $this->setNameTagVisible($customNameVisibleTag->getValue() !== "");
522 }else{
523 $this->setNameTagVisible($nbt->getByte(self::TAG_CUSTOM_NAME_VISIBLE, 1) !== 0);
524 }
525 }
526 }
527
528 protected function addAttributes() : void{
529
530 }
531
532 public function attack(EntityDamageEvent $source) : void{
533 if($this->isFireProof() && (
534 $source->getCause() === EntityDamageEvent::CAUSE_FIRE ||
535 $source->getCause() === EntityDamageEvent::CAUSE_FIRE_TICK ||
536 $source->getCause() === EntityDamageEvent::CAUSE_LAVA
537 )
538 ){
539 $source->cancel();
540 }
541 $source->call();
542 if($source->isCancelled()){
543 return;
544 }
545
546 $this->setLastDamageCause($source);
547
548 $this->setHealth($this->getHealth() - $source->getFinalDamage());
549 }
550
551 public function heal(EntityRegainHealthEvent $source) : void{
552 $source->call();
553 if($source->isCancelled()){
554 return;
555 }
556
557 $this->setHealth($this->getHealth() + $source->getAmount());
558 }
559
560 public function kill() : void{
561 if($this->isAlive()){
562 $this->health = 0;
563 $this->onDeath();
564 $this->scheduleUpdate();
565 }
566 }
567
571 protected function onDeath() : void{
572
573 }
574
578 protected function onDeathUpdate(int $tickDiff) : bool{
579 return true;
580 }
581
582 public function isAlive() : bool{
583 return $this->health > 0;
584 }
585
586 public function getHealth() : float{
587 return $this->health;
588 }
589
593 public function setHealth(float $amount) : void{
594 if($amount == $this->health){
595 return;
596 }
597
598 if($amount <= 0){
599 if($this->isAlive()){
600 if(!$this->justCreated){
601 $this->kill();
602 }else{
603 $this->health = 0;
604 }
605 }
606 }elseif($amount <= $this->getMaxHealth() || $amount < $this->health){
607 $this->health = $amount;
608 }else{
609 $this->health = $this->getMaxHealth();
610 }
611 }
612
613 public function getMaxHealth() : int{
614 return $this->maxHealth;
615 }
616
617 public function setMaxHealth(int $amount) : void{
618 $this->maxHealth = $amount;
619 }
620
621 public function setLastDamageCause(EntityDamageEvent $type) : void{
622 $this->lastDamageCause = $type;
623 }
624
625 public function getLastDamageCause() : ?EntityDamageEvent{
626 return $this->lastDamageCause;
627 }
628
629 public function getAttributeMap() : AttributeMap{
630 return $this->attributeMap;
631 }
632
633 public function getNetworkProperties() : EntityMetadataCollection{
634 return $this->networkProperties;
635 }
636
637 protected function entityBaseTick(int $tickDiff = 1) : bool{
638 //TODO: check vehicles
639
640 if($this->justCreated){
641 $this->justCreated = false;
642 if(!$this->isAlive()){
643 $this->kill();
644 }
645 }
646
647 $changedProperties = $this->getDirtyNetworkData();
648 if(count($changedProperties) > 0){
649 $this->sendData(null, $changedProperties);
650 $this->networkProperties->clearDirtyProperties();
651 }
652
653 $hasUpdate = false;
654
655 if($this->checkBlockIntersectionsNextTick){
656 $this->checkBlockIntersections();
657 }
658 $this->checkBlockIntersectionsNextTick = true;
659
660 if($this->location->y <= World::Y_MIN - 16 && $this->isAlive()){
661 $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_VOID, 10);
662 $this->attack($ev);
663 $hasUpdate = true;
664 }
665
666 if($this->isOnFire() && $this->doOnFireTick($tickDiff)){
667 $hasUpdate = true;
668 }
669
670 if($this->noDamageTicks > 0){
671 $this->noDamageTicks -= $tickDiff;
672 if($this->noDamageTicks < 0){
673 $this->noDamageTicks = 0;
674 }
675 }
676
677 $this->ticksLived += $tickDiff;
678
679 return $hasUpdate;
680 }
681
682 public function isOnFire() : bool{
683 return $this->fireTicks > 0;
684 }
685
686 public function setOnFire(int $seconds) : void{
687 $ticks = $seconds * 20;
688 if($ticks > $this->getFireTicks()){
689 $this->setFireTicks($ticks);
690 }
691 $this->networkPropertiesDirty = true;
692 }
693
694 public function getFireTicks() : int{
695 return $this->fireTicks;
696 }
697
701 public function setFireTicks(int $fireTicks) : void{
702 if($fireTicks < 0 || $fireTicks > 0x7fff){
703 throw new \InvalidArgumentException("Fire ticks must be in range 0 ... " . 0x7fff . ", got $fireTicks");
704 }
705 if(!$this->isFireProof()){
706 $this->fireTicks = $fireTicks;
707 $this->networkPropertiesDirty = true;
708 }
709 }
710
711 public function extinguish() : void{
712 $this->fireTicks = 0;
713 $this->networkPropertiesDirty = true;
714 }
715
716 public function isFireProof() : bool{
717 return false;
718 }
719
720 protected function doOnFireTick(int $tickDiff = 1) : bool{
721 if($this->isFireProof() && $this->isOnFire()){
722 $this->extinguish();
723 return false;
724 }
725
726 $this->fireTicks -= $tickDiff;
727
728 if(($this->fireTicks % 20 === 0) || $tickDiff > 20){
729 $this->dealFireDamage();
730 }
731
732 if(!$this->isOnFire()){
733 $this->extinguish();
734 }else{
735 return true;
736 }
737
738 return false;
739 }
740
744 protected function dealFireDamage() : void{
745 $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_FIRE_TICK, 1);
746 $this->attack($ev);
747 }
748
749 public function canCollideWith(Entity $entity) : bool{
750 return !$this->justCreated && $entity !== $this;
751 }
752
753 public function canBeCollidedWith() : bool{
754 return $this->isAlive();
755 }
756
757 protected function updateMovement(bool $teleport = false) : void{
758 $diffPosition = $this->location->distanceSquared($this->lastLocation);
759 $diffRotation = ($this->location->yaw - $this->lastLocation->yaw) ** 2 + ($this->location->pitch - $this->lastLocation->pitch) ** 2;
760
761 $diffMotion = $this->motion->subtractVector($this->lastMotion)->lengthSquared();
762
763 $still = $this->motion->lengthSquared() == 0.0;
764 $wasStill = $this->lastMotion->lengthSquared() == 0.0;
765 if($wasStill !== $still){
766 //TODO: hack for client-side AI interference: prevent client sided movement when motion is 0
767 $this->setNoClientPredictions($still);
768 }
769
770 if($teleport || $diffPosition > 0.0001 || $diffRotation > 1.0 || (!$wasStill && $still)){
771 $this->lastLocation = $this->location->asLocation();
772
773 $this->broadcastMovement($teleport);
774 }
775
776 if($diffMotion > 0.0025 || $wasStill !== $still){ //0.05 ** 2
777 $this->lastMotion = clone $this->motion;
778
779 $this->broadcastMotion();
780 }
781 }
782
783 public function getOffsetPosition(Vector3 $vector3) : Vector3{
784 return $vector3;
785 }
786
787 protected function broadcastMovement(bool $teleport = false) : void{
788 NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [MoveActorAbsolutePacket::create(
789 $this->id,
790 $this->getOffsetPosition($this->location),
791 $this->location->pitch,
792 $this->location->yaw,
793 $this->location->yaw,
794 (
795 //TODO: We should be setting FLAG_TELEPORT here to disable client-side movement interpolation, but it
796 //breaks player teleporting (observers see the player rubberband back to the pre-teleport position while
797 //the teleported player sees themselves at the correct position), and does nothing whatsoever for
798 //non-player entities (movement is still interpolated). Both of these are client bugs.
799 //See https://github.com/pmmp/PocketMine-MP/issues/4394
800 ($this->onGround ? MoveActorAbsolutePacket::FLAG_GROUND : 0)
801 )
802 )]);
803 }
804
805 protected function broadcastMotion() : void{
806 NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [SetActorMotionPacket::create($this->id, $this->getMotion(), tick: 0)]);
807 }
808
809 public function getGravity() : float{
810 return $this->gravity;
811 }
812
813 public function setGravity(float $gravity) : void{
814 Utils::checkFloatNotInfOrNaN("gravity", $gravity);
815 $this->gravity = $gravity;
816 }
817
818 public function hasGravity() : bool{
819 return $this->gravityEnabled;
820 }
821
822 public function setHasGravity(bool $v = true) : void{
823 $this->gravityEnabled = $v;
824 }
825
826 protected function applyDragBeforeGravity() : bool{
827 return false;
828 }
829
830 protected function tryChangeMovement() : void{
831 $friction = 1 - $this->drag;
832
833 $mY = $this->motion->y;
834
835 if($this->applyDragBeforeGravity()){
836 $mY *= $friction;
837 }
838
839 if($this->gravityEnabled){
840 $mY -= $this->gravity;
841 }
842
843 if(!$this->applyDragBeforeGravity()){
844 $mY *= $friction;
845 }
846
847 if($this->onGround){
848 $friction *= $this->getWorld()->getBlockAt((int) floor($this->location->x), (int) floor($this->location->y - 1), (int) floor($this->location->z))->getFrictionFactor();
849 }
850
851 $this->motion = new Vector3($this->motion->x * $friction, $mY, $this->motion->z * $friction);
852 }
853
854 protected function checkObstruction(float $x, float $y, float $z) : bool{
855 $world = $this->getWorld();
856 if(count($world->getBlockCollisionBoxes($this->boundingBox)) === 0){
857 return false;
858 }
859
860 $floorX = (int) floor($x);
861 $floorY = (int) floor($y);
862 $floorZ = (int) floor($z);
863
864 $diffX = $x - $floorX;
865 $diffY = $y - $floorY;
866 $diffZ = $z - $floorZ;
867
868 if($world->getBlockAt($floorX, $floorY, $floorZ)->isSolid()){
869 $westNonSolid = !$world->getBlockAt($floorX - 1, $floorY, $floorZ)->isSolid();
870 $eastNonSolid = !$world->getBlockAt($floorX + 1, $floorY, $floorZ)->isSolid();
871 $downNonSolid = !$world->getBlockAt($floorX, $floorY - 1, $floorZ)->isSolid();
872 $upNonSolid = !$world->getBlockAt($floorX, $floorY + 1, $floorZ)->isSolid();
873 $northNonSolid = !$world->getBlockAt($floorX, $floorY, $floorZ - 1)->isSolid();
874 $southNonSolid = !$world->getBlockAt($floorX, $floorY, $floorZ + 1)->isSolid();
875
876 $direction = -1;
877 $limit = 9999;
878
879 if($westNonSolid){
880 $limit = $diffX;
881 $direction = Facing::WEST;
882 }
883
884 if($eastNonSolid && 1 - $diffX < $limit){
885 $limit = 1 - $diffX;
886 $direction = Facing::EAST;
887 }
888
889 if($downNonSolid && $diffY < $limit){
890 $limit = $diffY;
891 $direction = Facing::DOWN;
892 }
893
894 if($upNonSolid && 1 - $diffY < $limit){
895 $limit = 1 - $diffY;
896 $direction = Facing::UP;
897 }
898
899 if($northNonSolid && $diffZ < $limit){
900 $limit = $diffZ;
901 $direction = Facing::NORTH;
902 }
903
904 if($southNonSolid && 1 - $diffZ < $limit){
905 $direction = Facing::SOUTH;
906 }
907
908 if($direction === -1){
909 return false;
910 }
911
912 $force = Utils::getRandomFloat() * 0.2 + 0.1;
913
914 $this->motion = match($direction){
915 Facing::WEST => $this->motion->withComponents(-$force, null, null),
916 Facing::EAST => $this->motion->withComponents($force, null, null),
917 Facing::DOWN => $this->motion->withComponents(null, -$force, null),
918 Facing::UP => $this->motion->withComponents(null, $force, null),
919 Facing::NORTH => $this->motion->withComponents(null, null, -$force),
920 Facing::SOUTH => $this->motion->withComponents(null, null, $force),
921 };
922 return true;
923 }
924
925 return false;
926 }
927
928 public function getHorizontalFacing() : int{
929 $angle = fmod($this->location->yaw, 360);
930 if($angle < 0){
931 $angle += 360.0;
932 }
933
934 if((0 <= $angle && $angle < 45) || (315 <= $angle && $angle < 360)){
935 return Facing::SOUTH;
936 }
937 if(45 <= $angle && $angle < 135){
938 return Facing::WEST;
939 }
940 if(135 <= $angle && $angle < 225){
941 return Facing::NORTH;
942 }
943
944 return Facing::EAST;
945 }
946
947 public function getDirectionVector() : Vector3{
948 $y = -sin(deg2rad($this->location->pitch));
949 $xz = cos(deg2rad($this->location->pitch));
950 $x = -$xz * sin(deg2rad($this->location->yaw));
951 $z = $xz * cos(deg2rad($this->location->yaw));
952
953 return (new Vector3($x, $y, $z))->normalize();
954 }
955
956 public function getDirectionPlane() : Vector2{
957 return (new Vector2(-cos(deg2rad($this->location->yaw) - M_PI_2), -sin(deg2rad($this->location->yaw) - M_PI_2)))->normalize();
958 }
959
964 protected function onFirstUpdate(int $currentTick) : void{
965 (new EntitySpawnEvent($this))->call();
966 }
967
968 public function onUpdate(int $currentTick) : bool{
969 if($this->closed){
970 return false;
971 }
972
973 $tickDiff = $currentTick - $this->lastUpdate;
974 if($tickDiff <= 0){
975 if(!$this->justCreated){
976 $this->server->getLogger()->debug("Expected tick difference of at least 1, got $tickDiff for " . get_class($this));
977 }
978
979 return true;
980 }
981
982 $this->lastUpdate = $currentTick;
983
984 if($this->justCreated){
985 $this->onFirstUpdate($currentTick);
986 }
987
988 if(!$this->isAlive()){
989 if($this->onDeathUpdate($tickDiff)){
990 $this->flagForDespawn();
991 }
992
993 return true;
994 }
995
996 $this->timings->startTiming();
997
998 if($this->hasMovementUpdate()){
999 $this->tryChangeMovement();
1000
1001 $this->motion = $this->motion->withComponents(
1002 abs($this->motion->x) <= self::MOTION_THRESHOLD ? 0 : null,
1003 abs($this->motion->y) <= self::MOTION_THRESHOLD ? 0 : null,
1004 abs($this->motion->z) <= self::MOTION_THRESHOLD ? 0 : null
1005 );
1006
1007 if($this->motion->x != 0 || $this->motion->y != 0 || $this->motion->z != 0){
1008 $this->move($this->motion->x, $this->motion->y, $this->motion->z);
1009 }
1010
1011 $this->forceMovementUpdate = false;
1012 }
1013
1014 $this->updateMovement();
1015
1016 Timings::$entityBaseTick->startTiming();
1017 $hasUpdate = $this->entityBaseTick($tickDiff);
1018 Timings::$entityBaseTick->stopTiming();
1019
1020 $this->timings->stopTiming();
1021
1022 return ($hasUpdate || $this->hasMovementUpdate());
1023 }
1024
1025 final public function scheduleUpdate() : void{
1026 if($this->closed){
1027 throw new \LogicException("Cannot schedule update on garbage entity " . get_class($this));
1028 }
1029 $this->getWorld()->updateEntities[$this->id] = $this;
1030 }
1031
1032 public function onNearbyBlockChange() : void{
1033 $this->setForceMovementUpdate();
1034 $this->scheduleUpdate();
1035 }
1036
1041 public function onRandomUpdate() : void{
1042 $this->scheduleUpdate();
1043 }
1044
1049 final public function setForceMovementUpdate(bool $value = true) : void{
1050 $this->forceMovementUpdate = $value;
1051
1052 $this->blocksAround = null;
1053 }
1054
1058 public function hasMovementUpdate() : bool{
1059 return (
1060 $this->forceMovementUpdate ||
1061 $this->motion->x != 0 ||
1062 $this->motion->y != 0 ||
1063 $this->motion->z != 0 ||
1064 !$this->onGround
1065 );
1066 }
1067
1068 public function getFallDistance() : float{ return $this->fallDistance; }
1069
1070 public function setFallDistance(float $fallDistance) : void{
1071 $this->fallDistance = $fallDistance;
1072 }
1073
1074 public function resetFallDistance() : void{
1075 $this->fallDistance = 0.0;
1076 }
1077
1078 protected function updateFallState(float $distanceThisTick, bool $onGround) : ?float{
1079 if($distanceThisTick < $this->fallDistance){
1080 //we've fallen some distance (distanceThisTick is negative)
1081 //or we ascended back towards where fall distance was measured from initially (distanceThisTick is positive but less than existing fallDistance)
1082 $this->fallDistance -= $distanceThisTick;
1083 }else{
1084 //we ascended past the apex where fall distance was originally being measured from
1085 //reset it so it will be measured starting from the new, higher position
1086 $this->fallDistance = 0;
1087 }
1088 if($onGround && $this->fallDistance > 0){
1089 $newVerticalVelocity = $this->onHitGround();
1090 $this->resetFallDistance();
1091 return $newVerticalVelocity;
1092 }
1093 return null;
1094 }
1095
1099 protected function onHitGround() : ?float{
1100 return null;
1101 }
1102
1103 public function getEyeHeight() : float{
1104 return $this->size->getEyeHeight();
1105 }
1106
1107 public function getEyePos() : Vector3{
1108 return new Vector3($this->location->x, $this->location->y + $this->getEyeHeight(), $this->location->z);
1109 }
1110
1111 public function onCollideWithPlayer(Player $player) : void{
1112
1113 }
1114
1118 public function onInteract(Player $player, Vector3 $clickPos) : bool{
1119 return false;
1120 }
1121
1122 public function isUnderwater() : bool{
1123 $block = $this->getWorld()->getBlockAt((int) floor($this->location->x), $blockY = (int) floor($y = ($this->location->y + $this->getEyeHeight())), (int) floor($this->location->z));
1124
1125 if($block instanceof Water){
1126 $f = ($blockY + 1) - ($block->getFluidHeightPercent() - 0.1111111);
1127 return $y < $f;
1128 }
1129
1130 return false;
1131 }
1132
1133 public function isInsideOfSolid() : bool{
1134 $block = $this->getWorld()->getBlockAt((int) floor($this->location->x), (int) floor($y = ($this->location->y + $this->getEyeHeight())), (int) floor($this->location->z));
1135
1136 return $block->isSolid() && !$block->isTransparent() && $block->collidesWithBB($this->getBoundingBox());
1137 }
1138
1139 protected function move(float $dx, float $dy, float $dz) : void{
1140 $this->blocksAround = null;
1141
1142 Timings::$entityMove->startTiming();
1143 Timings::$entityMoveCollision->startTiming();
1144
1145 $wantedX = $dx;
1146 $wantedY = $dy;
1147 $wantedZ = $dz;
1148
1149 if($this->keepMovement){
1150 $this->boundingBox->offset($dx, $dy, $dz);
1151 }else{
1152 $this->ySize *= self::STEP_CLIP_MULTIPLIER;
1153
1154 $moveBB = clone $this->boundingBox;
1155
1156 assert(abs($dx) <= 20 && abs($dy) <= 20 && abs($dz) <= 20, "Movement distance is excessive: dx=$dx, dy=$dy, dz=$dz");
1157
1158 $list = $this->getWorld()->getBlockCollisionBoxes($moveBB->addCoord($dx, $dy, $dz));
1159
1160 foreach($list as $bb){
1161 $dy = $bb->calculateYOffset($moveBB, $dy);
1162 }
1163
1164 $moveBB->offset(0, $dy, 0);
1165
1166 $fallingFlag = ($this->onGround || ($dy != $wantedY && $wantedY < 0));
1167
1168 foreach($list as $bb){
1169 $dx = $bb->calculateXOffset($moveBB, $dx);
1170 }
1171
1172 $moveBB->offset($dx, 0, 0);
1173
1174 foreach($list as $bb){
1175 $dz = $bb->calculateZOffset($moveBB, $dz);
1176 }
1177
1178 $moveBB->offset(0, 0, $dz);
1179
1180 if($this->stepHeight > 0 && $fallingFlag && ($wantedX != $dx || $wantedZ != $dz)){
1181 $cx = $dx;
1182 $cy = $dy;
1183 $cz = $dz;
1184 $dx = $wantedX;
1185 $dy = $this->stepHeight;
1186 $dz = $wantedZ;
1187
1188 $stepBB = clone $this->boundingBox;
1189
1190 $list = $this->getWorld()->getBlockCollisionBoxes($stepBB->addCoord($dx, $dy, $dz));
1191 foreach($list as $bb){
1192 $dy = $bb->calculateYOffset($stepBB, $dy);
1193 }
1194
1195 $stepBB->offset(0, $dy, 0);
1196
1197 foreach($list as $bb){
1198 $dx = $bb->calculateXOffset($stepBB, $dx);
1199 }
1200
1201 $stepBB->offset($dx, 0, 0);
1202
1203 foreach($list as $bb){
1204 $dz = $bb->calculateZOffset($stepBB, $dz);
1205 }
1206
1207 $stepBB->offset(0, 0, $dz);
1208
1209 $reverseDY = -$dy;
1210 foreach($list as $bb){
1211 $reverseDY = $bb->calculateYOffset($stepBB, $reverseDY);
1212 }
1213 $dy += $reverseDY;
1214 $stepBB->offset(0, $reverseDY, 0);
1215
1216 if(($cx ** 2 + $cz ** 2) >= ($dx ** 2 + $dz ** 2)){
1217 $dx = $cx;
1218 $dy = $cy;
1219 $dz = $cz;
1220 }else{
1221 $moveBB = $stepBB;
1222 $this->ySize += $dy;
1223 }
1224 }
1225
1226 $this->boundingBox = $moveBB;
1227 }
1228 Timings::$entityMoveCollision->stopTiming();
1229
1230 $this->location = new Location(
1231 ($this->boundingBox->minX + $this->boundingBox->maxX) / 2,
1232 $this->boundingBox->minY - $this->ySize,
1233 ($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2,
1234 $this->location->world,
1235 $this->location->yaw,
1236 $this->location->pitch
1237 );
1238
1239 $this->getWorld()->onEntityMoved($this);
1240 $this->checkBlockIntersections();
1241 $this->checkGroundState($wantedX, $wantedY, $wantedZ, $dx, $dy, $dz);
1242 $postFallVerticalVelocity = $this->updateFallState($dy, $this->onGround);
1243
1244 $this->motion = $this->motion->withComponents(
1245 $wantedX != $dx ? 0 : null,
1246 $postFallVerticalVelocity ?? ($wantedY != $dy ? 0 : null),
1247 $wantedZ != $dz ? 0 : null
1248 );
1249
1250 //TODO: vehicle collision events (first we need to spawn them!)
1251
1252 Timings::$entityMove->stopTiming();
1253 }
1254
1255 protected function checkGroundState(float $wantedX, float $wantedY, float $wantedZ, float $dx, float $dy, float $dz) : void{
1256 $this->isCollidedVertically = $wantedY != $dy;
1257 $this->isCollidedHorizontally = ($wantedX != $dx || $wantedZ != $dz);
1258 $this->isCollided = ($this->isCollidedHorizontally || $this->isCollidedVertically);
1259 $this->onGround = ($wantedY != $dy && $wantedY < 0);
1260 }
1261
1267 protected function getBlocksIntersected(float $inset) : \Generator{
1268 $minX = (int) floor($this->boundingBox->minX + $inset);
1269 $minY = (int) floor($this->boundingBox->minY + $inset);
1270 $minZ = (int) floor($this->boundingBox->minZ + $inset);
1271 $maxX = (int) floor($this->boundingBox->maxX - $inset);
1272 $maxY = (int) floor($this->boundingBox->maxY - $inset);
1273 $maxZ = (int) floor($this->boundingBox->maxZ - $inset);
1274
1275 $world = $this->getWorld();
1276
1277 for($z = $minZ; $z <= $maxZ; ++$z){
1278 for($x = $minX; $x <= $maxX; ++$x){
1279 for($y = $minY; $y <= $maxY; ++$y){
1280 yield $world->getBlockAt($x, $y, $z);
1281 }
1282 }
1283 }
1284 }
1285
1289 protected function getBlocksAroundWithEntityInsideActions() : array{
1290 if($this->blocksAround === null){
1291 $this->blocksAround = [];
1292
1293 $inset = 0.001; //Offset against floating-point errors
1294 foreach($this->getBlocksIntersected($inset) as $block){
1295 if($block->hasEntityCollision()){
1296 $this->blocksAround[] = $block;
1297 }
1298 }
1299 }
1300
1301 return $this->blocksAround;
1302 }
1303
1307 public function canBeMovedByCurrents() : bool{
1308 return true;
1309 }
1310
1311 protected function checkBlockIntersections() : void{
1312 $this->checkBlockIntersectionsNextTick = false;
1313 $vectors = [];
1314
1315 foreach($this->getBlocksAroundWithEntityInsideActions() as $block){
1316 if(!$block->onEntityInside($this)){
1317 $this->blocksAround = null;
1318 }
1319 if(($v = $block->addVelocityToEntity($this)) !== null){
1320 $vectors[] = $v;
1321 }
1322 }
1323
1324 if(count($vectors) > 0){
1325 $vector = Vector3::sum(...$vectors);
1326 if($vector->lengthSquared() > 0){
1327 $d = 0.014;
1328 $this->motion = $this->motion->addVector($vector->normalize()->multiply($d));
1329 }
1330 }
1331 }
1332
1333 public function getPosition() : Position{
1334 return $this->location->asPosition();
1335 }
1336
1337 public function getLocation() : Location{
1338 return $this->location->asLocation();
1339 }
1340
1341 public function getWorld() : World{
1342 return $this->location->getWorld();
1343 }
1344
1345 protected function setPosition(Vector3 $pos) : bool{
1346 if($this->closed){
1347 return false;
1348 }
1349
1350 $oldWorld = $this->getWorld();
1351 $newWorld = $pos instanceof Position ? $pos->getWorld() : $oldWorld;
1352 if($oldWorld !== $newWorld){
1353 $this->despawnFromAll();
1354 $oldWorld->removeEntity($this);
1355 }
1356
1357 $this->location = Location::fromObject(
1358 $pos,
1359 $newWorld,
1360 $this->location->yaw,
1361 $this->location->pitch
1362 );
1363
1364 $this->recalculateBoundingBox();
1365
1366 $this->blocksAround = null;
1367
1368 if($oldWorld !== $newWorld){
1369 $newWorld->addEntity($this);
1370 }else{
1371 $newWorld->onEntityMoved($this);
1372 }
1373
1374 return true;
1375 }
1376
1377 public function setRotation(float $yaw, float $pitch) : void{
1378 Utils::checkFloatNotInfOrNaN("yaw", $yaw);
1379 Utils::checkFloatNotInfOrNaN("pitch", $pitch);
1380 $this->location->yaw = $yaw;
1381 $this->location->pitch = $pitch;
1382 $this->scheduleUpdate();
1383 }
1384
1385 protected function setPositionAndRotation(Vector3 $pos, float $yaw, float $pitch) : bool{
1386 if($this->setPosition($pos)){
1387 $this->setRotation($yaw, $pitch);
1388
1389 return true;
1390 }
1391
1392 return false;
1393 }
1394
1395 protected function resetLastMovements() : void{
1396 $this->lastLocation = $this->location->asLocation();
1397 $this->lastMotion = clone $this->motion;
1398 }
1399
1400 public function getMotion() : Vector3{
1401 return clone $this->motion;
1402 }
1403
1404 public function setMotion(Vector3 $motion) : bool{
1405 Utils::checkVector3NotInfOrNaN($motion);
1406 if(!$this->justCreated){
1407 $ev = new EntityMotionEvent($this, $motion);
1408 $ev->call();
1409 if($ev->isCancelled()){
1410 return false;
1411 }
1412 }
1413
1414 $this->motion = clone $motion;
1415
1416 if(!$this->justCreated){
1417 $this->updateMovement();
1418 }
1419
1420 return true;
1421 }
1422
1426 public function addMotion(float $x, float $y, float $z) : void{
1427 Utils::checkFloatNotInfOrNaN("x", $x);
1428 Utils::checkFloatNotInfOrNaN("y", $y);
1429 Utils::checkFloatNotInfOrNaN("z", $z);
1430 $this->motion = $this->motion->add($x, $y, $z);
1431 }
1432
1433 public function isOnGround() : bool{
1434 return $this->onGround;
1435 }
1436
1440 public function teleport(Vector3 $pos, ?float $yaw = null, ?float $pitch = null) : bool{
1441 Utils::checkVector3NotInfOrNaN($pos);
1442 if($pos instanceof Location){
1443 $yaw = $yaw ?? $pos->yaw;
1444 $pitch = $pitch ?? $pos->pitch;
1445 }
1446 if($yaw !== null){
1447 Utils::checkFloatNotInfOrNaN("yaw", $yaw);
1448 }
1449 if($pitch !== null){
1450 Utils::checkFloatNotInfOrNaN("pitch", $pitch);
1451 }
1452
1453 $from = $this->location->asPosition();
1454 $to = Position::fromObject($pos, $pos instanceof Position ? $pos->getWorld() : $this->getWorld());
1455 $ev = new EntityTeleportEvent($this, $from, $to);
1456 $ev->call();
1457 if($ev->isCancelled()){
1458 return false;
1459 }
1460 $this->ySize = 0;
1461 $pos = $ev->getTo();
1462
1463 $this->setMotion(new Vector3(0, 0, 0));
1464 if($this->setPositionAndRotation($pos, $yaw ?? $this->location->yaw, $pitch ?? $this->location->pitch)){
1465 $this->resetFallDistance();
1466 $this->setForceMovementUpdate();
1467
1468 $this->updateMovement(true);
1469
1470 return true;
1471 }
1472
1473 return false;
1474 }
1475
1476 public function getId() : int{
1477 return $this->id;
1478 }
1479
1483 public function getViewers() : array{
1484 return $this->hasSpawned;
1485 }
1486
1487 abstract public static function getNetworkTypeId() : string;
1488
1492 protected function sendSpawnPacket(Player $player) : void{
1493 $player->getNetworkSession()->sendDataPacket(AddActorPacket::create(
1494 $this->getId(), //TODO: actor unique ID
1495 $this->getId(),
1496 static::getNetworkTypeId(),
1497 $this->location->asVector3(),
1498 $this->getMotion(),
1499 $this->location->pitch,
1500 $this->location->yaw,
1501 $this->location->yaw, //TODO: head yaw
1502 $this->location->yaw, //TODO: body yaw (wtf mojang?)
1503 array_map(function(Attribute $attr) : NetworkAttribute{
1504 return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue(), []);
1505 }, $this->attributeMap->getAll()),
1506 $this->getAllNetworkData(),
1507 new PropertySyncData([], []),
1508 [] //TODO: entity links
1509 ));
1510 }
1511
1512 public function spawnTo(Player $player) : void{
1513 $id = spl_object_id($player);
1514 //TODO: this will cause some visible lag during chunk resends; if the player uses a spawn egg in a chunk, the
1515 //created entity won't be visible until after the resend arrives. However, this is better than possibly crashing
1516 //the player by sending them entities too early.
1517 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)){
1518 $this->hasSpawned[$id] = $player;
1519
1520 $this->sendSpawnPacket($player);
1521 }
1522 }
1523
1524 public function spawnToAll() : void{
1525 if($this->closed){
1526 return;
1527 }
1528 foreach($this->getWorld()->getViewersForPosition($this->location) as $player){
1529 $this->spawnTo($player);
1530 }
1531 }
1532
1533 public function respawnToAll() : void{
1534 foreach($this->hasSpawned as $key => $player){
1535 unset($this->hasSpawned[$key]);
1536 $this->spawnTo($player);
1537 }
1538 }
1539
1544 public function despawnFrom(Player $player, bool $send = true) : void{
1545 $id = spl_object_id($player);
1546 if(isset($this->hasSpawned[$id])){
1547 if($send){
1548 $player->getNetworkSession()->getEntityEventBroadcaster()->onEntityRemoved([$player->getNetworkSession()], $this);
1549 }
1550 unset($this->hasSpawned[$id]);
1551 }
1552 }
1553
1558 public function despawnFromAll() : void{
1559 NetworkBroadcastUtils::broadcastEntityEvent(
1560 $this->hasSpawned,
1561 fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onEntityRemoved($recipients, $this)
1562 );
1563 $this->hasSpawned = [];
1564 }
1565
1569 public function getPickedItem() : ?Item{
1570 return null;
1571 }
1572
1576 public function flagForDespawn() : void{
1577 $this->needsDespawn = true;
1578 $this->scheduleUpdate();
1579 }
1580
1581 public function isFlaggedForDespawn() : bool{
1582 return $this->needsDespawn;
1583 }
1584
1588 public function isClosed() : bool{
1589 return $this->closed;
1590 }
1591
1597 final public function close() : void{
1598 if($this->closeInFlight){
1599 return;
1600 }
1601
1602 if(!$this->closed){
1603 $this->closeInFlight = true;
1604 (new EntityDespawnEvent($this))->call();
1605
1606 $this->onDispose();
1607 $this->closed = true;
1608 $this->destroyCycles();
1609 $this->closeInFlight = false;
1610 }
1611 }
1612
1617 protected function onDispose() : void{
1618 $this->despawnFromAll();
1619 if($this->location->isValid()){
1620 $this->getWorld()->removeEntity($this);
1621 }
1622 }
1623
1630 protected function destroyCycles() : void{
1631 $this->lastDamageCause = null;
1632 }
1633
1640 public function sendData(?array $targets, ?array $data = null) : void{
1641 $targets = $targets ?? $this->hasSpawned;
1642 $data = $data ?? $this->getAllNetworkData();
1643
1644 NetworkBroadcastUtils::broadcastEntityEvent($targets, fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->syncActorData($recipients, $this, $data));
1645 }
1646
1651 final protected function getDirtyNetworkData() : array{
1652 if($this->networkPropertiesDirty){
1653 $this->syncNetworkData($this->networkProperties);
1654 $this->networkPropertiesDirty = false;
1655 }
1656 return $this->networkProperties->getDirty();
1657 }
1658
1663 final protected function getAllNetworkData() : array{
1664 if($this->networkPropertiesDirty){
1665 $this->syncNetworkData($this->networkProperties);
1666 $this->networkPropertiesDirty = false;
1667 }
1668 return $this->networkProperties->getAll();
1669 }
1670
1671 protected function syncNetworkData(EntityMetadataCollection $properties) : void{
1672 $properties->setByte(EntityMetadataProperties::ALWAYS_SHOW_NAMETAG, $this->alwaysShowNameTag ? 1 : 0);
1673 $properties->setFloat(EntityMetadataProperties::BOUNDING_BOX_HEIGHT, $this->size->getHeight() / $this->scale);
1674 $properties->setFloat(EntityMetadataProperties::BOUNDING_BOX_WIDTH, $this->size->getWidth() / $this->scale);
1675 $properties->setFloat(EntityMetadataProperties::SCALE, $this->scale);
1676 $properties->setLong(EntityMetadataProperties::LEAD_HOLDER_EID, -1);
1677 $properties->setLong(EntityMetadataProperties::OWNER_EID, $this->ownerId ?? -1);
1678 $properties->setLong(EntityMetadataProperties::TARGET_EID, $this->targetId ?? 0);
1679 $properties->setString(EntityMetadataProperties::NAMETAG, $this->nameTag);
1680 $properties->setString(EntityMetadataProperties::SCORE_TAG, $this->scoreTag);
1681 $properties->setByte(EntityMetadataProperties::COLOR, 0);
1682
1683 $properties->setGenericFlag(EntityMetadataFlags::AFFECTED_BY_GRAVITY, $this->gravityEnabled);
1684 $properties->setGenericFlag(EntityMetadataFlags::CAN_CLIMB, $this->canClimb);
1685 $properties->setGenericFlag(EntityMetadataFlags::CAN_SHOW_NAMETAG, $this->nameTagVisible);
1686 $properties->setGenericFlag(EntityMetadataFlags::HAS_COLLISION, true);
1687 $properties->setGenericFlag(EntityMetadataFlags::NO_AI, $this->noClientPredictions);
1688 $properties->setGenericFlag(EntityMetadataFlags::INVISIBLE, $this->invisible);
1689 $properties->setGenericFlag(EntityMetadataFlags::SILENT, $this->silent);
1690 $properties->setGenericFlag(EntityMetadataFlags::ONFIRE, $this->isOnFire());
1691 $properties->setGenericFlag(EntityMetadataFlags::WALLCLIMBING, $this->canClimbWalls);
1692 }
1693
1697 public function broadcastAnimation(Animation $animation, ?array $targets = null) : void{
1698 NetworkBroadcastUtils::broadcastPackets($targets ?? $this->getViewers(), $animation->encode());
1699 }
1700
1705 public function broadcastSound(Sound $sound, ?array $targets = null) : void{
1706 if(!$this->silent){
1707 $this->getWorld()->addSound($this->location->asVector3(), $sound, $targets ?? $this->getViewers());
1708 }
1709 }
1710
1711 public function __destruct(){
1712 $this->close();
1713 }
1714
1715 public function __toString(){
1716 return (new \ReflectionClass($this))->getShortName() . "(" . $this->getId() . ")";
1717 }
1718}
static parseVec3(CompoundTag $nbt, string $tagName, bool $optional)
setNoClientPredictions(bool $value=true)
Definition Entity.php:345
getBlocksIntersected(float $inset)
Definition Entity.php:1267
sendData(?array $targets, ?array $data=null)
Definition Entity.php:1640
setCanClimbWalls(bool $value=true)
Definition Entity.php:393
despawnFrom(Player $player, bool $send=true)
Definition Entity.php:1544
setCanClimb(bool $value=true)
Definition Entity.php:378
teleport(Vector3 $pos, ?float $yaw=null, ?float $pitch=null)
Definition Entity.php:1440
broadcastSound(Sound $sound, ?array $targets=null)
Definition Entity.php:1705
setCanSaveWithChunk(bool $value)
Definition Entity.php:470
broadcastAnimation(Animation $animation, ?array $targets=null)
Definition Entity.php:1697
sendSpawnPacket(Player $player)
Definition Entity.php:1492
setForceMovementUpdate(bool $value=true)
Definition Entity.php:1049
setOwningEntity(?Entity $owner)
Definition Entity.php:417
setFireTicks(int $fireTicks)
Definition Entity.php:701
addMotion(float $x, float $y, float $z)
Definition Entity.php:1426
onFirstUpdate(int $currentTick)
Definition Entity.php:964
setTargetEntity(?Entity $target)
Definition Entity.php:448
setHealth(float $amount)
Definition Entity.php:593
setFloat(string $name, float $value)
hasReceivedChunk(int $chunkX, int $chunkZ)
Definition Player.php:1026
syncActorData(array $recipients, Entity $entity, array $properties)