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