= $split){ return $split; } } return 1; } /** * Splits the specified amount of XP into an array of acceptable XP orb sizes. * * @return int[] */ public static function splitIntoOrbSizes(int $amount) : array{ $result = []; while($amount > 0){ $size = self::getMaxOrbSize($amount); $result[] = $size; $amount -= $size; } return $result; } public $height = 0.25; public $width = 0.25; public $gravity = 0.04; public $drag = 0.02; /** @var int */ protected $age = 0; /** * @var int * Ticker used for determining interval in which to look for new target players. */ protected $lookForTargetTime = 0; /** * @var int|null * Runtime entity ID of the player this XP orb is targeting. */ protected $targetPlayerRuntimeId = null; /** @var int */ protected $xpValue = 1; protected function initEntity(CompoundTag $nbt) : void{ parent::initEntity($nbt); $this->age = $nbt->getShort("Age", 0); $value = 1; if(($valuePcTag = $nbt->getTag(self::TAG_VALUE_PC)) instanceof ShortTag){ //PC $value = $valuePcTag->getValue(); }elseif(($valuePeTag = $nbt->getTag(self::TAG_VALUE_PE)) instanceof IntTag){ //PE save format $value = $valuePeTag->getValue(); } $this->setXpValue($value); } public function saveNBT() : CompoundTag{ $nbt = parent::saveNBT(); $nbt->setShort("Age", $this->age); $nbt->setShort(self::TAG_VALUE_PC, $this->getXpValue()); $nbt->setInt(self::TAG_VALUE_PE, $this->getXpValue()); return $nbt; } public function getXpValue() : int{ return $this->xpValue; } public function setXpValue(int $amount) : void{ if($amount <= 0){ throw new \InvalidArgumentException("XP amount must be greater than 0, got $amount"); } $this->xpValue = $amount; } public function hasTargetPlayer() : bool{ return $this->targetPlayerRuntimeId !== null; } public function getTargetPlayer() : ?Human{ if($this->targetPlayerRuntimeId === null){ return null; } $entity = $this->getWorld()->getEntity($this->targetPlayerRuntimeId); if($entity instanceof Human){ return $entity; } return null; } public function setTargetPlayer(?Human $player) : void{ $this->targetPlayerRuntimeId = $player !== null ? $player->getId() : null; } protected function entityBaseTick(int $tickDiff = 1) : bool{ $hasUpdate = parent::entityBaseTick($tickDiff); $this->age += $tickDiff; if($this->age > 6000){ $this->flagForDespawn(); return true; } $currentTarget = $this->getTargetPlayer(); if($currentTarget !== null and (!$currentTarget->isAlive() or $currentTarget->location->distanceSquared($this->location) > self::MAX_TARGET_DISTANCE ** 2)){ $currentTarget = null; } if($this->lookForTargetTime >= 20){ if($currentTarget === null){ $newTarget = $this->getWorld()->getNearestEntity($this->location, self::MAX_TARGET_DISTANCE, Human::class); if($newTarget instanceof Human and !($newTarget instanceof Player and $newTarget->isSpectator())){ $currentTarget = $newTarget; } } $this->lookForTargetTime = 0; }else{ $this->lookForTargetTime += $tickDiff; } $this->setTargetPlayer($currentTarget); if($currentTarget !== null){ $vector = $currentTarget->getPosition()->add(0, $currentTarget->getEyeHeight() / 2, 0)->subtractVector($this->location)->divide(self::MAX_TARGET_DISTANCE); $distance = $vector->lengthSquared(); if($distance < 1){ $this->motion = $this->motion->addVector($vector->normalize()->multiply(0.2 * (1 - sqrt($distance)) ** 2)); } if($currentTarget->getXpManager()->canPickupXp() and $this->boundingBox->intersectsWith($currentTarget->getBoundingBox())){ $this->flagForDespawn(); $currentTarget->getXpManager()->onPickupXp($this->getXpValue()); } } return $hasUpdate; } protected function tryChangeMovement() : void{ $this->checkObstruction($this->location->x, $this->location->y, $this->location->z); parent::tryChangeMovement(); } public function canBeCollidedWith() : bool{ return false; } protected function syncNetworkData(EntityMetadataCollection $properties) : void{ parent::syncNetworkData($properties); $properties->setInt(EntityMetadataProperties::EXPERIENCE_VALUE, $this->xpValue); } }