48    private const TAG_HEALTH = 
"Health"; 
 
   49    private const TAG_AGE = 
"Age"; 
 
   50    private const TAG_PICKUP_DELAY = 
"PickupDelay"; 
 
   51    private const TAG_OWNER = 
"Owner"; 
 
   52    private const TAG_THROWER = 
"Thrower"; 
 
   53    public const TAG_ITEM = 
"Item"; 
 
   55    public static function getNetworkTypeId() : 
string{ 
return EntityIds::ITEM; }
 
   57    public const MERGE_CHECK_PERIOD = 2; 
 
   58    public const DEFAULT_DESPAWN_DELAY = 6000; 
 
   59    public const NEVER_DESPAWN = -1;
 
   60    public const MAX_DESPAWN_DELAY = 32767 + self::DEFAULT_DESPAWN_DELAY; 
 
   62    protected string $owner = 
"";
 
   63    protected string $thrower = 
"";
 
   64    protected int $pickupDelay = 0;
 
   65    protected int $despawnDelay = self::DEFAULT_DESPAWN_DELAY;
 
   70            throw new \InvalidArgumentException(
"Item entity must have a non-air item with a count of at least 1");
 
   72        $this->item = clone $item;
 
   73        parent::__construct($location, $nbt);
 
   82    protected function initEntity(
CompoundTag $nbt) : void{
 
   83        parent::initEntity($nbt);
 
   85        $this->setMaxHealth(5);
 
   86        $this->setHealth($nbt->getShort(self::TAG_HEALTH, (
int) $this->getHealth()));
 
   88        $age = $nbt->getShort(self::TAG_AGE, 0);
 
   90            $this->despawnDelay = self::NEVER_DESPAWN;
 
   92            $this->despawnDelay = max(0, self::DEFAULT_DESPAWN_DELAY - $age);
 
   94        $this->pickupDelay = $nbt->getShort(self::TAG_PICKUP_DELAY, $this->pickupDelay);
 
   95        $this->owner = $nbt->getString(self::TAG_OWNER, $this->owner);
 
   96        $this->thrower = $nbt->getString(self::TAG_THROWER, $this->thrower);
 
  101        parent::onFirstUpdate($currentTick);
 
 
  104    protected function entityBaseTick(
int $tickDiff = 1) : bool{
 
  109        Timings::$itemEntityBaseTick->startTiming();
 
  112            $hasUpdate = parent::entityBaseTick($tickDiff);
 
  114            if($this->isFlaggedForDespawn()){
 
  118            if($this->pickupDelay !== self::NEVER_DESPAWN && $this->pickupDelay > 0){ 
 
  120                $this->pickupDelay -= $tickDiff;
 
  121                if($this->pickupDelay < 0){
 
  122                    $this->pickupDelay = 0;
 
  126            if($this->hasMovementUpdate() && $this->isMergeCandidate() && $this->despawnDelay % self::MERGE_CHECK_PERIOD === 0){
 
  127                $mergeable = [$this]; 
 
  128                $mergeTarget = $this;
 
  129                foreach($this->getWorld()->getNearbyEntities($this->boundingBox->expandedCopy(0.5, 0.5, 0.5), $this) as $entity){
 
  130                    if(!$entity instanceof ItemEntity || $entity->isFlaggedForDespawn()){
 
  134                    if($entity->isMergeable($this)){
 
  135                        $mergeable[] = $entity;
 
  136                        if($entity->item->getCount() > $mergeTarget->item->getCount()){
 
  137                            $mergeTarget = $entity;
 
  141                foreach($mergeable as $itemEntity){
 
  142                    if($itemEntity !== $mergeTarget){
 
  143                        $itemEntity->tryMergeInto($mergeTarget);
 
  148            if(!$this->isFlaggedForDespawn() && $this->despawnDelay !== self::NEVER_DESPAWN){
 
  150                $this->despawnDelay -= $tickDiff;
 
  151                if($this->despawnDelay <= 0){
 
  152                    $ev = 
new ItemDespawnEvent($this);
 
  154                    if($ev->isCancelled()){
 
  155                        $this->despawnDelay = self::DEFAULT_DESPAWN_DELAY;
 
  157                        $this->flagForDespawn();
 
  164            Timings::$itemEntityBaseTick->stopTiming();
 
  168    private function isMergeCandidate() : bool{
 
  169        return $this->pickupDelay !== self::NEVER_DESPAWN && $this->item->getCount() < $this->item->getMaxStackSize();
 
  176        if(!$this->isMergeCandidate() || !$entity->isMergeCandidate()){
 
  179        $item = $entity->item;
 
  180        return $entity !== $this && $item->
canStackWith($this->item) && $item->getCount() + $this->item->getCount() <= $item->
getMaxStackSize();
 
 
  187        if(!$this->isMergeable($consumer)){
 
  194        if($ev->isCancelled()){
 
  198        $consumer->setStackSize($consumer->item->getCount() + $this->item->getCount());
 
  199        $this->flagForDespawn();
 
  200        $consumer->pickupDelay = max($consumer->pickupDelay, $this->pickupDelay);
 
  201        $consumer->despawnDelay = max($consumer->despawnDelay, $this->despawnDelay);
 
 
  206    protected function tryChangeMovement() : void{
 
  207        $this->checkObstruction($this->location->x, $this->location->y, $this->location->z);
 
  208        parent::tryChangeMovement();
 
  211    protected function applyDragBeforeGravity() : bool{
 
  216        return !$this->item->isNull() && parent::canSaveWithChunk();
 
 
  220        $nbt = parent::saveNBT();
 
  221        $nbt->
setTag(self::TAG_ITEM, $this->item->nbtSerialize());
 
  222        $nbt->
setShort(self::TAG_HEALTH, (
int) $this->getHealth());
 
  223        if($this->despawnDelay === self::NEVER_DESPAWN){
 
  226            $age = self::DEFAULT_DESPAWN_DELAY - $this->despawnDelay;
 
  228        $nbt->
setShort(self::TAG_AGE, $age);
 
  229        $nbt->
setShort(self::TAG_PICKUP_DELAY, $this->pickupDelay);
 
  230        $nbt->
setString(self::TAG_OWNER, $this->owner);
 
  231        $nbt->
setString(self::TAG_THROWER, $this->thrower);
 
  236    public function getItem() : 
Item{
 
  240    public function isFireProof() : bool{
 
  241        return $this->item->isFireProof();
 
  244    public function canCollideWith(Entity $entity) : bool{
 
  248    public function canBeCollidedWith() : bool{
 
  252    public function getPickupDelay() : int{
 
  253        return $this->pickupDelay;
 
  256    public function setPickupDelay(
int $delay) : void{
 
  257        $this->pickupDelay = $delay;
 
  264        return $this->despawnDelay;
 
 
  271        if(($despawnDelay < 0 || $despawnDelay > self::MAX_DESPAWN_DELAY) && $despawnDelay !== self::NEVER_DESPAWN){
 
  272            throw new \InvalidArgumentException(
"Despawn ticker must be in range 0 ... " . self::MAX_DESPAWN_DELAY . 
" or " . self::NEVER_DESPAWN . 
", got $despawnDelay");
 
  274        $this->despawnDelay = $despawnDelay;
 
 
  277    public function getOwner() : string{
 
  281    public function setOwner(
string $owner) : void{
 
  282        $this->owner = $owner;
 
  285    public function getThrower() : string{
 
  286        return $this->thrower;
 
  289    public function setThrower(
string $thrower) : void{
 
  290        $this->thrower = $thrower;
 
  294        $networkSession = $player->getNetworkSession();
 
  295        $networkSession->sendDataPacket(AddItemActorPacket::create(
 
  298            ItemStackWrapper::legacy($networkSession->getTypeConverter()->coreItemStackToNet($this->getItem())),
 
  299            $this->location->asVector3(),
 
  301            $this->getAllNetworkData(),
 
 
  306    public function setStackSize(
int $newCount) : void{
 
  308            throw new \InvalidArgumentException(
"Stack size must be at least 1");
 
  310        $this->item->setCount($newCount);
 
  311        $this->broadcastAnimation(
new ItemEntityStackSizeChangeAnimation($this, $newCount));
 
  314    public function getOffsetPosition(Vector3 $vector3) : Vector3{
 
  315        return $vector3->add(0, 0.125, 0);
 
  318    public function onCollideWithPlayer(Player $player) : void{
 
  319        if($this->getPickupDelay() !== 0){
 
  323        $item = $this->getItem();
 
  324        $playerInventory = match(
true){
 
  325            $player->getOffHandInventory()->getItem(0)->canStackWith($item) && $player->getOffHandInventory()->getAddableItemQuantity($item) > 0 => $player->getOffHandInventory(),
 
  326            $player->getInventory()->getAddableItemQuantity($item) > 0 => $player->getInventory(),
 
  330        $ev = 
new EntityItemPickupEvent($player, $this, $item, $playerInventory);
 
  336        if($ev->isCancelled()){
 
  340        NetworkBroadcastUtils::broadcastEntityEvent(
 
  342            fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onPickUpItem($recipients, $player, $this)
 
  345        $inventory = $ev->getInventory();
 
  346        if($inventory !== 
null){
 
  347            foreach($inventory->addItem($ev->getItem()) as $remains){
 
  348                $this->getWorld()->dropItem($this->location, $remains, 
new Vector3(0, 0, 0));
 
  351        $this->flagForDespawn();