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