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