40 public function getNetworkTypeId() :
string{
return EntityIds::XP_ORB; }
42 public const TAG_VALUE_PC =
"Value";
43 public const TAG_VALUE_PE =
"experience value";
44 private const TAG_AGE =
"Age";
50 public const ORB_SPLIT_SIZES = [2477, 1237, 617, 307, 149, 73, 37, 17, 7, 3, 1];
52 public const DEFAULT_DESPAWN_DELAY = 6000;
53 public const NEVER_DESPAWN = -1;
54 public const MAX_DESPAWN_DELAY = 32767 + self::DEFAULT_DESPAWN_DELAY;
62 if($amount >= $split){
79 $size = self::getMaxOrbSize($amount);
88 protected int $lookForTargetTime = 0;
91 protected ?
int $targetPlayerRuntimeId =
null;
93 protected int $xpValue;
95 private int $despawnDelay = self::DEFAULT_DESPAWN_DELAY;
98 $this->xpValue = $xpValue;
99 parent::__construct($location, $nbt);
108 protected function initEntity(
CompoundTag $nbt) : void{
109 parent::initEntity($nbt);
111 $age = $nbt->getShort(self::TAG_AGE, 0);
113 $this->despawnDelay = self::NEVER_DESPAWN;
115 $this->despawnDelay = max(0, self::DEFAULT_DESPAWN_DELAY - $age);
119 public function saveNBT() : CompoundTag{
120 $nbt = parent::saveNBT();
122 if($this->despawnDelay === self::NEVER_DESPAWN){
125 $age = self::DEFAULT_DESPAWN_DELAY - $this->despawnDelay;
127 $nbt->
setShort(self::TAG_AGE, $age);
129 $nbt->
setShort(self::TAG_VALUE_PC, $this->getXpValue());
130 $nbt->
setInt(self::TAG_VALUE_PE, $this->getXpValue());
135 public function getDespawnDelay() : int{ return $this->despawnDelay; }
137 public function setDespawnDelay(
int $despawnDelay) : void{
138 if(($despawnDelay < 0 || $despawnDelay > self::MAX_DESPAWN_DELAY) && $despawnDelay !== self::NEVER_DESPAWN){
139 throw new \InvalidArgumentException(
"Despawn ticker must be in range 0 ... " . self::MAX_DESPAWN_DELAY .
" or " . self::NEVER_DESPAWN .
", got $despawnDelay");
141 $this->despawnDelay = $despawnDelay;
144 public function getXpValue() : int{
145 return $this->xpValue;
148 public function setXpValue(
int $amount) : void{
150 throw new \InvalidArgumentException(
"XP amount must be greater than 0, got $amount");
152 $this->xpValue = $amount;
153 $this->networkPropertiesDirty =
true;
156 public function hasTargetPlayer() : bool{
157 return $this->targetPlayerRuntimeId !== null;
160 public function getTargetPlayer() : ?Human{
161 if($this->targetPlayerRuntimeId === null){
165 $entity = $this->getWorld()->getEntity($this->targetPlayerRuntimeId);
166 if($entity instanceof Human){
173 public function setTargetPlayer(?Human $player) : void{
174 $this->targetPlayerRuntimeId = $player !== null ? $player->getId() : null;
177 protected function entityBaseTick(
int $tickDiff = 1) : bool{
178 $hasUpdate = parent::entityBaseTick($tickDiff);
180 $this->despawnDelay -= $tickDiff;
181 if($this->despawnDelay <= 0){
182 $this->flagForDespawn();
186 $currentTarget = $this->getTargetPlayer();
187 if($currentTarget !==
null && (!$currentTarget->isAlive() || !$currentTarget->getXpManager()->canAttractXpOrbs() || $currentTarget->location->distanceSquared($this->location) > self::MAX_TARGET_DISTANCE ** 2)){
188 $currentTarget =
null;
191 if($this->lookForTargetTime >= 20){
192 if($currentTarget ===
null){
193 $newTarget = $this->getWorld()->getNearestEntity($this->location, self::MAX_TARGET_DISTANCE, Human::class);
195 if($newTarget instanceof Human && !($newTarget instanceof Player && $newTarget->isSpectator()) && $newTarget->getXpManager()->canAttractXpOrbs()){
196 $currentTarget = $newTarget;
200 $this->lookForTargetTime = 0;
202 $this->lookForTargetTime += $tickDiff;
205 $this->setTargetPlayer($currentTarget);
207 if($currentTarget !==
null){
208 $vector = $currentTarget->getPosition()->add(0, $currentTarget->getEyeHeight() / 2, 0)->subtractVector($this->location)->divide(self::MAX_TARGET_DISTANCE);
210 $distance = $vector->lengthSquared();
212 $this->motion = $this->motion->addVector($vector->normalize()->multiply(0.2 * (1 - sqrt($distance)) ** 2));
215 if($currentTarget->getXpManager()->canPickupXp() && $this->boundingBox->intersectsWith($currentTarget->getBoundingBox())){
216 $this->flagForDespawn();
218 $currentTarget->getXpManager()->onPickupXp($this->getXpValue());
225 protected function tryChangeMovement() : void{
226 $this->checkObstruction($this->location->x, $this->location->y, $this->location->z);
227 parent::tryChangeMovement();
230 public function canBeCollidedWith() : bool{
234 protected function syncNetworkData(EntityMetadataCollection $properties) : void{
235 parent::syncNetworkData($properties);
237 $properties->setInt(EntityMetadataProperties::EXPERIENCE_VALUE, $this->xpValue);