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