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