From f98cebbd6258f65c25fb6362cb66b9c00afa45ba Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Nov 2024 17:58:58 +0000 Subject: [PATCH 01/28] Separate hotbar from player inventory this allows this functionality to be used with any type of inventory, and also makes it a little nicer to use in many cases. --- src/command/defaults/EnchantCommand.php | 4 +- src/entity/ExperienceManager.php | 4 +- src/entity/Human.php | 32 +++-- src/inventory/Hotbar.php | 122 ++++++++++++++++++ src/inventory/PlayerInventory.php | 86 ------------ src/item/Armor.php | 2 +- src/network/mcpe/InventoryManager.php | 6 +- .../mcpe/StandardEntityEventBroadcaster.php | 8 +- .../mcpe/handler/InGamePacketHandler.php | 4 +- src/player/Player.php | 56 ++++---- src/player/SurvivalBlockBreakHandler.php | 2 +- 11 files changed, 185 insertions(+), 141 deletions(-) create mode 100644 src/inventory/Hotbar.php diff --git a/src/command/defaults/EnchantCommand.php b/src/command/defaults/EnchantCommand.php index 191a146b036..6b73aad6c41 100644 --- a/src/command/defaults/EnchantCommand.php +++ b/src/command/defaults/EnchantCommand.php @@ -56,7 +56,7 @@ public function execute(CommandSender $sender, string $commandLabel, array $args return true; } - $item = $player->getInventory()->getItemInHand(); + $item = $player->getHotbar()->getHeldItem(); if($item->isNull()){ $sender->sendMessage(KnownTranslationFactory::commands_enchant_noItem()); @@ -79,7 +79,7 @@ public function execute(CommandSender $sender, string $commandLabel, array $args //this is necessary to deal with enchanted books, which are a different item type than regular books $enchantedItem = EnchantingHelper::enchantItem($item, [new EnchantmentInstance($enchantment, $level)]); - $player->getInventory()->setItemInHand($enchantedItem); + $player->getHotbar()->setHeldItem($enchantedItem); self::broadcastCommandMessage($sender, KnownTranslationFactory::commands_enchant_success($player->getName())); return true; diff --git a/src/entity/ExperienceManager.php b/src/entity/ExperienceManager.php index 9cff48f330c..5e04728f8c5 100644 --- a/src/entity/ExperienceManager.php +++ b/src/entity/ExperienceManager.php @@ -243,7 +243,7 @@ public function onPickupXp(int $xpValue) : void{ //TODO: replace this with a more generic equipment getting/setting interface $equipment = []; - if(($item = $this->entity->getInventory()->getItemInHand()) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){ + if(($item = $this->entity->getHotbar()->getHeldItem()) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){ $equipment[$mainHandIndex] = $item; } if(($item = $this->entity->getOffHandInventory()->getItem(0)) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){ @@ -263,7 +263,7 @@ public function onPickupXp(int $xpValue) : void{ $xpValue -= (int) ceil($repairAmount / 2); if($k === $mainHandIndex){ - $this->entity->getInventory()->setItemInHand($repairItem); + $this->entity->getHotbar()->setHeldItem($repairItem); }elseif($k === $offHandIndex){ $this->entity->getOffHandInventory()->setItem(0, $repairItem); }else{ diff --git a/src/entity/Human.php b/src/entity/Human.php index 006bf6df864..8b446660a64 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -32,6 +32,7 @@ use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\player\PlayerExhaustEvent; use pocketmine\inventory\CallbackInventoryListener; +use pocketmine\inventory\Hotbar; use pocketmine\inventory\Inventory; use pocketmine\inventory\InventoryHolder; use pocketmine\inventory\PlayerEnderInventory; @@ -100,6 +101,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ public function getNetworkTypeId() : string{ return EntityIds::PLAYER; } + protected Hotbar $hotbar; protected PlayerInventory $inventory; protected PlayerOffHandInventory $offHandInventory; protected PlayerEnderInventory $enderInventory; @@ -228,6 +230,10 @@ public function getXpDropAmount() : int{ return min(100, 7 * $this->xpManager->getXpLevel()); } + public function getHotbar() : Hotbar{ + return $this->hotbar; + } + public function getInventory() : PlayerInventory{ return $this->inventory; } @@ -266,18 +272,20 @@ protected function initEntity(CompoundTag $nbt) : void{ $this->xpManager = new ExperienceManager($this); $this->inventory = new PlayerInventory($this); + $this->hotbar = new Hotbar($this->inventory); + $syncHeldItem = fn() => NetworkBroadcastUtils::broadcastEntityEvent( $this->getViewers(), fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobMainHandItemChange($recipients, $this) ); $this->inventory->getListeners()->add(new CallbackInventoryListener( function(Inventory $unused, int $slot, Item $unused2) use ($syncHeldItem) : void{ - if($slot === $this->inventory->getHeldItemIndex()){ + if($slot === $this->hotbar->getSelectedIndex()){ $syncHeldItem(); } }, function(Inventory $unused, array $oldItems) use ($syncHeldItem) : void{ - if(array_key_exists($this->inventory->getHeldItemIndex(), $oldItems)){ + if(array_key_exists($this->hotbar->getSelectedIndex(), $oldItems)){ $syncHeldItem(); } } @@ -326,8 +334,8 @@ function(Inventory $unused, array $oldItems) use ($syncHeldItem) : void{ self::populateInventoryFromListTag($this->enderInventory, $enderChestInventoryItems); } - $this->inventory->setHeldItemIndex($nbt->getInt(self::TAG_SELECTED_INVENTORY_SLOT, 0)); - $this->inventory->getHeldItemIndexChangeListeners()->add(fn() => NetworkBroadcastUtils::broadcastEntityEvent( + $this->hotbar->setSelectedIndex($nbt->getInt(self::TAG_SELECTED_INVENTORY_SLOT, 0)); + $this->hotbar->getSelectedIndexChangeListeners()->add(fn() => NetworkBroadcastUtils::broadcastEntityEvent( $this->getViewers(), fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobMainHandItemChange($recipients, $this) )); @@ -367,7 +375,7 @@ public function applyDamageModifiers(EntityDamageEvent $source) : void{ $type = $source->getCause(); if($type !== EntityDamageEvent::CAUSE_SUICIDE && $type !== EntityDamageEvent::CAUSE_VOID - && ($this->inventory->getItemInHand() instanceof Totem || $this->offHandInventory->getItem(0) instanceof Totem)){ + && ($this->hotbar->getHeldItem() instanceof Totem || $this->offHandInventory->getItem(0) instanceof Totem)){ $compensation = $this->getHealth() - $source->getFinalDamage() - 1; if($compensation <= -1){ @@ -389,10 +397,10 @@ protected function applyPostDamageEffects(EntityDamageEvent $source) : void{ $this->broadcastAnimation(new TotemUseAnimation($this)); $this->broadcastSound(new TotemUseSound()); - $hand = $this->inventory->getItemInHand(); + $hand = $this->hotbar->getHeldItem(); if($hand instanceof Totem){ $hand->pop(); //Plugins could alter max stack size - $this->inventory->setItemInHand($hand); + $this->hotbar->setHeldItem($hand); }elseif(($offHand = $this->offHandInventory->getItem(0)) instanceof Totem){ $offHand->pop(); $this->offHandInventory->setItem(0, $offHand); @@ -425,8 +433,8 @@ public function saveNBT() : CompoundTag{ $nbt->setTag(self::TAG_INVENTORY, $inventoryTag); //Normal inventory - $slotCount = $this->inventory->getSize() + $this->inventory->getHotbarSize(); - for($slot = $this->inventory->getHotbarSize(); $slot < $slotCount; ++$slot){ + $slotCount = $this->inventory->getSize() + $this->hotbar->getSize(); + for($slot = $this->hotbar->getSize(); $slot < $slotCount; ++$slot){ $item = $this->inventory->getItem($slot - 9); if(!$item->isNull()){ $inventoryTag->push($item->nbtSerialize($slot)); @@ -441,7 +449,7 @@ public function saveNBT() : CompoundTag{ } } - $nbt->setInt(self::TAG_SELECTED_INVENTORY_SLOT, $this->inventory->getHeldItemIndex()); + $nbt->setInt(self::TAG_SELECTED_INVENTORY_SLOT, $this->hotbar->getSelectedIndex()); $offHandItem = $this->offHandInventory->getItem(0); if(!$offHandItem->isNull()){ @@ -495,7 +503,7 @@ protected function sendSpawnPacket(Player $player) : void{ $this->location->pitch, $this->location->yaw, $this->location->yaw, //TODO: head yaw - ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($this->getInventory()->getItemInHand())), + ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($this->hotbar->getHeldItem())), GameMode::SURVIVAL, $this->getAllNetworkData(), new PropertySyncData([], []), @@ -529,8 +537,8 @@ public function getOffsetPosition(Vector3 $vector3) : Vector3{ } protected function onDispose() : void{ + $this->hotbar->getSelectedIndexChangeListeners()->clear(); $this->inventory->removeAllViewers(); - $this->inventory->getHeldItemIndexChangeListeners()->clear(); $this->offHandInventory->removeAllViewers(); $this->enderInventory->removeAllViewers(); parent::onDispose(); diff --git a/src/inventory/Hotbar.php b/src/inventory/Hotbar.php new file mode 100644 index 00000000000..a98a868d458 --- /dev/null +++ b/src/inventory/Hotbar.php @@ -0,0 +1,122 @@ + + */ + protected ObjectSet $selectedIndexChangeListeners; + + public function __construct( + private Inventory $inventory, + private int $size = 9 + ){ + if($this->inventory->getSize() < $this->size){ + throw new \InvalidArgumentException("Inventory size must be at least $this->size"); + } + $this->selectedIndexChangeListeners = new ObjectSet(); + } + + public function isHotbarSlot(int $slot) : bool{ + return $slot >= 0 && $slot < $this->getSize(); + } + + /** + * @throws \InvalidArgumentException + */ + private function throwIfNotHotbarSlot(int $slot) : void{ + if(!$this->isHotbarSlot($slot)){ + throw new \InvalidArgumentException("$slot is not a valid hotbar slot index (expected 0 - " . ($this->getSize() - 1) . ")"); + } + } + + /** + * Returns the item in the specified hotbar slot. + * + * @throws \InvalidArgumentException if the hotbar slot index is out of range + */ + public function getHotbarSlotItem(int $hotbarSlot) : Item{ + $this->throwIfNotHotbarSlot($hotbarSlot); + return $this->inventory->getItem($hotbarSlot); + } + + /** + * Returns the hotbar slot number the holder is currently holding. + */ + public function getSelectedIndex() : int{ + return $this->selectedIndex; + } + + /** + * Sets which hotbar slot the player is currently loading. + * + * @param int $hotbarSlot 0-8 index of the hotbar slot to hold + * + * @throws \InvalidArgumentException if the hotbar slot is out of range + */ + public function setSelectedIndex(int $hotbarSlot) : void{ + $this->throwIfNotHotbarSlot($hotbarSlot); + + $oldIndex = $this->selectedIndex; + $this->selectedIndex = $hotbarSlot; + + foreach($this->selectedIndexChangeListeners as $callback){ + $callback($oldIndex); + } + } + + /** + * @return \Closure[]|ObjectSet + * @phpstan-return ObjectSet<\Closure(int $oldIndex) : void> + */ + public function getSelectedIndexChangeListeners() : ObjectSet{ return $this->selectedIndexChangeListeners; } + + /** + * Returns the currently-held item. + */ + public function getHeldItem() : Item{ + return $this->getHotbarSlotItem($this->selectedIndex); + } + + /** + * Sets the item in the currently-held slot to the specified item. + */ + public function setHeldItem(Item $item) : void{ + $this->inventory->setItem($this->getSelectedIndex(), $item); + } + + /** + * Returns the number of slots in the hotbar. + */ + public function getSize() : int{ + return $this->size; + } +} diff --git a/src/inventory/PlayerInventory.php b/src/inventory/PlayerInventory.php index fdaa0adff88..a2e9e92521e 100644 --- a/src/inventory/PlayerInventory.php +++ b/src/inventory/PlayerInventory.php @@ -24,102 +24,16 @@ namespace pocketmine\inventory; use pocketmine\entity\Human; -use pocketmine\item\Item; -use pocketmine\player\Player; -use pocketmine\utils\ObjectSet; class PlayerInventory extends SimpleInventory{ protected Human $holder; - protected int $itemInHandIndex = 0; - - /** - * @var \Closure[]|ObjectSet - * @phpstan-var ObjectSet<\Closure(int $oldIndex) : void> - */ - protected ObjectSet $heldItemIndexChangeListeners; public function __construct(Human $player){ $this->holder = $player; - $this->heldItemIndexChangeListeners = new ObjectSet(); parent::__construct(36); } - public function isHotbarSlot(int $slot) : bool{ - return $slot >= 0 && $slot < $this->getHotbarSize(); - } - - /** - * @throws \InvalidArgumentException - */ - private function throwIfNotHotbarSlot(int $slot) : void{ - if(!$this->isHotbarSlot($slot)){ - throw new \InvalidArgumentException("$slot is not a valid hotbar slot index (expected 0 - " . ($this->getHotbarSize() - 1) . ")"); - } - } - - /** - * Returns the item in the specified hotbar slot. - * - * @throws \InvalidArgumentException if the hotbar slot index is out of range - */ - public function getHotbarSlotItem(int $hotbarSlot) : Item{ - $this->throwIfNotHotbarSlot($hotbarSlot); - return $this->getItem($hotbarSlot); - } - - /** - * Returns the hotbar slot number the holder is currently holding. - */ - public function getHeldItemIndex() : int{ - return $this->itemInHandIndex; - } - - /** - * Sets which hotbar slot the player is currently loading. - * - * @param int $hotbarSlot 0-8 index of the hotbar slot to hold - * - * @throws \InvalidArgumentException if the hotbar slot is out of range - */ - public function setHeldItemIndex(int $hotbarSlot) : void{ - $this->throwIfNotHotbarSlot($hotbarSlot); - - $oldIndex = $this->itemInHandIndex; - $this->itemInHandIndex = $hotbarSlot; - - foreach($this->heldItemIndexChangeListeners as $callback){ - $callback($oldIndex); - } - } - - /** - * @return \Closure[]|ObjectSet - * @phpstan-return ObjectSet<\Closure(int $oldIndex) : void> - */ - public function getHeldItemIndexChangeListeners() : ObjectSet{ return $this->heldItemIndexChangeListeners; } - - /** - * Returns the currently-held item. - */ - public function getItemInHand() : Item{ - return $this->getHotbarSlotItem($this->itemInHandIndex); - } - - /** - * Sets the item in the currently-held slot to the specified item. - */ - public function setItemInHand(Item $item) : void{ - $this->setItem($this->getHeldItemIndex(), $item); - } - - /** - * Returns the number of slots in the hotbar. - */ - public function getHotbarSize() : int{ - return 9; - } - public function getHolder() : Human{ return $this->holder; } diff --git a/src/item/Armor.php b/src/item/Armor.php index 417c57f75ca..9e1046d9629 100644 --- a/src/item/Armor.php +++ b/src/item/Armor.php @@ -145,7 +145,7 @@ public function onClickAir(Player $player, Vector3 $directionVector, array &$ret $thisCopy = clone $this; $new = $thisCopy->pop(); $player->getArmorInventory()->setItem($this->getArmorSlot(), $new); - $player->getInventory()->setItemInHand($existing); + $player->getHotbar()->setHeldItem($existing); $sound = $new->getMaterial()->getEquipSound(); if($sound !== null){ $player->broadcastSound($sound); diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index e4c303121f7..85c0dc0fb36 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -132,7 +132,7 @@ public function __construct( $this->addComplex(UIInventorySlotOffset::CURSOR, $this->player->getCursorInventory()); $this->addComplex(UIInventorySlotOffset::CRAFTING2X2_INPUT, $this->player->getCraftingGrid()); - $this->player->getInventory()->getHeldItemIndexChangeListeners()->add($this->syncSelectedHotbarSlot(...)); + $this->player->getHotbar()->getSelectedIndexChangeListeners()->add($this->syncSelectedHotbarSlot(...)); } private function associateIdWithInventory(int $id, Inventory $inventory) : void{ @@ -668,7 +668,7 @@ public function onClientSelectHotbarSlot(int $slot) : void{ public function syncSelectedHotbarSlot() : void{ $playerInventory = $this->player->getInventory(); - $selected = $playerInventory->getHeldItemIndex(); + $selected = $this->player->getHotbar()->getSelectedIndex(); if($selected !== $this->clientSelectedHotbarSlot){ $inventoryEntry = $this->inventories[spl_object_id($playerInventory)] ?? null; if($inventoryEntry === null){ @@ -681,7 +681,7 @@ public function syncSelectedHotbarSlot() : void{ $this->session->sendDataPacket(MobEquipmentPacket::create( $this->player->getId(), - new ItemStackWrapper($itemStackInfo->getStackId(), $this->session->getTypeConverter()->coreItemStackToNet($playerInventory->getItemInHand())), + new ItemStackWrapper($itemStackInfo->getStackId(), $this->session->getTypeConverter()->coreItemStackToNet($playerInventory->getItem($selected))), $selected, $selected, ContainerIds::INVENTORY diff --git a/src/network/mcpe/StandardEntityEventBroadcaster.php b/src/network/mcpe/StandardEntityEventBroadcaster.php index 3e2df399483..b19a631dc9d 100644 --- a/src/network/mcpe/StandardEntityEventBroadcaster.php +++ b/src/network/mcpe/StandardEntityEventBroadcaster.php @@ -103,12 +103,12 @@ public function onEntityRemoved(array $recipients, Entity $entity) : void{ public function onMobMainHandItemChange(array $recipients, Human $mob) : void{ //TODO: we could send zero for slot here because remote players don't need to know which slot was selected - $inv = $mob->getInventory(); + $inv = $mob->getHotbar(); $this->sendDataPacket($recipients, MobEquipmentPacket::create( $mob->getId(), - ItemStackWrapper::legacy($this->typeConverter->coreItemStackToNet($inv->getItemInHand())), - $inv->getHeldItemIndex(), - $inv->getHeldItemIndex(), + ItemStackWrapper::legacy($this->typeConverter->coreItemStackToNet($inv->getHeldItem())), + $inv->getSelectedIndex(), + $inv->getSelectedIndex(), ContainerIds::INVENTORY )); } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index e74eb87c618..8c101853f63 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -304,11 +304,11 @@ public function handleActorEvent(ActorEventPacket $packet) : bool{ switch($packet->eventId){ case ActorEvent::EATING_ITEM: //TODO: ignore this and handle it server-side - $item = $this->player->getInventory()->getItemInHand(); + $item = $this->player->getHotbar()->getHeldItem(); if($item->isNull()){ return false; } - $this->player->broadcastAnimation(new ConsumingItemAnimation($this->player, $this->player->getInventory()->getItemInHand())); + $this->player->broadcastAnimation(new ConsumingItemAnimation($this->player, $item)); break; default: return false; diff --git a/src/player/Player.php b/src/player/Player.php index 858ad6bf54a..3dba79931b0 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -347,7 +347,7 @@ protected function initHumanData(CompoundTag $nbt) : void{ } private function callDummyItemHeldEvent() : void{ - $slot = $this->inventory->getHeldItemIndex(); + $slot = $this->hotbar->getSelectedIndex(); $event = new PlayerItemHeldEvent($this, $this->inventory->getItem($slot), $slot); $event->call(); @@ -362,7 +362,7 @@ protected function initEntity(CompoundTag $nbt) : void{ $this->inventory->getListeners()->add(new CallbackInventoryListener( function(Inventory $unused, int $slot) : void{ - if($slot === $this->inventory->getHeldItemIndex()){ + if($slot === $this->hotbar->getSelectedIndex()){ $this->setUsingItem(false); $this->callDummyItemHeldEvent(); @@ -1540,10 +1540,10 @@ public function chat(string $message) : bool{ } public function selectHotbarSlot(int $hotbarSlot) : bool{ - if(!$this->inventory->isHotbarSlot($hotbarSlot)){ //TODO: exception here? + if(!$this->hotbar->isHotbarSlot($hotbarSlot)){ //TODO: exception here? return false; } - if($hotbarSlot === $this->inventory->getHeldItemIndex()){ + if($hotbarSlot === $this->hotbar->getSelectedIndex()){ return true; } @@ -1553,7 +1553,7 @@ public function selectHotbarSlot(int $hotbarSlot) : bool{ return false; } - $this->inventory->setHeldItemIndex($hotbarSlot); + $this->hotbar->setSelectedIndex($hotbarSlot); $this->setUsingItem(false); return true; @@ -1565,7 +1565,7 @@ public function selectHotbarSlot(int $hotbarSlot) : bool{ private function returnItemsFromAction(Item $oldHeldItem, Item $newHeldItem, array $extraReturnedItems) : void{ $heldItemChanged = false; - if(!$newHeldItem->equalsExact($oldHeldItem) && $oldHeldItem->equalsExact($this->inventory->getItemInHand())){ + if(!$newHeldItem->equalsExact($oldHeldItem) && $oldHeldItem->equalsExact($this->hotbar->getHeldItem())){ //determine if the item was changed in some meaningful way, or just damaged/changed count //if it was really changed we always need to set it, whether we have finite resources or not $newReplica = clone $oldHeldItem; @@ -1579,7 +1579,7 @@ private function returnItemsFromAction(Item $oldHeldItem, Item $newHeldItem, arr if($newHeldItem instanceof Durable && $newHeldItem->isBroken()){ $this->broadcastSound(new ItemBreakSound()); } - $this->inventory->setItemInHand($newHeldItem); + $this->hotbar->setHeldItem($newHeldItem); $heldItemChanged = true; } } @@ -1589,7 +1589,7 @@ private function returnItemsFromAction(Item $oldHeldItem, Item $newHeldItem, arr } if($heldItemChanged && count($extraReturnedItems) > 0 && $newHeldItem->isNull()){ - $this->inventory->setItemInHand(array_shift($extraReturnedItems)); + $this->hotbar->setHeldItem(array_shift($extraReturnedItems)); } foreach($this->inventory->addItem(...$extraReturnedItems) as $drop){ //TODO: we can't generate a transaction for this since the items aren't coming from an inventory :( @@ -1611,7 +1611,7 @@ private function returnItemsFromAction(Item $oldHeldItem, Item $newHeldItem, arr */ public function useHeldItem() : bool{ $directionVector = $this->getDirectionVector(); - $item = $this->inventory->getItemInHand(); + $item = $this->hotbar->getHeldItem(); $oldItem = clone $item; $ev = new PlayerItemUseEvent($this, $item, $directionVector); @@ -1645,7 +1645,7 @@ public function useHeldItem() : bool{ * @return bool if the consumption succeeded. */ public function consumeHeldItem() : bool{ - $slot = $this->inventory->getItemInHand(); + $slot = $this->hotbar->getHeldItem(); if($slot instanceof ConsumableItem){ $oldItem = clone $slot; @@ -1678,7 +1678,7 @@ public function consumeHeldItem() : bool{ */ public function releaseHeldItem() : bool{ try{ - $item = $this->inventory->getItemInHand(); + $item = $this->hotbar->getHeldItem(); if(!$this->isUsingItem() || $this->hasItemCooldown($item)){ return false; } @@ -1748,21 +1748,21 @@ public function pickEntity(int $entityId) : bool{ private function equipOrAddPickedItem(int $existingSlot, Item $item) : void{ if($existingSlot !== -1){ - if($existingSlot < $this->inventory->getHotbarSize()){ - $this->inventory->setHeldItemIndex($existingSlot); + if($existingSlot < $this->hotbar->getSize()){ + $this->hotbar->setSelectedIndex($existingSlot); }else{ - $this->inventory->swap($this->inventory->getHeldItemIndex(), $existingSlot); + $this->inventory->swap($this->hotbar->getSelectedIndex(), $existingSlot); } }else{ $firstEmpty = $this->inventory->firstEmpty(); if($firstEmpty === -1){ //full inventory - $this->inventory->setItemInHand($item); - }elseif($firstEmpty < $this->inventory->getHotbarSize()){ + $this->hotbar->setHeldItem($item); + }elseif($firstEmpty < $this->hotbar->getSize()){ $this->inventory->setItem($firstEmpty, $item); - $this->inventory->setHeldItemIndex($firstEmpty); + $this->hotbar->setSelectedIndex($firstEmpty); }else{ - $this->inventory->swap($this->inventory->getHeldItemIndex(), $firstEmpty); - $this->inventory->setItemInHand($item); + $this->inventory->swap($this->hotbar->getSelectedIndex(), $firstEmpty); + $this->hotbar->setHeldItem($item); } } } @@ -1779,7 +1779,7 @@ public function attackBlock(Vector3 $pos, int $face) : bool{ $target = $this->getWorld()->getBlock($pos); - $ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, null, $face, PlayerInteractEvent::LEFT_CLICK_BLOCK); + $ev = new PlayerInteractEvent($this, $this->hotbar->getHeldItem(), $target, null, $face, PlayerInteractEvent::LEFT_CLICK_BLOCK); if($this->isSpectator()){ $ev->cancel(); } @@ -1788,7 +1788,7 @@ public function attackBlock(Vector3 $pos, int $face) : bool{ return false; } $this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers()); - if($target->onAttack($this->inventory->getItemInHand(), $face, $this)){ + if($target->onAttack($this->hotbar->getHeldItem(), $face, $this)){ return true; } @@ -1829,7 +1829,7 @@ public function breakBlock(Vector3 $pos) : bool{ if($this->canInteract($pos->add(0.5, 0.5, 0.5), $this->isCreative() ? self::MAX_REACH_DISTANCE_CREATIVE : self::MAX_REACH_DISTANCE_SURVIVAL)){ $this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers()); $this->stopBreakBlock($pos); - $item = $this->inventory->getItemInHand(); + $item = $this->hotbar->getHeldItem(); $oldItem = clone $item; $returnedItems = []; if($this->getWorld()->useBreakOn($pos, $item, $this, true, $returnedItems)){ @@ -1854,7 +1854,7 @@ public function interactBlock(Vector3 $pos, int $face, Vector3 $clickOffset) : b if($this->canInteract($pos->add(0.5, 0.5, 0.5), $this->isCreative() ? self::MAX_REACH_DISTANCE_CREATIVE : self::MAX_REACH_DISTANCE_SURVIVAL)){ $this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers()); - $item = $this->inventory->getItemInHand(); //this is a copy of the real item + $item = $this->hotbar->getHeldItem(); //this is a copy of the real item $oldItem = clone $item; $returnedItems = []; if($this->getWorld()->useItemOn($pos, $item, $face, $clickOffset, $this, true, $returnedItems)){ @@ -1883,7 +1883,7 @@ public function attackEntity(Entity $entity) : bool{ return false; } - $heldItem = $this->inventory->getItemInHand(); + $heldItem = $this->hotbar->getHeldItem(); $oldItem = clone $heldItem; $ev = new EntityDamageByEntityEvent($this, $entity, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $heldItem->getAttackPoints()); @@ -1969,15 +1969,15 @@ public function interactEntity(Entity $entity, Vector3 $clickPos) : bool{ $ev->call(); - $item = $this->inventory->getItemInHand(); + $item = $this->hotbar->getHeldItem(); $oldItem = clone $item; if(!$ev->isCancelled()){ if($item->onInteractEntity($this, $entity, $clickPos)){ - if($this->hasFiniteResources() && !$item->equalsExact($oldItem) && $oldItem->equalsExact($this->inventory->getItemInHand())){ + if($this->hasFiniteResources() && !$item->equalsExact($oldItem) && $oldItem->equalsExact($this->hotbar->getHeldItem())){ if($item instanceof Durable && $item->isBroken()){ $this->broadcastSound(new ItemBreakSound()); } - $this->inventory->setItemInHand($item); + $this->hotbar->setHeldItem($item); } } return $entity->onInteract($this, $clickPos); @@ -2405,8 +2405,8 @@ protected function onDeath() : void{ $this->getWorld()->dropItem($this->location, $item); } + $this->hotbar->setSelectedIndex(0); $clearInventory = fn(Inventory $inventory) => $inventory->setContents(array_filter($inventory->getContents(), fn(Item $item) => $item->keepOnDeath())); - $this->inventory->setHeldItemIndex(0); $clearInventory($this->inventory); $clearInventory($this->armorInventory); $clearInventory($this->offHandInventory); diff --git a/src/player/SurvivalBlockBreakHandler.php b/src/player/SurvivalBlockBreakHandler.php index e31e77ef7c7..95db3916871 100644 --- a/src/player/SurvivalBlockBreakHandler.php +++ b/src/player/SurvivalBlockBreakHandler.php @@ -66,7 +66,7 @@ private function calculateBreakProgressPerTick() : float{ return 0.0; } //TODO: improve this to take stuff like swimming, ladders, enchanted tools into account, fix wrong tool break time calculations for bad tools (pmmp/PocketMine-MP#211) - $breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getInventory()->getItemInHand()) * 20; + $breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getHotbar()->getHeldItem()) * 20; if($breakTimePerTick > 0){ return 1 / $breakTimePerTick; From 473bbe64e036cf3fe8b560e8dc295e4460840876 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Nov 2024 17:59:57 +0000 Subject: [PATCH 02/28] BaseInventory no longer uses viewers to send updates to players we want viewers to be as close to decorative as possible, so that they provide useful information to plugins, but don't get in the way of other changes. --- src/inventory/BaseInventory.php | 20 -------------------- src/network/mcpe/InventoryManager.php | 13 +++++++++++-- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 522c827a4b2..14ed270c543 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -99,15 +99,10 @@ public function setContents(array $items) : void{ $listeners = $this->listeners->toArray(); $this->listeners->clear(); - $viewers = $this->viewers; - $this->viewers = []; $this->internalSetContents($items); $this->listeners->add(...$listeners); //don't directly write, in case listeners were added while operation was in progress - foreach($viewers as $id => $viewer){ - $this->viewers[$id] = $viewer; - } $this->onContentChange($oldContents); } @@ -369,13 +364,6 @@ protected function onSlotChange(int $index, Item $before) : void{ foreach($this->listeners as $listener){ $listener->onSlotChange($this, $index, $before); } - foreach($this->viewers as $viewer){ - $invManager = $viewer->getNetworkSession()->getInvManager(); - if($invManager === null){ - continue; - } - $invManager->onSlotChange($this, $index); - } } /** @@ -386,14 +374,6 @@ protected function onContentChange(array $itemsBefore) : void{ foreach($this->listeners as $listener){ $listener->onContentChange($this, $itemsBefore); } - - foreach($this->getViewers() as $viewer){ - $invManager = $viewer->getNetworkSession()->getInvManager(); - if($invManager === null){ - continue; - } - $invManager->syncContents($this); - } } public function slotExists(int $slot) : bool{ diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index 85c0dc0fb36..a57bf20c45b 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -37,10 +37,12 @@ use pocketmine\crafting\FurnaceType; use pocketmine\data\bedrock\EnchantmentIdMap; use pocketmine\inventory\Inventory; +use pocketmine\inventory\InventoryListener; use pocketmine\inventory\transaction\action\SlotChangeAction; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\item\enchantment\EnchantingOption; use pocketmine\item\enchantment\EnchantmentInstance; +use pocketmine\item\Item; use pocketmine\network\mcpe\cache\CreativeInventoryCache; use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\ContainerClosePacket; @@ -78,7 +80,7 @@ /** * @phpstan-type ContainerOpenClosure \Closure(int $id, Inventory $inventory) : (list|null) */ -class InventoryManager{ +class InventoryManager implements InventoryListener{ /** * @var InventoryManagerEntry[] spl_object_id(Inventory) => InventoryManagerEntry * @phpstan-var array @@ -149,6 +151,7 @@ private function add(int $id, Inventory $inventory) : void{ throw new \InvalidArgumentException("Inventory " . get_class($inventory) . " is already tracked"); } $this->inventories[spl_object_id($inventory)] = new InventoryManagerEntry($inventory); + $inventory->getListeners()->add($this); $this->associateIdWithInventory($id, $inventory); } @@ -171,6 +174,7 @@ private function addComplex(array|int $slotMap, Inventory $inventory) : void{ $inventory, $complexSlotMap ); + $inventory->getListeners()->add($this); foreach($complexSlotMap->getSlotMap() as $netSlot => $coreSlot){ $this->complexSlotToInventoryMap[$netSlot] = $complexSlotMap; } @@ -191,6 +195,7 @@ private function remove(int $id) : void{ $inventory = $this->networkIdToInventoryMap[$id]; unset($this->networkIdToInventoryMap[$id]); if($this->getWindowId($inventory) === null){ + $inventory->getListeners()->remove($this); unset($this->inventories[spl_object_id($inventory)]); foreach($this->complexSlotToInventoryMap as $netSlot => $entry){ if($entry->getInventory() === $inventory){ @@ -468,7 +473,7 @@ private function itemStacksEqual(ItemStack $left, ItemStack $right) : bool{ $this->itemStackExtraDataEqual($left, $right); } - public function onSlotChange(Inventory $inventory, int $slot) : void{ + public function onSlotChange(Inventory $inventory, int $slot, Item $oldItem) : void{ $inventoryEntry = $this->inventories[spl_object_id($inventory)] ?? null; if($inventoryEntry === null){ //this can happen when an inventory changed during InventoryCloseEvent, or when a temporary inventory @@ -570,6 +575,10 @@ public function syncSlot(Inventory $inventory, int $slot, ItemStack $itemStack) unset($entry->predictions[$slot], $entry->pendingSyncs[$slot]); } + public function onContentChange(Inventory $inventory, array $oldContents) : void{ + $this->syncContents($inventory); + } + public function syncContents(Inventory $inventory) : void{ $entry = $this->inventories[spl_object_id($inventory)] ?? null; if($entry === null){ From 45a4282e8b4e131fc2cf37575a915bc77e157db5 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Nov 2024 21:40:47 +0000 Subject: [PATCH 03/28] First look: Split up Inventory & InventoryWindow this unblocks a variety of changes, such as positionless tiles, enhanced APIs on Blocks for inventories, and also eliminates a bunch of cyclic references within the core code. linked to #5033 --- src/block/Anvil.php | 4 +- src/block/Barrel.php | 3 +- src/block/BrewingStand.php | 3 +- src/block/Campfire.php | 9 +- src/block/CartographyTable.php | 4 +- src/block/Chest.php | 23 +- src/block/CraftingTable.php | 4 +- src/block/EnchantingTable.php | 4 +- src/block/EnderChest.php | 5 +- src/block/Furnace.php | 3 +- src/block/Hopper.php | 3 +- src/block/Loom.php | 4 +- src/block/ShulkerBox.php | 3 +- src/block/SmithingTable.php | 4 +- src/block/Stonecutter.php | 4 +- .../inventory/AnimatedBlockInventoryTrait.php | 67 ---- .../AnimatedBlockInventoryWindow.php | 65 ++++ ...Inventory.php => AnvilInventoryWindow.php} | 15 +- ...nventory.php => BarrelInventoryWindow.php} | 18 +- src/block/inventory/BlockInventoryWindow.php | 42 +++ ...it.php => BrewingStandInventoryWindow.php} | 14 +- src/block/inventory/CampfireInventory.php | 8 +- ...hp => CartographyTableInventoryWindow.php} | 14 +- ...Inventory.php => ChestInventoryWindow.php} | 16 +- ...y.php => CraftingTableInventoryWindow.php} | 14 +- src/block/inventory/DoubleChestInventory.php | 33 +- ...ory.php => DoubleChestInventoryWindow.php} | 32 +- src/block/inventory/EnchantInventory.php | 85 ----- .../EnchantingTableInventoryWindow.php | 110 +++++++ ...tory.php => EnderChestInventoryWindow.php} | 55 ++-- ...ventory.php => FurnaceInventoryWindow.php} | 24 +- ...nventory.php => HopperInventoryWindow.php} | 4 +- ...mInventory.php => LoomInventoryWindow.php} | 14 +- ...tory.php => ShulkerBoxInventoryWindow.php} | 27 +- .../inventory/SmithingTableInventory.php | 37 --- ...y.php => SmithingTableInventoryWindow.php} | 11 +- ...ory.php => StonecutterInventoryWindow.php} | 12 +- src/block/tile/Barrel.php | 11 +- src/block/tile/BrewingStand.php | 23 +- src/block/tile/Campfire.php | 2 +- src/block/tile/Chest.php | 11 +- src/block/tile/Furnace.php | 23 +- src/block/tile/Hopper.php | 11 +- src/block/tile/ShulkerBox.php | 24 +- src/block/utils/BrewingStandSlot.php | 8 +- src/crafting/CraftingGrid.php | 2 +- src/entity/Human.php | 26 +- src/entity/Living.php | 3 +- src/event/inventory/InventoryCloseEvent.php | 4 +- src/event/inventory/InventoryEvent.php | 8 +- src/event/inventory/InventoryOpenEvent.php | 4 +- .../PlayerEnchantingOptionsRequestEvent.php | 7 +- src/inventory/ArmorInventory.php | 9 +- src/inventory/BaseInventory.php | 11 +- src/inventory/DelegateInventory.php | 4 + src/inventory/Inventory.php | 12 + src/inventory/PlayerCraftingInventory.php | 36 --- src/inventory/PlayerCursorInventory.php | 38 --- src/inventory/PlayerEnderInventory.php | 37 --- src/inventory/PlayerInventory.php | 40 --- src/inventory/PlayerOffHandInventory.php | 37 --- src/inventory/SimpleInventory.php | 2 +- .../transaction/InventoryTransaction.php | 30 +- ...entory.php => SlotChangeActionBuilder.php} | 31 +- .../transaction/TransactionBuilder.php | 8 +- .../transaction/action/SlotChangeAction.php | 26 +- ...MapEntry.php => ComplexWindowMapEntry.php} | 8 +- src/network/mcpe/InventoryManager.php | 303 ++++++++++-------- src/network/mcpe/InventoryManagerEntry.php | 6 +- .../mcpe/handler/InGamePacketHandler.php | 6 +- .../mcpe/handler/ItemStackRequestExecutor.php | 37 ++- .../mcpe/handler/ItemStackResponseBuilder.php | 3 +- src/player/Player.php | 108 ++++--- .../TemporaryInventoryWindow.php} | 4 +- 74 files changed, 827 insertions(+), 933 deletions(-) delete mode 100644 src/block/inventory/AnimatedBlockInventoryTrait.php create mode 100644 src/block/inventory/AnimatedBlockInventoryWindow.php rename src/block/inventory/{AnvilInventory.php => AnvilInventoryWindow.php} (74%) rename src/block/inventory/{BarrelInventory.php => BarrelInventoryWindow.php} (71%) create mode 100644 src/block/inventory/BlockInventoryWindow.php rename src/block/inventory/{BlockInventoryTrait.php => BrewingStandInventoryWindow.php} (76%) rename src/block/inventory/{CartographyTableInventory.php => CartographyTableInventoryWindow.php} (72%) rename src/block/inventory/{ChestInventory.php => ChestInventoryWindow.php} (72%) rename src/block/inventory/{CraftingTableInventory.php => CraftingTableInventoryWindow.php} (72%) rename src/block/inventory/{BrewingStandInventory.php => DoubleChestInventoryWindow.php} (54%) delete mode 100644 src/block/inventory/EnchantInventory.php create mode 100644 src/block/inventory/EnchantingTableInventoryWindow.php rename src/block/inventory/{EnderChestInventory.php => EnderChestInventoryWindow.php} (53%) rename src/block/inventory/{FurnaceInventory.php => FurnaceInventoryWindow.php} (71%) rename src/block/inventory/{BlockInventory.php => HopperInventoryWindow.php} (88%) rename src/block/inventory/{LoomInventory.php => LoomInventoryWindow.php} (75%) rename src/block/inventory/{ShulkerBoxInventory.php => ShulkerBoxInventoryWindow.php} (59%) delete mode 100644 src/block/inventory/SmithingTableInventory.php rename src/block/inventory/{HopperInventory.php => SmithingTableInventoryWindow.php} (73%) rename src/block/inventory/{StonecutterInventory.php => StonecutterInventoryWindow.php} (73%) delete mode 100644 src/inventory/PlayerCraftingInventory.php delete mode 100644 src/inventory/PlayerCursorInventory.php delete mode 100644 src/inventory/PlayerEnderInventory.php delete mode 100644 src/inventory/PlayerInventory.php delete mode 100644 src/inventory/PlayerOffHandInventory.php rename src/inventory/transaction/{TransactionBuilderInventory.php => SlotChangeActionBuilder.php} (70%) rename src/network/mcpe/{ComplexInventoryMapEntry.php => ComplexWindowMapEntry.php} (88%) rename src/{inventory/TemporaryInventory.php => player/TemporaryInventoryWindow.php} (90%) diff --git a/src/block/Anvil.php b/src/block/Anvil.php index 4b4afef6153..84013baaddb 100644 --- a/src/block/Anvil.php +++ b/src/block/Anvil.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\AnvilInventory; +use pocketmine\block\inventory\AnvilInventoryWindow; use pocketmine\block\utils\Fallable; use pocketmine\block\utils\FallableTrait; use pocketmine\block\utils\HorizontalFacingTrait; @@ -83,7 +83,7 @@ public function getSupportType(int $facing) : SupportType{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player instanceof Player){ - $player->setCurrentWindow(new AnvilInventory($this->position)); + $player->setCurrentWindow(new AnvilInventoryWindow($player, $this->position)); } return true; diff --git a/src/block/Barrel.php b/src/block/Barrel.php index 0f0499ab93b..b1dcd8124e5 100644 --- a/src/block/Barrel.php +++ b/src/block/Barrel.php @@ -23,6 +23,7 @@ namespace pocketmine\block; +use pocketmine\block\inventory\BarrelInventoryWindow; use pocketmine\block\tile\Barrel as TileBarrel; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -81,7 +82,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } - $player->setCurrentWindow($barrel->getInventory()); + $player->setCurrentWindow(new BarrelInventoryWindow($player, $barrel->getInventory(), $this->position)); } } diff --git a/src/block/BrewingStand.php b/src/block/BrewingStand.php index 03969316494..2ea276e7577 100644 --- a/src/block/BrewingStand.php +++ b/src/block/BrewingStand.php @@ -23,6 +23,7 @@ namespace pocketmine\block; +use pocketmine\block\inventory\BrewingStandInventoryWindow; use pocketmine\block\tile\BrewingStand as TileBrewingStand; use pocketmine\block\utils\BrewingStandSlot; use pocketmine\block\utils\SupportType; @@ -99,7 +100,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player if($player instanceof Player){ $stand = $this->position->getWorld()->getTile($this->position); if($stand instanceof TileBrewingStand && $stand->canOpenWith($item->getCustomName())){ - $player->setCurrentWindow($stand->getInventory()); + $player->setCurrentWindow(new BrewingStandInventoryWindow($player, $stand->getInventory(), $this->position)); } } diff --git a/src/block/Campfire.php b/src/block/Campfire.php index ce759ee87f7..d25f3f2cd73 100644 --- a/src/block/Campfire.php +++ b/src/block/Campfire.php @@ -71,6 +71,13 @@ class Campfire extends Transparent{ protected CampfireInventory $inventory; + public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){ + parent::__construct($idInfo, $name, $typeInfo); + //TODO: this should never have been in the block - it creates problems for setting blocks in different positions + //as inventories aren't designed to be cloned + $this->inventory = new CampfireInventory(); + } + /** * @var int[] slot => ticks * @phpstan-var array @@ -89,7 +96,7 @@ public function readStateFromWorld() : Block{ $this->inventory = $tile->getInventory(); $this->cookingTimes = $tile->getCookingTimes(); }else{ - $this->inventory = new CampfireInventory($this->position); + $this->inventory = new CampfireInventory(); } return $this; diff --git a/src/block/CartographyTable.php b/src/block/CartographyTable.php index 67d950c5ac6..3338f219b0d 100644 --- a/src/block/CartographyTable.php +++ b/src/block/CartographyTable.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\CartographyTableInventory; +use pocketmine\block\inventory\CartographyTableInventoryWindow; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; @@ -32,7 +32,7 @@ final class CartographyTable extends Opaque{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ - $player->setCurrentWindow(new CartographyTableInventory($this->position)); + $player->setCurrentWindow(new CartographyTableInventoryWindow($player, $this->position)); } return true; diff --git a/src/block/Chest.php b/src/block/Chest.php index dca21576aa9..e823187c3d3 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -23,6 +23,8 @@ namespace pocketmine\block; +use pocketmine\block\inventory\ChestInventoryWindow; +use pocketmine\block\inventory\DoubleChestInventoryWindow; use pocketmine\block\tile\Chest as TileChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; @@ -74,8 +76,8 @@ public function onPostPlace() : void{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player instanceof Player){ - - $chest = $this->position->getWorld()->getTile($this->position); + $world = $this->position->getWorld(); + $chest = $world->getTile($this->position); if($chest instanceof TileChest){ if( !$this->getSide(Facing::UP)->isTransparent() || @@ -85,7 +87,22 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } - $player->setCurrentWindow($chest->getInventory()); + foreach([false, true] as $clockwise){ + $sideFacing = Facing::rotateY($this->facing, $clockwise); + $side = $this->position->getSide($sideFacing); + $pair = $world->getTile($side); + if($pair instanceof TileChest && $pair->getPair() === $chest){ + [$left, $right] = $clockwise ? [$side, $this->position] : [$this->position, $side]; + + //TODO: we should probably construct DoubleChestInventory here directly too using the same logic + //right now it uses some weird logic in TileChest which produces incorrect results + //however I'm not sure if this is currently possible + $window = new DoubleChestInventoryWindow($player, $chest->getInventory(), $left, $right); + break; + } + } + + $player->setCurrentWindow($window ?? new ChestInventoryWindow($player, $chest->getInventory(), $this->position)); } } diff --git a/src/block/CraftingTable.php b/src/block/CraftingTable.php index dcd9edce2cd..7fd0e43fdff 100644 --- a/src/block/CraftingTable.php +++ b/src/block/CraftingTable.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\CraftingTableInventory; +use pocketmine\block\inventory\CraftingTableInventoryWindow; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; @@ -32,7 +32,7 @@ class CraftingTable extends Opaque{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player instanceof Player){ - $player->setCurrentWindow(new CraftingTableInventory($this->position)); + $player->setCurrentWindow(new CraftingTableInventoryWindow($player, $this->position)); } return true; diff --git a/src/block/EnchantingTable.php b/src/block/EnchantingTable.php index 6a6c936b220..36ec15c8a4f 100644 --- a/src/block/EnchantingTable.php +++ b/src/block/EnchantingTable.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\EnchantInventory; +use pocketmine\block\inventory\EnchantingTableInventoryWindow; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; @@ -48,7 +48,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player if($player instanceof Player){ //TODO lock - $player->setCurrentWindow(new EnchantInventory($this->position)); + $player->setCurrentWindow(new EnchantingTableInventoryWindow($player, $this->position)); } return true; diff --git a/src/block/EnderChest.php b/src/block/EnderChest.php index 9004f7c79f9..f4634e20095 100644 --- a/src/block/EnderChest.php +++ b/src/block/EnderChest.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\EnderChestInventory; +use pocketmine\block\inventory\EnderChestInventoryWindow; use pocketmine\block\tile\EnderChest as TileEnderChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; @@ -56,8 +56,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player if($player instanceof Player){ $enderChest = $this->position->getWorld()->getTile($this->position); if($enderChest instanceof TileEnderChest && $this->getSide(Facing::UP)->isTransparent()){ - $enderChest->setViewerCount($enderChest->getViewerCount() + 1); - $player->setCurrentWindow(new EnderChestInventory($this->position, $player->getEnderInventory())); + $player->setCurrentWindow(new EnderChestInventoryWindow($player, $player->getEnderInventory(), $this->position)); } } diff --git a/src/block/Furnace.php b/src/block/Furnace.php index 7a64e3cd3e5..d8394fcc91c 100644 --- a/src/block/Furnace.php +++ b/src/block/Furnace.php @@ -23,6 +23,7 @@ namespace pocketmine\block; +use pocketmine\block\inventory\FurnaceInventoryWindow; use pocketmine\block\tile\Furnace as TileFurnace; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\LightableTrait; @@ -61,7 +62,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player if($player instanceof Player){ $furnace = $this->position->getWorld()->getTile($this->position); if($furnace instanceof TileFurnace && $furnace->canOpenWith($item->getCustomName())){ - $player->setCurrentWindow($furnace->getInventory()); + $player->setCurrentWindow(new FurnaceInventoryWindow($player, $furnace->getInventory(), $this->position, $this->furnaceType)); } } diff --git a/src/block/Hopper.php b/src/block/Hopper.php index 0d823674b23..45d8b6112e7 100644 --- a/src/block/Hopper.php +++ b/src/block/Hopper.php @@ -23,6 +23,7 @@ namespace pocketmine\block; +use pocketmine\block\inventory\HopperInventoryWindow; use pocketmine\block\tile\Hopper as TileHopper; use pocketmine\block\utils\PoweredByRedstoneTrait; use pocketmine\block\utils\SupportType; @@ -84,7 +85,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player if($player !== null){ $tile = $this->position->getWorld()->getTile($this->position); if($tile instanceof TileHopper){ //TODO: find a way to have inventories open on click without this boilerplate in every block - $player->setCurrentWindow($tile->getInventory()); + $player->setCurrentWindow(new HopperInventoryWindow($player, $tile->getInventory(), $this->position)); } return true; } diff --git a/src/block/Loom.php b/src/block/Loom.php index d3dd4f3c766..e9e634ff7b5 100644 --- a/src/block/Loom.php +++ b/src/block/Loom.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\LoomInventory; +use pocketmine\block\inventory\LoomInventoryWindow; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\item\Item; use pocketmine\math\Vector3; @@ -34,7 +34,7 @@ final class Loom extends Opaque{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ - $player->setCurrentWindow(new LoomInventory($this->position)); + $player->setCurrentWindow(new LoomInventoryWindow($player, $this->position)); return true; } return false; diff --git a/src/block/ShulkerBox.php b/src/block/ShulkerBox.php index d557401eec3..e6f208ba0d8 100644 --- a/src/block/ShulkerBox.php +++ b/src/block/ShulkerBox.php @@ -23,6 +23,7 @@ namespace pocketmine\block; +use pocketmine\block\inventory\ShulkerBoxInventoryWindow; use pocketmine\block\tile\ShulkerBox as TileShulkerBox; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\block\utils\SupportType; @@ -105,7 +106,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } - $player->setCurrentWindow($shulker->getInventory()); + $player->setCurrentWindow(new ShulkerBoxInventoryWindow($player, $shulker->getInventory(), $this->position)); } } diff --git a/src/block/SmithingTable.php b/src/block/SmithingTable.php index 741e9c02fdc..5d0f7632495 100644 --- a/src/block/SmithingTable.php +++ b/src/block/SmithingTable.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\SmithingTableInventory; +use pocketmine\block\inventory\SmithingTableInventoryWindow; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; @@ -32,7 +32,7 @@ final class SmithingTable extends Opaque{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ - $player->setCurrentWindow(new SmithingTableInventory($this->position)); + $player->setCurrentWindow(new SmithingTableInventoryWindow($player, $this->position)); } return true; diff --git a/src/block/Stonecutter.php b/src/block/Stonecutter.php index 30c19d25dcb..0c739e36a36 100644 --- a/src/block/Stonecutter.php +++ b/src/block/Stonecutter.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\StonecutterInventory; +use pocketmine\block\inventory\StonecutterInventoryWindow; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; @@ -37,7 +37,7 @@ class Stonecutter extends Transparent{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ - $player->setCurrentWindow(new StonecutterInventory($this->position)); + $player->setCurrentWindow(new StonecutterInventoryWindow($player, $this->position)); } return true; } diff --git a/src/block/inventory/AnimatedBlockInventoryTrait.php b/src/block/inventory/AnimatedBlockInventoryTrait.php deleted file mode 100644 index 8720c985b1e..00000000000 --- a/src/block/inventory/AnimatedBlockInventoryTrait.php +++ /dev/null @@ -1,67 +0,0 @@ -getViewers()); - } - - /** - * @return Player[] - * @phpstan-return array - */ - abstract public function getViewers() : array; - - abstract protected function getOpenSound() : Sound; - - abstract protected function getCloseSound() : Sound; - - public function onOpen(Player $who) : void{ - parent::onOpen($who); - - if($this->holder->isValid() && $this->getViewerCount() === 1){ - //TODO: this crap really shouldn't be managed by the inventory - $this->animateBlock(true); - $this->holder->getWorld()->addSound($this->holder->add(0.5, 0.5, 0.5), $this->getOpenSound()); - } - } - - abstract protected function animateBlock(bool $isOpen) : void; - - public function onClose(Player $who) : void{ - if($this->holder->isValid() && $this->getViewerCount() === 1){ - //TODO: this crap really shouldn't be managed by the inventory - $this->animateBlock(false); - $this->holder->getWorld()->addSound($this->holder->add(0.5, 0.5, 0.5), $this->getCloseSound()); - } - parent::onClose($who); - } -} diff --git a/src/block/inventory/AnimatedBlockInventoryWindow.php b/src/block/inventory/AnimatedBlockInventoryWindow.php new file mode 100644 index 00000000000..3dcf9207fd6 --- /dev/null +++ b/src/block/inventory/AnimatedBlockInventoryWindow.php @@ -0,0 +1,65 @@ +inventory->getViewers()); + } + + abstract protected function getOpenSound() : Sound; + + abstract protected function getCloseSound() : Sound; + + abstract protected function animateBlock(Position $position, bool $isOpen) : void; + + protected function playSound(Position $position, bool $isOpen) : void{ + $position->getWorld()->addSound($position->add(0.5, 0.5, 0.5), $isOpen ? $this->getOpenSound() : $this->getCloseSound()); + } + + protected function doBlockEffects(bool $isOpen) : void{ + $position = $this->holder; + $this->animateBlock($position, $isOpen); + $this->playSound($position, $isOpen); + } + + public function onOpen() : void{ + parent::onOpen(); + if($this->getViewerCount() === 1){ + $this->doBlockEffects(true); + } + } + + public function onClose() : void{ + if($this->getViewerCount() === 1){ + $this->doBlockEffects(false); + } + parent::onClose(); + } +} diff --git a/src/block/inventory/AnvilInventory.php b/src/block/inventory/AnvilInventoryWindow.php similarity index 74% rename from src/block/inventory/AnvilInventory.php rename to src/block/inventory/AnvilInventoryWindow.php index 7d906a6326e..50705d4f5d3 100644 --- a/src/block/inventory/AnvilInventory.php +++ b/src/block/inventory/AnvilInventoryWindow.php @@ -24,17 +24,18 @@ namespace pocketmine\block\inventory; use pocketmine\inventory\SimpleInventory; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\player\Player; +use pocketmine\player\TemporaryInventoryWindow; use pocketmine\world\Position; -class AnvilInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{ - use BlockInventoryTrait; - +final class AnvilInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ public const SLOT_INPUT = 0; public const SLOT_MATERIAL = 1; - public function __construct(Position $holder){ - $this->holder = $holder; - parent::__construct(2); + public function __construct( + Player $viewer, + Position $holder + ){ + parent::__construct($viewer, new SimpleInventory(2), $holder); } } diff --git a/src/block/inventory/BarrelInventory.php b/src/block/inventory/BarrelInventoryWindow.php similarity index 71% rename from src/block/inventory/BarrelInventory.php rename to src/block/inventory/BarrelInventoryWindow.php index 0d17d2a3e5b..89c8b70c773 100644 --- a/src/block/inventory/BarrelInventory.php +++ b/src/block/inventory/BarrelInventoryWindow.php @@ -24,19 +24,12 @@ namespace pocketmine\block\inventory; use pocketmine\block\Barrel; -use pocketmine\inventory\SimpleInventory; use pocketmine\world\Position; use pocketmine\world\sound\BarrelCloseSound; use pocketmine\world\sound\BarrelOpenSound; use pocketmine\world\sound\Sound; -class BarrelInventory extends SimpleInventory implements BlockInventory{ - use AnimatedBlockInventoryTrait; - - public function __construct(Position $holder){ - $this->holder = $holder; - parent::__construct(27); - } +final class BarrelInventoryWindow extends AnimatedBlockInventoryWindow{ protected function getOpenSound() : Sound{ return new BarrelOpenSound(); @@ -46,12 +39,11 @@ protected function getCloseSound() : Sound{ return new BarrelCloseSound(); } - protected function animateBlock(bool $isOpen) : void{ - $holder = $this->getHolder(); - $world = $holder->getWorld(); - $block = $world->getBlock($holder); + protected function animateBlock(Position $position, bool $isOpen) : void{ + $world = $position->getWorld(); + $block = $world->getBlock($position); if($block instanceof Barrel){ - $world->setBlock($holder, $block->setOpen($isOpen)); + $world->setBlock($position, $block->setOpen($isOpen)); } } } diff --git a/src/block/inventory/BlockInventoryWindow.php b/src/block/inventory/BlockInventoryWindow.php new file mode 100644 index 00000000000..23d27647e4b --- /dev/null +++ b/src/block/inventory/BlockInventoryWindow.php @@ -0,0 +1,42 @@ +holder; } +} diff --git a/src/block/inventory/BlockInventoryTrait.php b/src/block/inventory/BrewingStandInventoryWindow.php similarity index 76% rename from src/block/inventory/BlockInventoryTrait.php rename to src/block/inventory/BrewingStandInventoryWindow.php index 980e947f02b..eae68f60c6e 100644 --- a/src/block/inventory/BlockInventoryTrait.php +++ b/src/block/inventory/BrewingStandInventoryWindow.php @@ -23,12 +23,10 @@ namespace pocketmine\block\inventory; -use pocketmine\world\Position; - -trait BlockInventoryTrait{ - protected Position $holder; - - public function getHolder() : Position{ - return $this->holder; - } +final class BrewingStandInventoryWindow extends BlockInventoryWindow{ + public const SLOT_INGREDIENT = 0; + public const SLOT_BOTTLE_LEFT = 1; + public const SLOT_BOTTLE_MIDDLE = 2; + public const SLOT_BOTTLE_RIGHT = 3; + public const SLOT_FUEL = 4; } diff --git a/src/block/inventory/CampfireInventory.php b/src/block/inventory/CampfireInventory.php index ae762473e23..f3bd618c218 100644 --- a/src/block/inventory/CampfireInventory.php +++ b/src/block/inventory/CampfireInventory.php @@ -24,13 +24,9 @@ namespace pocketmine\block\inventory; use pocketmine\inventory\SimpleInventory; -use pocketmine\world\Position; -class CampfireInventory extends SimpleInventory implements BlockInventory{ - use BlockInventoryTrait; - - public function __construct(Position $holder){ - $this->holder = $holder; +final class CampfireInventory extends SimpleInventory{ + public function __construct(){ parent::__construct(4); } diff --git a/src/block/inventory/CartographyTableInventory.php b/src/block/inventory/CartographyTableInventoryWindow.php similarity index 72% rename from src/block/inventory/CartographyTableInventory.php rename to src/block/inventory/CartographyTableInventoryWindow.php index 7bd9146ac6f..90f06edb145 100644 --- a/src/block/inventory/CartographyTableInventory.php +++ b/src/block/inventory/CartographyTableInventoryWindow.php @@ -24,14 +24,16 @@ namespace pocketmine\block\inventory; use pocketmine\inventory\SimpleInventory; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\player\Player; +use pocketmine\player\TemporaryInventoryWindow; use pocketmine\world\Position; -final class CartographyTableInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{ - use BlockInventoryTrait; +final class CartographyTableInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ - public function __construct(Position $holder){ - $this->holder = $holder; - parent::__construct(2); + public function __construct( + Player $viewer, + Position $holder + ){ + parent::__construct($viewer, new SimpleInventory(2), $holder); } } diff --git a/src/block/inventory/ChestInventory.php b/src/block/inventory/ChestInventoryWindow.php similarity index 72% rename from src/block/inventory/ChestInventory.php rename to src/block/inventory/ChestInventoryWindow.php index b61fab57c35..be61fc22b1f 100644 --- a/src/block/inventory/ChestInventory.php +++ b/src/block/inventory/ChestInventoryWindow.php @@ -23,7 +23,6 @@ namespace pocketmine\block\inventory; -use pocketmine\inventory\SimpleInventory; use pocketmine\network\mcpe\protocol\BlockEventPacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\world\Position; @@ -31,13 +30,7 @@ use pocketmine\world\sound\ChestOpenSound; use pocketmine\world\sound\Sound; -class ChestInventory extends SimpleInventory implements BlockInventory{ - use AnimatedBlockInventoryTrait; - - public function __construct(Position $holder){ - $this->holder = $holder; - parent::__construct(27); - } +class ChestInventoryWindow extends AnimatedBlockInventoryWindow{ protected function getOpenSound() : Sound{ return new ChestOpenSound(); @@ -47,10 +40,9 @@ protected function getCloseSound() : Sound{ return new ChestCloseSound(); } - public function animateBlock(bool $isOpen) : void{ - $holder = $this->getHolder(); - + protected function animateBlock(Position $position, bool $isOpen) : void{ //event ID is always 1 for a chest - $holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0)); + //TODO: we probably shouldn't be sending a packet directly here, but it doesn't fit anywhere into existing systems + $position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); } } diff --git a/src/block/inventory/CraftingTableInventory.php b/src/block/inventory/CraftingTableInventoryWindow.php similarity index 72% rename from src/block/inventory/CraftingTableInventory.php rename to src/block/inventory/CraftingTableInventoryWindow.php index 767e8a5f49c..905cd1ccc54 100644 --- a/src/block/inventory/CraftingTableInventory.php +++ b/src/block/inventory/CraftingTableInventoryWindow.php @@ -24,14 +24,16 @@ namespace pocketmine\block\inventory; use pocketmine\crafting\CraftingGrid; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\player\Player; use pocketmine\world\Position; -final class CraftingTableInventory extends CraftingGrid implements BlockInventory, TemporaryInventory{ - use BlockInventoryTrait; +final class CraftingTableInventoryWindow extends BlockInventoryWindow{ - public function __construct(Position $holder){ - $this->holder = $holder; - parent::__construct(CraftingGrid::SIZE_BIG); + public function __construct( + Player $viewer, + Position $holder + ){ + //TODO: generics would be good for this, since it has special methods + parent::__construct($viewer, new CraftingGrid(CraftingGrid::SIZE_BIG), $holder); } } diff --git a/src/block/inventory/DoubleChestInventory.php b/src/block/inventory/DoubleChestInventory.php index a7eb4a4398c..5c11d1a7af7 100644 --- a/src/block/inventory/DoubleChestInventory.php +++ b/src/block/inventory/DoubleChestInventory.php @@ -24,27 +24,17 @@ namespace pocketmine\block\inventory; use pocketmine\inventory\BaseInventory; -use pocketmine\inventory\InventoryHolder; +use pocketmine\inventory\Inventory; use pocketmine\item\Item; -use pocketmine\world\sound\ChestCloseSound; -use pocketmine\world\sound\ChestOpenSound; -use pocketmine\world\sound\Sound; - -class DoubleChestInventory extends BaseInventory implements BlockInventory, InventoryHolder{ - use AnimatedBlockInventoryTrait; +final class DoubleChestInventory extends BaseInventory{ public function __construct( - private ChestInventory $left, - private ChestInventory $right + private Inventory $left, + private Inventory $right ){ - $this->holder = $this->left->getHolder(); parent::__construct(); } - public function getInventory() : self{ - return $this; - } - public function getSize() : int{ return $this->left->getSize() + $this->right->getSize(); } @@ -85,7 +75,7 @@ protected function internalSetContents(array $items) : void{ $this->right->setContents($rightContents); } - protected function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ + public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ $leftSize = $this->left->getSize(); return $slot < $leftSize ? $this->left->getMatchingItemCount($slot, $test, $checkTags) : @@ -99,20 +89,11 @@ public function isSlotEmpty(int $index) : bool{ $this->right->isSlotEmpty($index - $leftSize); } - protected function getOpenSound() : Sound{ return new ChestOpenSound(); } - - protected function getCloseSound() : Sound{ return new ChestCloseSound(); } - - protected function animateBlock(bool $isOpen) : void{ - $this->left->animateBlock($isOpen); - $this->right->animateBlock($isOpen); - } - - public function getLeftSide() : ChestInventory{ + public function getLeftSide() : Inventory{ return $this->left; } - public function getRightSide() : ChestInventory{ + public function getRightSide() : Inventory{ return $this->right; } } diff --git a/src/block/inventory/BrewingStandInventory.php b/src/block/inventory/DoubleChestInventoryWindow.php similarity index 54% rename from src/block/inventory/BrewingStandInventory.php rename to src/block/inventory/DoubleChestInventoryWindow.php index 8bab4ba97b0..e65312b1cf7 100644 --- a/src/block/inventory/BrewingStandInventory.php +++ b/src/block/inventory/DoubleChestInventoryWindow.php @@ -23,20 +23,30 @@ namespace pocketmine\block\inventory; -use pocketmine\inventory\SimpleInventory; +use pocketmine\inventory\Inventory; +use pocketmine\player\Player; use pocketmine\world\Position; -class BrewingStandInventory extends SimpleInventory implements BlockInventory{ - use BlockInventoryTrait; +final class DoubleChestInventoryWindow extends ChestInventoryWindow{ - public const SLOT_INGREDIENT = 0; - public const SLOT_BOTTLE_LEFT = 1; - public const SLOT_BOTTLE_MIDDLE = 2; - public const SLOT_BOTTLE_RIGHT = 3; - public const SLOT_FUEL = 4; + public function __construct( + Player $viewer, + Inventory $inventory, + private Position $left, + private Position $right + ){ + parent::__construct($viewer, $inventory, $this->left); + } + + public function getLeft() : Position{ return $this->left; } + + public function getRight() : Position{ return $this->right; } + + protected function doBlockEffects(bool $isOpen) : void{ + $this->animateBlock($this->left, $isOpen); + $this->animateBlock($this->right, $isOpen); - public function __construct(Position $holder, int $size = 5){ - $this->holder = $holder; - parent::__construct($size); + $this->playSound($this->left, $isOpen); + $this->playSound($this->right, $isOpen); } } diff --git a/src/block/inventory/EnchantInventory.php b/src/block/inventory/EnchantInventory.php deleted file mode 100644 index b726dbedf32..00000000000 --- a/src/block/inventory/EnchantInventory.php +++ /dev/null @@ -1,85 +0,0 @@ -holder = $holder; - parent::__construct(2); - } - - protected function onSlotChange(int $index, Item $before) : void{ - if($index === self::SLOT_INPUT){ - foreach($this->viewers as $viewer){ - $this->options = []; - $item = $this->getInput(); - $options = Helper::generateOptions($this->holder, $item, $viewer->getEnchantmentSeed()); - - $event = new PlayerEnchantingOptionsRequestEvent($viewer, $this, $options); - $event->call(); - if(!$event->isCancelled() && count($event->getOptions()) > 0){ - $this->options = array_values($event->getOptions()); - $viewer->getNetworkSession()->getInvManager()?->syncEnchantingTableOptions($this->options); - } - } - } - - parent::onSlotChange($index, $before); - } - - public function getInput() : Item{ - return $this->getItem(self::SLOT_INPUT); - } - - public function getLapis() : Item{ - return $this->getItem(self::SLOT_LAPIS); - } - - public function getOutput(int $optionId) : ?Item{ - $option = $this->getOption($optionId); - return $option === null ? null : Helper::enchantItem($this->getInput(), $option->getEnchantments()); - } - - public function getOption(int $optionId) : ?EnchantingOption{ - return $this->options[$optionId] ?? null; - } -} diff --git a/src/block/inventory/EnchantingTableInventoryWindow.php b/src/block/inventory/EnchantingTableInventoryWindow.php new file mode 100644 index 00000000000..660d5c707b3 --- /dev/null +++ b/src/block/inventory/EnchantingTableInventoryWindow.php @@ -0,0 +1,110 @@ + */ + private \WeakReference $listener; + + public function __construct( + Player $viewer, + Position $holder + ){ + parent::__construct($viewer, new SimpleInventory(2), $holder); + + /** @phpstan-var \WeakReference $weakThis */ + $weakThis = \WeakReference::create($this); + $listener = new CallbackInventoryListener( + onSlotChange: static function(Inventory $_, int $slot) use ($weakThis) : void{ //remaining params unneeded + if($slot === self::SLOT_INPUT && ($strongThis = $weakThis->get()) !== null){ + $strongThis->regenerateOptions(); + } + }, + onContentChange: static function() use ($weakThis) : void{ + if(($strongThis = $weakThis->get()) !== null){ + $strongThis->regenerateOptions(); + } + } + ); + $this->inventory->getListeners()->add($listener); + + $this->listener = \WeakReference::create($listener); + } + + public function __destruct(){ + $listener = $this->listener->get(); + if($listener !== null){ + $this->inventory->getListeners()->remove($listener); + } + } + + private function regenerateOptions() : void{ + $this->options = []; + $item = $this->getInput(); + $options = Helper::generateOptions($this->holder, $item, $this->viewer->getEnchantmentSeed()); + + $event = new PlayerEnchantingOptionsRequestEvent($this->viewer, $this, $options); + $event->call(); + if(!$event->isCancelled() && count($event->getOptions()) > 0){ + $this->options = array_values($event->getOptions()); + $this->viewer->getNetworkSession()->getInvManager()?->syncEnchantingTableOptions($this->options); + } + } + + public function getInput() : Item{ + return $this->inventory->getItem(self::SLOT_INPUT); + } + + public function getLapis() : Item{ + return $this->inventory->getItem(self::SLOT_LAPIS); + } + + public function getOutput(int $optionId) : ?Item{ + $option = $this->getOption($optionId); + return $option === null ? null : Helper::enchantItem($this->getInput(), $option->getEnchantments()); + } + + public function getOption(int $optionId) : ?EnchantingOption{ + return $this->options[$optionId] ?? null; + } +} diff --git a/src/block/inventory/EnderChestInventory.php b/src/block/inventory/EnderChestInventoryWindow.php similarity index 53% rename from src/block/inventory/EnderChestInventory.php rename to src/block/inventory/EnderChestInventoryWindow.php index c1d7c5401d3..ad290134d64 100644 --- a/src/block/inventory/EnderChestInventory.php +++ b/src/block/inventory/EnderChestInventoryWindow.php @@ -24,45 +24,30 @@ namespace pocketmine\block\inventory; use pocketmine\block\tile\EnderChest; -use pocketmine\inventory\DelegateInventory; -use pocketmine\inventory\Inventory; -use pocketmine\inventory\PlayerEnderInventory; use pocketmine\network\mcpe\protocol\BlockEventPacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; -use pocketmine\player\Player; use pocketmine\world\Position; use pocketmine\world\sound\EnderChestCloseSound; use pocketmine\world\sound\EnderChestOpenSound; use pocketmine\world\sound\Sound; -/** - * EnderChestInventory is not a real inventory; it's just a gateway to the player's ender inventory. - */ -class EnderChestInventory extends DelegateInventory implements BlockInventory{ - use AnimatedBlockInventoryTrait { - onClose as animatedBlockInventoryTrait_onClose; - } - - public function __construct( - Position $holder, - private PlayerEnderInventory $inventory - ){ - parent::__construct($inventory); - $this->holder = $holder; - } +final class EnderChestInventoryWindow extends AnimatedBlockInventoryWindow{ - public function getEnderInventory() : PlayerEnderInventory{ - return $this->inventory; - } - - public function getViewerCount() : int{ - $enderChest = $this->getHolder()->getWorld()->getTile($this->getHolder()); + protected function getViewerCount() : int{ + $enderChest = $this->holder->getWorld()->getTile($this->getHolder()); if(!$enderChest instanceof EnderChest){ return 0; } return $enderChest->getViewerCount(); } + private function updateViewerCount(int $amount) : void{ + $enderChest = $this->holder->getWorld()->getTile($this->getHolder()); + if($enderChest instanceof EnderChest){ + $enderChest->setViewerCount($enderChest->getViewerCount() + $amount); + } + } + protected function getOpenSound() : Sound{ return new EnderChestOpenSound(); } @@ -71,18 +56,18 @@ protected function getCloseSound() : Sound{ return new EnderChestCloseSound(); } - protected function animateBlock(bool $isOpen) : void{ - $holder = $this->getHolder(); - + protected function animateBlock(Position $position, bool $isOpen) : void{ //event ID is always 1 for a chest - $holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0)); + $position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); } - public function onClose(Player $who) : void{ - $this->animatedBlockInventoryTrait_onClose($who); - $enderChest = $this->getHolder()->getWorld()->getTile($this->getHolder()); - if($enderChest instanceof EnderChest){ - $enderChest->setViewerCount($enderChest->getViewerCount() - 1); - } + public function onOpen() : void{ + parent::onOpen(); + $this->updateViewerCount(1); + } + + public function onClose() : void{ + parent::onClose(); + $this->updateViewerCount(-1); } } diff --git a/src/block/inventory/FurnaceInventory.php b/src/block/inventory/FurnaceInventoryWindow.php similarity index 71% rename from src/block/inventory/FurnaceInventory.php rename to src/block/inventory/FurnaceInventoryWindow.php index ff44d6b7063..98e2ac360c4 100644 --- a/src/block/inventory/FurnaceInventory.php +++ b/src/block/inventory/FurnaceInventoryWindow.php @@ -24,48 +24,48 @@ namespace pocketmine\block\inventory; use pocketmine\crafting\FurnaceType; -use pocketmine\inventory\SimpleInventory; +use pocketmine\inventory\Inventory; use pocketmine\item\Item; +use pocketmine\player\Player; use pocketmine\world\Position; -class FurnaceInventory extends SimpleInventory implements BlockInventory{ - use BlockInventoryTrait; - +final class FurnaceInventoryWindow extends BlockInventoryWindow{ public const SLOT_INPUT = 0; public const SLOT_FUEL = 1; public const SLOT_RESULT = 2; public function __construct( + Player $viewer, + Inventory $inventory, Position $holder, private FurnaceType $furnaceType ){ - $this->holder = $holder; - parent::__construct(3); + parent::__construct($viewer, $inventory, $holder); } public function getFurnaceType() : FurnaceType{ return $this->furnaceType; } public function getResult() : Item{ - return $this->getItem(self::SLOT_RESULT); + return $this->inventory->getItem(self::SLOT_RESULT); } public function getFuel() : Item{ - return $this->getItem(self::SLOT_FUEL); + return $this->inventory->getItem(self::SLOT_FUEL); } public function getSmelting() : Item{ - return $this->getItem(self::SLOT_INPUT); + return $this->inventory->getItem(self::SLOT_INPUT); } public function setResult(Item $item) : void{ - $this->setItem(self::SLOT_RESULT, $item); + $this->inventory->setItem(self::SLOT_RESULT, $item); } public function setFuel(Item $item) : void{ - $this->setItem(self::SLOT_FUEL, $item); + $this->inventory->setItem(self::SLOT_FUEL, $item); } public function setSmelting(Item $item) : void{ - $this->setItem(self::SLOT_INPUT, $item); + $this->inventory->setItem(self::SLOT_INPUT, $item); } } diff --git a/src/block/inventory/BlockInventory.php b/src/block/inventory/HopperInventoryWindow.php similarity index 88% rename from src/block/inventory/BlockInventory.php rename to src/block/inventory/HopperInventoryWindow.php index 063a451a9ab..4ddf981c942 100644 --- a/src/block/inventory/BlockInventory.php +++ b/src/block/inventory/HopperInventoryWindow.php @@ -23,8 +23,6 @@ namespace pocketmine\block\inventory; -use pocketmine\world\Position; +final class HopperInventoryWindow extends BlockInventoryWindow{ -interface BlockInventory{ - public function getHolder() : Position; } diff --git a/src/block/inventory/LoomInventory.php b/src/block/inventory/LoomInventoryWindow.php similarity index 75% rename from src/block/inventory/LoomInventory.php rename to src/block/inventory/LoomInventoryWindow.php index fd34620a05f..1140cdf1a85 100644 --- a/src/block/inventory/LoomInventory.php +++ b/src/block/inventory/LoomInventoryWindow.php @@ -24,18 +24,20 @@ namespace pocketmine\block\inventory; use pocketmine\inventory\SimpleInventory; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\player\Player; +use pocketmine\player\TemporaryInventoryWindow; use pocketmine\world\Position; -final class LoomInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{ - use BlockInventoryTrait; +final class LoomInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ public const SLOT_BANNER = 0; public const SLOT_DYE = 1; public const SLOT_PATTERN = 2; - public function __construct(Position $holder, int $size = 3){ - $this->holder = $holder; - parent::__construct($size); + public function __construct( + Player $viewer, + Position $holder + ){ + parent::__construct($viewer, new SimpleInventory(3), $holder); } } diff --git a/src/block/inventory/ShulkerBoxInventory.php b/src/block/inventory/ShulkerBoxInventoryWindow.php similarity index 59% rename from src/block/inventory/ShulkerBoxInventory.php rename to src/block/inventory/ShulkerBoxInventoryWindow.php index d915a995182..a6e7b0c70cf 100644 --- a/src/block/inventory/ShulkerBoxInventory.php +++ b/src/block/inventory/ShulkerBoxInventoryWindow.php @@ -23,10 +23,6 @@ namespace pocketmine\block\inventory; -use pocketmine\block\BlockTypeIds; -use pocketmine\inventory\SimpleInventory; -use pocketmine\item\Item; -use pocketmine\item\ItemTypeIds; use pocketmine\network\mcpe\protocol\BlockEventPacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\world\Position; @@ -34,14 +30,7 @@ use pocketmine\world\sound\ShulkerBoxOpenSound; use pocketmine\world\sound\Sound; -class ShulkerBoxInventory extends SimpleInventory implements BlockInventory{ - use AnimatedBlockInventoryTrait; - - public function __construct(Position $holder){ - $this->holder = $holder; - parent::__construct(27); - } - +final class ShulkerBoxInventoryWindow extends AnimatedBlockInventoryWindow{ protected function getOpenSound() : Sound{ return new ShulkerBoxOpenSound(); } @@ -50,18 +39,8 @@ protected function getCloseSound() : Sound{ return new ShulkerBoxCloseSound(); } - public function canAddItem(Item $item) : bool{ - $blockTypeId = ItemTypeIds::toBlockTypeId($item->getTypeId()); - if($blockTypeId === BlockTypeIds::SHULKER_BOX || $blockTypeId === BlockTypeIds::DYED_SHULKER_BOX){ - return false; - } - return parent::canAddItem($item); - } - - protected function animateBlock(bool $isOpen) : void{ - $holder = $this->getHolder(); - + protected function animateBlock(Position $position, bool $isOpen) : void{ //event ID is always 1 for a chest - $holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0)); + $position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); } } diff --git a/src/block/inventory/SmithingTableInventory.php b/src/block/inventory/SmithingTableInventory.php deleted file mode 100644 index 2f67ac9d2dd..00000000000 --- a/src/block/inventory/SmithingTableInventory.php +++ /dev/null @@ -1,37 +0,0 @@ -holder = $holder; - parent::__construct(3); - } -} diff --git a/src/block/inventory/HopperInventory.php b/src/block/inventory/SmithingTableInventoryWindow.php similarity index 73% rename from src/block/inventory/HopperInventory.php rename to src/block/inventory/SmithingTableInventoryWindow.php index a20e9ae1a18..1d5bcf7ddef 100644 --- a/src/block/inventory/HopperInventory.php +++ b/src/block/inventory/SmithingTableInventoryWindow.php @@ -24,13 +24,12 @@ namespace pocketmine\block\inventory; use pocketmine\inventory\SimpleInventory; +use pocketmine\player\Player; +use pocketmine\player\TemporaryInventoryWindow; use pocketmine\world\Position; -class HopperInventory extends SimpleInventory implements BlockInventory{ - use BlockInventoryTrait; - - public function __construct(Position $holder, int $size = 5){ - $this->holder = $holder; - parent::__construct($size); +final class SmithingTableInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ + public function __construct(Player $viewer, Position $holder){ + parent::__construct($viewer, new SimpleInventory(3), $holder); } } diff --git a/src/block/inventory/StonecutterInventory.php b/src/block/inventory/StonecutterInventoryWindow.php similarity index 73% rename from src/block/inventory/StonecutterInventory.php rename to src/block/inventory/StonecutterInventoryWindow.php index 4ed644ff2c3..c37b3bb9416 100644 --- a/src/block/inventory/StonecutterInventory.php +++ b/src/block/inventory/StonecutterInventoryWindow.php @@ -24,16 +24,14 @@ namespace pocketmine\block\inventory; use pocketmine\inventory\SimpleInventory; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\player\Player; +use pocketmine\player\TemporaryInventoryWindow; use pocketmine\world\Position; -class StonecutterInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{ - use BlockInventoryTrait; - +final class StonecutterInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ public const SLOT_INPUT = 0; - public function __construct(Position $holder){ - $this->holder = $holder; - parent::__construct(1); + public function __construct(Player $viewer, Position $holder){ + parent::__construct($viewer, new SimpleInventory(1), $holder); } } diff --git a/src/block/tile/Barrel.php b/src/block/tile/Barrel.php index a7f3532142b..f642d6cac09 100644 --- a/src/block/tile/Barrel.php +++ b/src/block/tile/Barrel.php @@ -23,7 +23,8 @@ namespace pocketmine\block\tile; -use pocketmine\block\inventory\BarrelInventory; +use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\world\World; @@ -32,11 +33,11 @@ class Barrel extends Spawnable implements Container, Nameable{ use NameableTrait; use ContainerTrait; - protected BarrelInventory $inventory; + protected Inventory $inventory; public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new BarrelInventory($this->position); + $this->inventory = new SimpleInventory(27); } public function readSaveData(CompoundTag $nbt) : void{ @@ -56,11 +57,11 @@ public function close() : void{ } } - public function getInventory() : BarrelInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : BarrelInventory{ + public function getRealInventory() : Inventory{ return $this->inventory; } diff --git a/src/block/tile/BrewingStand.php b/src/block/tile/BrewingStand.php index c3a331e6c63..0e4289a8487 100644 --- a/src/block/tile/BrewingStand.php +++ b/src/block/tile/BrewingStand.php @@ -23,12 +23,13 @@ namespace pocketmine\block\tile; -use pocketmine\block\inventory\BrewingStandInventory; +use pocketmine\block\inventory\BrewingStandInventoryWindow; use pocketmine\crafting\BrewingRecipe; use pocketmine\event\block\BrewingFuelUseEvent; use pocketmine\event\block\BrewItemEvent; use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use pocketmine\math\Vector3; @@ -54,7 +55,7 @@ class BrewingStand extends Spawnable implements Container, Nameable{ private const TAG_REMAINING_FUEL_TIME = "Fuel"; //TAG_Byte private const TAG_REMAINING_FUEL_TIME_PE = "FuelAmount"; //TAG_Short - private BrewingStandInventory $inventory; + private Inventory $inventory; private int $brewTime = 0; private int $maxFuelTime = 0; @@ -62,7 +63,7 @@ class BrewingStand extends Spawnable implements Container, Nameable{ public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new BrewingStandInventory($this->position); + $this->inventory = new SimpleInventory(5); $this->inventory->getListeners()->add(CallbackInventoryListener::onAnyChange(static function(Inventory $unused) use ($world, $pos) : void{ $world->scheduleDelayedBlockUpdate($pos, 1); })); @@ -112,11 +113,11 @@ public function close() : void{ } } - public function getInventory() : BrewingStandInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : BrewingStandInventory{ + public function getRealInventory() : Inventory{ return $this->inventory; } @@ -132,7 +133,7 @@ private function checkFuel(Item $item) : void{ } $item->pop(); - $this->inventory->setItem(BrewingStandInventory::SLOT_FUEL, $item); + $this->inventory->setItem(BrewingStandInventoryWindow::SLOT_FUEL, $item); $this->maxFuelTime = $this->remainingFuelTime = $ev->getFuelTime(); } @@ -142,14 +143,14 @@ private function checkFuel(Item $item) : void{ * @phpstan-return array */ private function getBrewableRecipes() : array{ - $ingredient = $this->inventory->getItem(BrewingStandInventory::SLOT_INGREDIENT); + $ingredient = $this->inventory->getItem(BrewingStandInventoryWindow::SLOT_INGREDIENT); if($ingredient->isNull()){ return []; } $recipes = []; $craftingManager = $this->position->getWorld()->getServer()->getCraftingManager(); - foreach([BrewingStandInventory::SLOT_BOTTLE_LEFT, BrewingStandInventory::SLOT_BOTTLE_MIDDLE, BrewingStandInventory::SLOT_BOTTLE_RIGHT] as $slot){ + foreach([BrewingStandInventoryWindow::SLOT_BOTTLE_LEFT, BrewingStandInventoryWindow::SLOT_BOTTLE_MIDDLE, BrewingStandInventoryWindow::SLOT_BOTTLE_RIGHT] as $slot){ $input = $this->inventory->getItem($slot); if($input->isNull()){ continue; @@ -176,8 +177,8 @@ public function onUpdate() : bool{ $ret = false; - $fuel = $this->inventory->getItem(BrewingStandInventory::SLOT_FUEL); - $ingredient = $this->inventory->getItem(BrewingStandInventory::SLOT_INGREDIENT); + $fuel = $this->inventory->getItem(BrewingStandInventoryWindow::SLOT_FUEL); + $ingredient = $this->inventory->getItem(BrewingStandInventoryWindow::SLOT_INGREDIENT); $recipes = $this->getBrewableRecipes(); $canBrew = count($recipes) !== 0; @@ -219,7 +220,7 @@ public function onUpdate() : bool{ } $ingredient->pop(); - $this->inventory->setItem(BrewingStandInventory::SLOT_INGREDIENT, $ingredient); + $this->inventory->setItem(BrewingStandInventoryWindow::SLOT_INGREDIENT, $ingredient); $this->brewTime = 0; }else{ diff --git a/src/block/tile/Campfire.php b/src/block/tile/Campfire.php index ad4a193d7b8..89e42fa06d0 100644 --- a/src/block/tile/Campfire.php +++ b/src/block/tile/Campfire.php @@ -53,7 +53,7 @@ class Campfire extends Spawnable implements Container{ public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new CampfireInventory($this->position); + $this->inventory = new CampfireInventory(); $this->inventory->getListeners()->add(CallbackInventoryListener::onAnyChange( static function(Inventory $unused) use ($world, $pos) : void{ $block = $world->getBlock($pos); diff --git a/src/block/tile/Chest.php b/src/block/tile/Chest.php index 4f97eed234a..35e9425e1f6 100644 --- a/src/block/tile/Chest.php +++ b/src/block/tile/Chest.php @@ -23,8 +23,9 @@ namespace pocketmine\block\tile; -use pocketmine\block\inventory\ChestInventory; use pocketmine\block\inventory\DoubleChestInventory; +use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; @@ -44,7 +45,7 @@ class Chest extends Spawnable implements Container, Nameable{ public const TAG_PAIRZ = "pairz"; public const TAG_PAIR_LEAD = "pairlead"; - protected ChestInventory $inventory; + protected Inventory $inventory; protected ?DoubleChestInventory $doubleInventory = null; private ?int $pairX = null; @@ -52,7 +53,7 @@ class Chest extends Spawnable implements Container, Nameable{ public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new ChestInventory($this->position); + $this->inventory = new SimpleInventory(27); } public function readSaveData(CompoundTag $nbt) : void{ @@ -114,14 +115,14 @@ protected function onBlockDestroyedHook() : void{ $this->containerTraitBlockDestroyedHook(); } - public function getInventory() : ChestInventory|DoubleChestInventory{ + public function getInventory() : Inventory|DoubleChestInventory{ if($this->isPaired() && $this->doubleInventory === null){ $this->checkPairing(); } return $this->doubleInventory instanceof DoubleChestInventory ? $this->doubleInventory : $this->inventory; } - public function getRealInventory() : ChestInventory{ + public function getRealInventory() : Inventory{ return $this->inventory; } diff --git a/src/block/tile/Furnace.php b/src/block/tile/Furnace.php index a706a827e74..f744e6b41f8 100644 --- a/src/block/tile/Furnace.php +++ b/src/block/tile/Furnace.php @@ -24,13 +24,14 @@ namespace pocketmine\block\tile; use pocketmine\block\Furnace as BlockFurnace; -use pocketmine\block\inventory\FurnaceInventory; +use pocketmine\block\inventory\FurnaceInventoryWindow; use pocketmine\crafting\FurnaceRecipe; use pocketmine\crafting\FurnaceType; use pocketmine\event\inventory\FurnaceBurnEvent; use pocketmine\event\inventory\FurnaceSmeltEvent; use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; @@ -48,14 +49,14 @@ abstract class Furnace extends Spawnable implements Container, Nameable{ public const TAG_COOK_TIME = "CookTime"; public const TAG_MAX_TIME = "MaxTime"; - protected FurnaceInventory $inventory; + protected Inventory $inventory; private int $remainingFuelTime = 0; private int $cookTime = 0; private int $maxFuelTime = 0; public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new FurnaceInventory($this->position, $this->getFurnaceType()); + $this->inventory = new SimpleInventory(3); $this->inventory->getListeners()->add(CallbackInventoryListener::onAnyChange( static function(Inventory $unused) use ($world, $pos) : void{ $world->scheduleDelayedBlockUpdate($pos, 1); @@ -104,11 +105,11 @@ public function close() : void{ } } - public function getInventory() : FurnaceInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : FurnaceInventory{ + public function getRealInventory() : Inventory{ return $this->getInventory(); } @@ -123,7 +124,7 @@ protected function checkFuel(Item $fuel) : void{ $this->onStartSmelting(); if($this->remainingFuelTime > 0 && $ev->isBurning()){ - $this->inventory->setFuel($fuel->getFuelResidue()); + $this->inventory->setItem(FurnaceInventoryWindow::SLOT_FUEL, $fuel->getFuelResidue()); } } @@ -159,9 +160,9 @@ public function onUpdate() : bool{ $ret = false; - $fuel = $this->inventory->getFuel(); - $raw = $this->inventory->getSmelting(); - $product = $this->inventory->getResult(); + $fuel = $this->inventory->getItem(FurnaceInventoryWindow::SLOT_FUEL); + $raw = $this->inventory->getItem(FurnaceInventoryWindow::SLOT_INPUT); + $product = $this->inventory->getItem(FurnaceInventoryWindow::SLOT_RESULT); $furnaceType = $this->getFurnaceType(); $smelt = $this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($furnaceType)->match($raw); @@ -184,9 +185,9 @@ public function onUpdate() : bool{ $ev->call(); if(!$ev->isCancelled()){ - $this->inventory->setResult($ev->getResult()); + $this->inventory->setItem(FurnaceInventoryWindow::SLOT_RESULT, $ev->getResult()); $raw->pop(); - $this->inventory->setSmelting($raw); + $this->inventory->setItem(FurnaceInventoryWindow::SLOT_INPUT, $raw); } $this->cookTime -= $furnaceType->getCookDurationTicks(); diff --git a/src/block/tile/Hopper.php b/src/block/tile/Hopper.php index 5c39bc2bd5a..8e43fa15b73 100644 --- a/src/block/tile/Hopper.php +++ b/src/block/tile/Hopper.php @@ -23,7 +23,8 @@ namespace pocketmine\block\tile; -use pocketmine\block\inventory\HopperInventory; +use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\world\World; @@ -35,12 +36,12 @@ class Hopper extends Spawnable implements Container, Nameable{ private const TAG_TRANSFER_COOLDOWN = "TransferCooldown"; - private HopperInventory $inventory; + private Inventory $inventory; private int $transferCooldown = 0; public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new HopperInventory($this->position); + $this->inventory = new SimpleInventory(5); } public function readSaveData(CompoundTag $nbt) : void{ @@ -69,11 +70,11 @@ public function getDefaultName() : string{ return "Hopper"; } - public function getInventory() : HopperInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : HopperInventory{ + public function getRealInventory() : Inventory{ return $this->inventory; } } diff --git a/src/block/tile/ShulkerBox.php b/src/block/tile/ShulkerBox.php index a30b75c4ea8..1c5b74a0806 100644 --- a/src/block/tile/ShulkerBox.php +++ b/src/block/tile/ShulkerBox.php @@ -23,8 +23,13 @@ namespace pocketmine\block\tile; -use pocketmine\block\inventory\ShulkerBoxInventory; +use pocketmine\block\BlockTypeIds; +use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; +use pocketmine\inventory\transaction\action\validator\CallbackSlotValidator; +use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; +use pocketmine\item\ItemTypeIds; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; @@ -40,11 +45,20 @@ class ShulkerBox extends Spawnable implements Container, Nameable{ protected int $facing = Facing::NORTH; - protected ShulkerBoxInventory $inventory; + protected Inventory $inventory; public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new ShulkerBoxInventory($this->position); + $this->inventory = new SimpleInventory(27); + + $this->inventory->getSlotValidators()->add(new CallbackSlotValidator(static function(Inventory $_, Item $item) : ?TransactionValidationException{ //remaining params not needed + $blockTypeId = ItemTypeIds::toBlockTypeId($item->getTypeId()); + if($blockTypeId === BlockTypeIds::SHULKER_BOX || $blockTypeId === BlockTypeIds::DYED_SHULKER_BOX){ + return new TransactionValidationException("Shulker box inventory cannot contain shulker boxes"); + } + + return null; + })); } public function readSaveData(CompoundTag $nbt) : void{ @@ -93,11 +107,11 @@ public function setFacing(int $facing) : void{ $this->facing = $facing; } - public function getInventory() : ShulkerBoxInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : ShulkerBoxInventory{ + public function getRealInventory() : Inventory{ return $this->inventory; } diff --git a/src/block/utils/BrewingStandSlot.php b/src/block/utils/BrewingStandSlot.php index c7d74d8da03..edf8374fa3c 100644 --- a/src/block/utils/BrewingStandSlot.php +++ b/src/block/utils/BrewingStandSlot.php @@ -23,7 +23,7 @@ namespace pocketmine\block\utils; -use pocketmine\block\inventory\BrewingStandInventory; +use pocketmine\block\inventory\BrewingStandInventoryWindow; enum BrewingStandSlot{ case EAST; @@ -35,9 +35,9 @@ enum BrewingStandSlot{ */ public function getSlotNumber() : int{ return match($this){ - self::EAST => BrewingStandInventory::SLOT_BOTTLE_LEFT, - self::NORTHWEST => BrewingStandInventory::SLOT_BOTTLE_MIDDLE, - self::SOUTHWEST => BrewingStandInventory::SLOT_BOTTLE_RIGHT + self::EAST => BrewingStandInventoryWindow::SLOT_BOTTLE_LEFT, + self::NORTHWEST => BrewingStandInventoryWindow::SLOT_BOTTLE_MIDDLE, + self::SOUTHWEST => BrewingStandInventoryWindow::SLOT_BOTTLE_RIGHT }; } } diff --git a/src/crafting/CraftingGrid.php b/src/crafting/CraftingGrid.php index a41b5e3a73d..47cca94baed 100644 --- a/src/crafting/CraftingGrid.php +++ b/src/crafting/CraftingGrid.php @@ -29,7 +29,7 @@ use function min; use const PHP_INT_MAX; -abstract class CraftingGrid extends SimpleInventory{ +class CraftingGrid extends SimpleInventory{ public const SIZE_SMALL = 2; public const SIZE_BIG = 3; diff --git a/src/entity/Human.php b/src/entity/Human.php index 8b446660a64..5f7f577027e 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -35,9 +35,7 @@ use pocketmine\inventory\Hotbar; use pocketmine\inventory\Inventory; use pocketmine\inventory\InventoryHolder; -use pocketmine\inventory\PlayerEnderInventory; -use pocketmine\inventory\PlayerInventory; -use pocketmine\inventory\PlayerOffHandInventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\item\enchantment\EnchantingHelper; use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\Item; @@ -102,9 +100,9 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ public function getNetworkTypeId() : string{ return EntityIds::PLAYER; } protected Hotbar $hotbar; - protected PlayerInventory $inventory; - protected PlayerOffHandInventory $offHandInventory; - protected PlayerEnderInventory $enderInventory; + protected Inventory $inventory; + protected Inventory $offHandInventory; + protected Inventory $enderInventory; protected UuidInterface $uuid; @@ -234,13 +232,13 @@ public function getHotbar() : Hotbar{ return $this->hotbar; } - public function getInventory() : PlayerInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getOffHandInventory() : PlayerOffHandInventory{ return $this->offHandInventory; } + public function getOffHandInventory() : Inventory{ return $this->offHandInventory; } - public function getEnderInventory() : PlayerEnderInventory{ + public function getEnderInventory() : Inventory{ return $this->enderInventory; } @@ -271,7 +269,7 @@ protected function initEntity(CompoundTag $nbt) : void{ $this->hungerManager = new HungerManager($this); $this->xpManager = new ExperienceManager($this); - $this->inventory = new PlayerInventory($this); + $this->inventory = new SimpleInventory(36); $this->hotbar = new Hotbar($this->inventory); $syncHeldItem = fn() => NetworkBroadcastUtils::broadcastEntityEvent( @@ -290,8 +288,8 @@ function(Inventory $unused, array $oldItems) use ($syncHeldItem) : void{ } } )); - $this->offHandInventory = new PlayerOffHandInventory($this); - $this->enderInventory = new PlayerEnderInventory($this); + $this->offHandInventory = new SimpleInventory(1); + $this->enderInventory = new SimpleInventory(27); $this->initHumanData($nbt); $inventoryTag = $nbt->getListTag(self::TAG_INVENTORY); @@ -335,6 +333,7 @@ function(Inventory $unused, array $oldItems) use ($syncHeldItem) : void{ } $this->hotbar->setSelectedIndex($nbt->getInt(self::TAG_SELECTED_INVENTORY_SLOT, 0)); + //TODO: cyclic reference $this->hotbar->getSelectedIndexChangeListeners()->add(fn() => NetworkBroadcastUtils::broadcastEntityEvent( $this->getViewers(), fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobMainHandItemChange($recipients, $this) @@ -546,9 +545,6 @@ protected function onDispose() : void{ protected function destroyCycles() : void{ unset( - $this->inventory, - $this->offHandInventory, - $this->enderInventory, $this->hungerManager, $this->xpManager ); diff --git a/src/entity/Living.php b/src/entity/Living.php index 81f46424f17..faed09ef157 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -145,7 +145,7 @@ protected function initEntity(CompoundTag $nbt) : void{ $this->effectManager->getEffectAddHooks()->add(function() : void{ $this->networkPropertiesDirty = true; }); $this->effectManager->getEffectRemoveHooks()->add(function() : void{ $this->networkPropertiesDirty = true; }); - $this->armorInventory = new ArmorInventory($this); + $this->armorInventory = new ArmorInventory(); //TODO: load/save armor inventory contents $this->armorInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(fn() => NetworkBroadcastUtils::broadcastEntityEvent( $this->getViewers(), @@ -928,7 +928,6 @@ protected function onDispose() : void{ protected function destroyCycles() : void{ unset( - $this->armorInventory, $this->effectManager ); parent::destroyCycles(); diff --git a/src/event/inventory/InventoryCloseEvent.php b/src/event/inventory/InventoryCloseEvent.php index 4401a71264b..be5415e27f9 100644 --- a/src/event/inventory/InventoryCloseEvent.php +++ b/src/event/inventory/InventoryCloseEvent.php @@ -23,12 +23,12 @@ namespace pocketmine\event\inventory; -use pocketmine\inventory\Inventory; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; class InventoryCloseEvent extends InventoryEvent{ public function __construct( - Inventory $inventory, + InventoryWindow $inventory, private Player $who ){ parent::__construct($inventory); diff --git a/src/event/inventory/InventoryEvent.php b/src/event/inventory/InventoryEvent.php index 4202946791b..30250505ba8 100644 --- a/src/event/inventory/InventoryEvent.php +++ b/src/event/inventory/InventoryEvent.php @@ -27,15 +27,15 @@ namespace pocketmine\event\inventory; use pocketmine\event\Event; -use pocketmine\inventory\Inventory; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; abstract class InventoryEvent extends Event{ public function __construct( - protected Inventory $inventory + protected InventoryWindow $inventory ){} - public function getInventory() : Inventory{ + public function getInventory() : InventoryWindow{ return $this->inventory; } @@ -43,6 +43,6 @@ public function getInventory() : Inventory{ * @return Player[] */ public function getViewers() : array{ - return $this->inventory->getViewers(); + return $this->inventory->getInventory()->getViewers(); } } diff --git a/src/event/inventory/InventoryOpenEvent.php b/src/event/inventory/InventoryOpenEvent.php index f45056bf458..44275b671b3 100644 --- a/src/event/inventory/InventoryOpenEvent.php +++ b/src/event/inventory/InventoryOpenEvent.php @@ -25,14 +25,14 @@ use pocketmine\event\Cancellable; use pocketmine\event\CancellableTrait; -use pocketmine\inventory\Inventory; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; class InventoryOpenEvent extends InventoryEvent implements Cancellable{ use CancellableTrait; public function __construct( - Inventory $inventory, + InventoryWindow $inventory, private Player $who ){ parent::__construct($inventory); diff --git a/src/event/player/PlayerEnchantingOptionsRequestEvent.php b/src/event/player/PlayerEnchantingOptionsRequestEvent.php index 833185f7605..e2918352097 100644 --- a/src/event/player/PlayerEnchantingOptionsRequestEvent.php +++ b/src/event/player/PlayerEnchantingOptionsRequestEvent.php @@ -23,10 +23,9 @@ namespace pocketmine\event\player; -use pocketmine\block\inventory\EnchantInventory; +use pocketmine\block\inventory\EnchantingTableInventoryWindow; use pocketmine\event\Cancellable; use pocketmine\event\CancellableTrait; -use pocketmine\event\Event; use pocketmine\item\enchantment\EnchantingOption; use pocketmine\player\Player; use pocketmine\utils\Utils; @@ -44,13 +43,13 @@ class PlayerEnchantingOptionsRequestEvent extends PlayerEvent implements Cancell */ public function __construct( Player $player, - private readonly EnchantInventory $inventory, + private readonly EnchantingTableInventoryWindow $inventory, private array $options ){ $this->player = $player; } - public function getInventory() : EnchantInventory{ + public function getInventory() : EnchantingTableInventoryWindow{ return $this->inventory; } diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index 8591cc65bf3..6985f460a61 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -24,7 +24,6 @@ namespace pocketmine\inventory; use pocketmine\block\BlockTypeIds; -use pocketmine\entity\Living; use pocketmine\inventory\transaction\action\validator\CallbackSlotValidator; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Armor; @@ -37,18 +36,12 @@ class ArmorInventory extends SimpleInventory{ public const SLOT_LEGS = 2; public const SLOT_FEET = 3; - public function __construct( - protected Living $holder - ){ + public function __construct(){ parent::__construct(4); $this->validators->add(new CallbackSlotValidator(self::validate(...))); } - public function getHolder() : Living{ - return $this->holder; - } - public function getHelmet() : Item{ return $this->getItem(self::SLOT_HEAD); } diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 14ed270c543..e307b5b85a4 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -107,15 +107,6 @@ public function setContents(array $items) : void{ $this->onContentChange($oldContents); } - /** - * Helper for utility functions which search the inventory. - * TODO: make this abstract instead of providing a slow default implementation (BC break) - */ - protected function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ - $item = $this->getItem($slot); - return $item->equals($test, true, $checkTags) ? $item->getCount() : 0; - } - public function contains(Item $item) : bool{ $count = max(1, $item->getCount()); $checkTags = $item->hasNamedTag(); @@ -345,7 +336,7 @@ public function getViewers() : array{ */ public function removeAllViewers() : void{ foreach($this->viewers as $hash => $viewer){ - if($viewer->getCurrentWindow() === $this){ //this might not be the case for the player's own inventory + if($viewer->getCurrentWindow()?->getInventory() === $this){ //this might not be the case for the player's own inventory $viewer->removeCurrentWindow(); } unset($this->viewers[$hash]); diff --git a/src/inventory/DelegateInventory.php b/src/inventory/DelegateInventory.php index a211732cf46..5bf9b908ae0 100644 --- a/src/inventory/DelegateInventory.php +++ b/src/inventory/DelegateInventory.php @@ -85,6 +85,10 @@ protected function internalSetContents(array $items) : void{ $this->backingInventory->setContents($items); } + public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ + return $this->backingInventory->getMatchingItemCount($slot, $test, $checkTags); + } + public function isSlotEmpty(int $index) : bool{ return $this->backingInventory->isSlotEmpty($index); } diff --git a/src/inventory/Inventory.php b/src/inventory/Inventory.php index 5c81d7d9c86..de460ca2927 100644 --- a/src/inventory/Inventory.php +++ b/src/inventory/Inventory.php @@ -98,6 +98,13 @@ public function canAddItem(Item $item) : bool; */ public function getAddableItemQuantity(Item $item) : int; + /** + * Returns the number of items in the inventory that match the given item. + * + * @param bool $checkTags If true, the NBT of the items will also be checked and must be the same to be counted. + */ + public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int; + /** * Returns whether the total amount of matching items is at least the stack size of the given item. Multiple stacks * of the same item are added together. @@ -179,6 +186,11 @@ public function swap(int $slot1, int $slot2) : void; */ public function getViewers() : array; + /** + * Tells all Players viewing this inventory to stop viewing it and discard associated windows. + */ + public function removeAllViewers() : void; + /** * Called when a player opens this inventory. */ diff --git a/src/inventory/PlayerCraftingInventory.php b/src/inventory/PlayerCraftingInventory.php deleted file mode 100644 index 75752c9e716..00000000000 --- a/src/inventory/PlayerCraftingInventory.php +++ /dev/null @@ -1,36 +0,0 @@ -holder; } -} diff --git a/src/inventory/PlayerCursorInventory.php b/src/inventory/PlayerCursorInventory.php deleted file mode 100644 index 30f9e3aec25..00000000000 --- a/src/inventory/PlayerCursorInventory.php +++ /dev/null @@ -1,38 +0,0 @@ -holder; - } -} diff --git a/src/inventory/PlayerEnderInventory.php b/src/inventory/PlayerEnderInventory.php deleted file mode 100644 index c10c42b12ee..00000000000 --- a/src/inventory/PlayerEnderInventory.php +++ /dev/null @@ -1,37 +0,0 @@ -holder; } -} diff --git a/src/inventory/PlayerInventory.php b/src/inventory/PlayerInventory.php deleted file mode 100644 index a2e9e92521e..00000000000 --- a/src/inventory/PlayerInventory.php +++ /dev/null @@ -1,40 +0,0 @@ -holder = $player; - parent::__construct(36); - } - - public function getHolder() : Human{ - return $this->holder; - } -} diff --git a/src/inventory/PlayerOffHandInventory.php b/src/inventory/PlayerOffHandInventory.php deleted file mode 100644 index 127b09f98a6..00000000000 --- a/src/inventory/PlayerOffHandInventory.php +++ /dev/null @@ -1,37 +0,0 @@ -holder = $player; - parent::__construct(1); - } - - public function getHolder() : Human{ return $this->holder; } -} diff --git a/src/inventory/SimpleInventory.php b/src/inventory/SimpleInventory.php index 4b44326fa17..a6a3393227b 100644 --- a/src/inventory/SimpleInventory.php +++ b/src/inventory/SimpleInventory.php @@ -84,7 +84,7 @@ protected function internalSetContents(array $items) : void{ } } - protected function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ + public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ $slotItem = $this->slots[$slot]; return $slotItem !== null && $slotItem->equals($test, true, $checkTags) ? $slotItem->getCount() : 0; } diff --git a/src/inventory/transaction/InventoryTransaction.php b/src/inventory/transaction/InventoryTransaction.php index 2ca00f910c2..a994a156f95 100644 --- a/src/inventory/transaction/InventoryTransaction.php +++ b/src/inventory/transaction/InventoryTransaction.php @@ -28,6 +28,7 @@ use pocketmine\inventory\transaction\action\InventoryAction; use pocketmine\inventory\transaction\action\SlotChangeAction; use pocketmine\item\Item; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; use function array_keys; use function array_values; @@ -57,8 +58,8 @@ class InventoryTransaction{ protected bool $hasExecuted = false; - /** @var Inventory[] */ - protected array $inventories = []; + /** @var InventoryWindow[] */ + protected array $inventoryWindows = []; /** @var InventoryAction[] */ protected array $actions = []; @@ -80,10 +81,10 @@ public function getSource() : Player{ } /** - * @return Inventory[] + * @return InventoryWindow[] */ - public function getInventories() : array{ - return $this->inventories; + public function getInventoryWindows() : array{ + return $this->inventoryWindows; } /** @@ -124,9 +125,9 @@ private function shuffleActions() : void{ * @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions * involving inventories. */ - public function addInventory(Inventory $inventory) : void{ - if(!isset($this->inventories[$hash = spl_object_id($inventory)])){ - $this->inventories[$hash] = $inventory; + public function addInventoryWindow(InventoryWindow $inventoryWindow) : void{ + if(!isset($this->inventoryWindows[$hash = spl_object_id($inventoryWindow)])){ + $this->inventoryWindows[$hash] = $inventoryWindow; } } @@ -190,15 +191,15 @@ protected function matchItems(array &$needItems, array &$haveItems) : void{ protected function squashDuplicateSlotChanges() : void{ /** @var SlotChangeAction[][] $slotChanges */ $slotChanges = []; - /** @var Inventory[] $inventories */ + /** @var InventoryWindow[] $inventories */ $inventories = []; /** @var int[] $slots */ $slots = []; foreach($this->actions as $key => $action){ if($action instanceof SlotChangeAction){ - $slotChanges[$h = (spl_object_hash($action->getInventory()) . "@" . $action->getSlot())][] = $action; - $inventories[$h] = $action->getInventory(); + $slotChanges[$h = (spl_object_hash($action->getInventoryWindow()) . "@" . $action->getSlot())][] = $action; + $inventories[$h] = $action->getInventoryWindow(); $slots[$h] = $action->getSlot(); } } @@ -208,10 +209,11 @@ protected function squashDuplicateSlotChanges() : void{ continue; } - $inventory = $inventories[$hash]; + $window = $inventories[$hash]; + $inventory = $window->getInventory(); $slot = $slots[$hash]; if(!$inventory->slotExists($slot)){ //this can get hit for crafting tables because the validation happens after this compaction - throw new TransactionValidationException("Slot $slot does not exist in inventory " . get_class($inventory)); + throw new TransactionValidationException("Slot $slot does not exist in inventory window " . get_class($window)); } $sourceItem = $inventory->getItem($slot); @@ -226,7 +228,7 @@ protected function squashDuplicateSlotChanges() : void{ if(!$targetItem->equalsExact($sourceItem)){ //sometimes we get actions on the crafting grid whose source and target items are the same, so dump them - $this->addAction(new SlotChangeAction($inventory, $slot, $sourceItem, $targetItem)); + $this->addAction(new SlotChangeAction($window, $slot, $sourceItem, $targetItem)); } } } diff --git a/src/inventory/transaction/TransactionBuilderInventory.php b/src/inventory/transaction/SlotChangeActionBuilder.php similarity index 70% rename from src/inventory/transaction/TransactionBuilderInventory.php rename to src/inventory/transaction/SlotChangeActionBuilder.php index 95b6c4a147c..90fe2111aac 100644 --- a/src/inventory/transaction/TransactionBuilderInventory.php +++ b/src/inventory/transaction/SlotChangeActionBuilder.php @@ -28,6 +28,7 @@ use pocketmine\inventory\transaction\action\SlotChangeAction; use pocketmine\item\Item; use pocketmine\item\VanillaItems; +use pocketmine\player\InventoryWindow; /** * This class facilitates generating SlotChangeActions to build an inventory transaction. @@ -35,7 +36,7 @@ * This allows you to use the normal Inventory API methods like addItem() and so on to build a transaction, without * modifying the original inventory. */ -final class TransactionBuilderInventory extends BaseInventory{ +final class SlotChangeActionBuilder extends BaseInventory{ /** * @var \SplFixedArray|(Item|null)[] @@ -44,14 +45,14 @@ final class TransactionBuilderInventory extends BaseInventory{ private \SplFixedArray $changedSlots; public function __construct( - private Inventory $actualInventory + private InventoryWindow $inventoryWindow ){ parent::__construct(); - $this->changedSlots = new \SplFixedArray($this->actualInventory->getSize()); + $this->changedSlots = new \SplFixedArray($this->inventoryWindow->getInventory()->getSize()); } - public function getActualInventory() : Inventory{ - return $this->actualInventory; + public function getInventoryWindow() : InventoryWindow{ + return $this->inventoryWindow; } protected function internalSetContents(array $items) : void{ @@ -65,21 +66,21 @@ protected function internalSetContents(array $items) : void{ } protected function internalSetItem(int $index, Item $item) : void{ - if(!$item->equalsExact($this->actualInventory->getItem($index))){ + if(!$item->equalsExact($this->inventoryWindow->getInventory()->getItem($index))){ $this->changedSlots[$index] = $item->isNull() ? VanillaItems::AIR() : clone $item; } } public function getSize() : int{ - return $this->actualInventory->getSize(); + return $this->inventoryWindow->getInventory()->getSize(); } public function getItem(int $index) : Item{ - return $this->changedSlots[$index] !== null ? clone $this->changedSlots[$index] : $this->actualInventory->getItem($index); + return $this->changedSlots[$index] !== null ? clone $this->changedSlots[$index] : $this->inventoryWindow->getInventory()->getItem($index); } public function getContents(bool $includeEmpty = false) : array{ - $contents = $this->actualInventory->getContents($includeEmpty); + $contents = $this->inventoryWindow->getInventory()->getContents($includeEmpty); foreach($this->changedSlots as $index => $item){ if($item !== null){ if($includeEmpty || !$item->isNull()){ @@ -92,6 +93,14 @@ public function getContents(bool $includeEmpty = false) : array{ return $contents; } + public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ + $slotItem = $this->changedSlots[$slot] ?? null; + if($slotItem !== null){ + return $slotItem->equals($test, true, $checkTags) ? $slotItem->getCount() : 0; + } + return $this->inventoryWindow->getInventory()->getMatchingItemCount($slot, $test, $checkTags); + } + /** * @return SlotChangeAction[] */ @@ -99,9 +108,9 @@ public function generateActions() : array{ $result = []; foreach($this->changedSlots as $index => $newItem){ if($newItem !== null){ - $oldItem = $this->actualInventory->getItem($index); + $oldItem = $this->inventoryWindow->getInventory()->getItem($index); if(!$newItem->equalsExact($oldItem)){ - $result[] = new SlotChangeAction($this->actualInventory, $index, $oldItem, $newItem); + $result[] = new SlotChangeAction($this->inventoryWindow, $index, $oldItem, $newItem); } } } diff --git a/src/inventory/transaction/TransactionBuilder.php b/src/inventory/transaction/TransactionBuilder.php index f56b2aaa160..6232a44a4d5 100644 --- a/src/inventory/transaction/TransactionBuilder.php +++ b/src/inventory/transaction/TransactionBuilder.php @@ -23,13 +23,13 @@ namespace pocketmine\inventory\transaction; -use pocketmine\inventory\Inventory; use pocketmine\inventory\transaction\action\InventoryAction; +use pocketmine\player\InventoryWindow; use function spl_object_id; final class TransactionBuilder{ - /** @var TransactionBuilderInventory[] */ + /** @var SlotChangeActionBuilder[] */ private array $inventories = []; /** @var InventoryAction[] */ @@ -39,9 +39,9 @@ public function addAction(InventoryAction $action) : void{ $this->extraActions[spl_object_id($action)] = $action; } - public function getInventory(Inventory $inventory) : TransactionBuilderInventory{ + public function getActionBuilder(InventoryWindow $inventory) : SlotChangeActionBuilder{ $id = spl_object_id($inventory); - return $this->inventories[$id] ??= new TransactionBuilderInventory($inventory); + return $this->inventories[$id] ??= new SlotChangeActionBuilder($inventory); } /** diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index 68c3dba1b32..2be05100686 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -28,6 +28,7 @@ use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; /** @@ -35,7 +36,7 @@ */ class SlotChangeAction extends InventoryAction{ public function __construct( - protected Inventory $inventory, + protected InventoryWindow $inventoryWindow, private int $inventorySlot, Item $sourceItem, Item $targetItem @@ -44,10 +45,10 @@ public function __construct( } /** - * Returns the inventory involved in this action. + * Returns the inventory window involved in this action. */ - public function getInventory() : Inventory{ - return $this->inventory; + public function getInventoryWindow() : InventoryWindow{ + return $this->inventoryWindow; } /** @@ -63,21 +64,22 @@ public function getSlot() : int{ * @throws TransactionValidationException */ public function validate(Player $source) : void{ - if(!$this->inventory->slotExists($this->inventorySlot)){ + $inventory = $this->inventoryWindow->getInventory(); + if(!$inventory->slotExists($this->inventorySlot)){ throw new TransactionValidationException("Slot does not exist"); } - if(!$this->inventory->getItem($this->inventorySlot)->equalsExact($this->sourceItem)){ + if(!$inventory->getItem($this->inventorySlot)->equalsExact($this->sourceItem)){ throw new TransactionValidationException("Slot does not contain expected original item"); } if($this->targetItem->getCount() > $this->targetItem->getMaxStackSize()){ throw new TransactionValidationException("Target item exceeds item type max stack size"); } - if($this->targetItem->getCount() > $this->inventory->getMaxStackSize()){ + if($this->targetItem->getCount() > $inventory->getMaxStackSize()){ throw new TransactionValidationException("Target item exceeds inventory max stack size"); } - if($this->inventory instanceof SlotValidatedInventory && !$this->targetItem->isNull()){ - foreach($this->inventory->getSlotValidators() as $validator){ - $ret = $validator->validate($this->inventory, $this->targetItem, $this->inventorySlot); + if($inventory instanceof SlotValidatedInventory && !$this->targetItem->isNull()){ + foreach($inventory->getSlotValidators() as $validator){ + $ret = $validator->validate($inventory, $this->targetItem, $this->inventorySlot); if($ret !== null){ throw new TransactionValidationException("Target item is not accepted by the inventory at slot #" . $this->inventorySlot . ": " . $ret->getMessage(), 0, $ret); } @@ -89,13 +91,13 @@ public function validate(Player $source) : void{ * Adds this action's target inventory to the transaction's inventory list. */ public function onAddToTransaction(InventoryTransaction $transaction) : void{ - $transaction->addInventory($this->inventory); + $transaction->addInventoryWindow($this->inventoryWindow); } /** * Sets the item into the target inventory. */ public function execute(Player $source) : void{ - $this->inventory->setItem($this->inventorySlot, $this->targetItem); + $this->inventoryWindow->getInventory()->setItem($this->inventorySlot, $this->targetItem); } } diff --git a/src/network/mcpe/ComplexInventoryMapEntry.php b/src/network/mcpe/ComplexWindowMapEntry.php similarity index 88% rename from src/network/mcpe/ComplexInventoryMapEntry.php rename to src/network/mcpe/ComplexWindowMapEntry.php index dfd3e999a47..fa457e14f68 100644 --- a/src/network/mcpe/ComplexInventoryMapEntry.php +++ b/src/network/mcpe/ComplexWindowMapEntry.php @@ -23,9 +23,9 @@ namespace pocketmine\network\mcpe; -use pocketmine\inventory\Inventory; +use pocketmine\player\InventoryWindow; -final class ComplexInventoryMapEntry{ +final class ComplexWindowMapEntry{ /** * @var int[] @@ -38,7 +38,7 @@ final class ComplexInventoryMapEntry{ * @phpstan-param array $slotMap */ public function __construct( - private Inventory $inventory, + private InventoryWindow $inventory, private array $slotMap ){ foreach($slotMap as $slot => $index){ @@ -46,7 +46,7 @@ public function __construct( } } - public function getInventory() : Inventory{ return $this->inventory; } + public function getWindow() : InventoryWindow{ return $this->inventory; } /** * @return int[] diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index a57bf20c45b..91be5336207 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -23,17 +23,17 @@ namespace pocketmine\network\mcpe; -use pocketmine\block\inventory\AnvilInventory; -use pocketmine\block\inventory\BlockInventory; -use pocketmine\block\inventory\BrewingStandInventory; -use pocketmine\block\inventory\CartographyTableInventory; -use pocketmine\block\inventory\CraftingTableInventory; -use pocketmine\block\inventory\EnchantInventory; -use pocketmine\block\inventory\FurnaceInventory; -use pocketmine\block\inventory\HopperInventory; -use pocketmine\block\inventory\LoomInventory; -use pocketmine\block\inventory\SmithingTableInventory; -use pocketmine\block\inventory\StonecutterInventory; +use pocketmine\block\inventory\AnvilInventoryWindow; +use pocketmine\block\inventory\BlockInventoryWindow; +use pocketmine\block\inventory\BrewingStandInventoryWindow; +use pocketmine\block\inventory\CartographyTableInventoryWindow; +use pocketmine\block\inventory\CraftingTableInventoryWindow; +use pocketmine\block\inventory\EnchantingTableInventoryWindow; +use pocketmine\block\inventory\FurnaceInventoryWindow; +use pocketmine\block\inventory\HopperInventoryWindow; +use pocketmine\block\inventory\LoomInventoryWindow; +use pocketmine\block\inventory\SmithingTableInventoryWindow; +use pocketmine\block\inventory\StonecutterInventoryWindow; use pocketmine\crafting\FurnaceType; use pocketmine\data\bedrock\EnchantmentIdMap; use pocketmine\inventory\Inventory; @@ -63,7 +63,9 @@ use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset; use pocketmine\network\mcpe\protocol\types\inventory\WindowTypes; use pocketmine\network\PacketHandlingException; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; +use pocketmine\player\PlayerInventoryWindow; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\ObjectSet; use function array_fill_keys; @@ -78,27 +80,27 @@ use function spl_object_id; /** - * @phpstan-type ContainerOpenClosure \Closure(int $id, Inventory $inventory) : (list|null) + * @phpstan-type ContainerOpenClosure \Closure(int $id, InventoryWindow $window) : (list|null) */ class InventoryManager implements InventoryListener{ /** * @var InventoryManagerEntry[] spl_object_id(Inventory) => InventoryManagerEntry * @phpstan-var array */ - private array $inventories = []; + private array $entries = []; /** - * @var Inventory[] network window ID => Inventory - * @phpstan-var array + * @var InventoryWindow[] network window ID => InventoryWindow + * @phpstan-var array */ - private array $networkIdToInventoryMap = []; + private array $networkIdToWindowMap = []; /** - * @var ComplexInventoryMapEntry[] net slot ID => ComplexWindowMapEntry - * @phpstan-var array + * @var ComplexWindowMapEntry[] net slot ID => ComplexWindowMapEntry + * @phpstan-var array */ - private array $complexSlotToInventoryMap = []; + private array $complexSlotToWindowMap = []; - private int $lastInventoryNetworkId = ContainerIds::FIRST; + private int $lastWindowNetworkId = ContainerIds::FIRST; private int $currentWindowType = WindowTypes::CONTAINER; private int $clientSelectedHotbarSlot = -1; @@ -128,34 +130,48 @@ public function __construct( $this->containerOpenCallbacks = new ObjectSet(); $this->containerOpenCallbacks->add(self::createContainerOpen(...)); - $this->add(ContainerIds::INVENTORY, $this->player->getInventory()); - $this->add(ContainerIds::OFFHAND, $this->player->getOffHandInventory()); - $this->add(ContainerIds::ARMOR, $this->player->getArmorInventory()); - $this->addComplex(UIInventorySlotOffset::CURSOR, $this->player->getCursorInventory()); - $this->addComplex(UIInventorySlotOffset::CRAFTING2X2_INPUT, $this->player->getCraftingGrid()); + foreach($this->player->getPermanentWindows() as $window){ + match($window->getType()){ + PlayerInventoryWindow::TYPE_INVENTORY => $this->add(ContainerIds::INVENTORY, $window), + PlayerInventoryWindow::TYPE_OFFHAND => $this->add(ContainerIds::OFFHAND, $window), + PlayerInventoryWindow::TYPE_ARMOR => $this->add(ContainerIds::ARMOR, $window), + PlayerInventoryWindow::TYPE_CURSOR => $this->addComplex(UIInventorySlotOffset::CURSOR, $window), + PlayerInventoryWindow::TYPE_CRAFTING => $this->addComplex(UIInventorySlotOffset::CRAFTING2X2_INPUT, $window), + default => throw new AssumptionFailedError("Unknown permanent window type " . $window->getType()) + }; + } $this->player->getHotbar()->getSelectedIndexChangeListeners()->add($this->syncSelectedHotbarSlot(...)); } - private function associateIdWithInventory(int $id, Inventory $inventory) : void{ - $this->networkIdToInventoryMap[$id] = $inventory; + private function associateIdWithInventory(int $id, InventoryWindow $window) : void{ + $this->networkIdToWindowMap[$id] = $window; } private function getNewWindowId() : int{ - $this->lastInventoryNetworkId = max(ContainerIds::FIRST, ($this->lastInventoryNetworkId + 1) % ContainerIds::LAST); - return $this->lastInventoryNetworkId; + $this->lastWindowNetworkId = max(ContainerIds::FIRST, ($this->lastWindowNetworkId + 1) % ContainerIds::LAST); + return $this->lastWindowNetworkId; + } + + private function getEntry(Inventory $inventory) : ?InventoryManagerEntry{ + return $this->entries[spl_object_id($inventory)] ?? null; + } + + public function getInventoryWindow(Inventory $inventory) : ?InventoryWindow{ + return $this->getEntry($inventory)?->window; } - private function add(int $id, Inventory $inventory) : void{ - if(isset($this->inventories[spl_object_id($inventory)])){ - throw new \InvalidArgumentException("Inventory " . get_class($inventory) . " is already tracked"); + private function add(int $id, InventoryWindow $window) : void{ + $k = spl_object_id($window->getInventory()); + if(isset($this->entries[$k])){ + throw new \InvalidArgumentException("Inventory " . get_class($window) . " is already tracked"); } - $this->inventories[spl_object_id($inventory)] = new InventoryManagerEntry($inventory); - $inventory->getListeners()->add($this); - $this->associateIdWithInventory($id, $inventory); + $this->entries[$k] = new InventoryManagerEntry($window); + $window->getInventory()->getListeners()->add($this); + $this->associateIdWithInventory($id, $window); } - private function addDynamic(Inventory $inventory) : int{ + private function addDynamic(InventoryWindow $inventory) : int{ $id = $this->getNewWindowId(); $this->add($id, $inventory); return $id; @@ -165,18 +181,19 @@ private function addDynamic(Inventory $inventory) : int{ * @param int[]|int $slotMap * @phpstan-param array|int $slotMap */ - private function addComplex(array|int $slotMap, Inventory $inventory) : void{ - if(isset($this->inventories[spl_object_id($inventory)])){ - throw new \InvalidArgumentException("Inventory " . get_class($inventory) . " is already tracked"); + private function addComplex(array|int $slotMap, InventoryWindow $window) : void{ + $k = spl_object_id($window->getInventory()); + if(isset($this->entries[$k])){ + throw new \InvalidArgumentException("Inventory " . get_class($window) . " is already tracked"); } - $complexSlotMap = new ComplexInventoryMapEntry($inventory, is_int($slotMap) ? [$slotMap => 0] : $slotMap); - $this->inventories[spl_object_id($inventory)] = new InventoryManagerEntry( - $inventory, + $complexSlotMap = new ComplexWindowMapEntry($window, is_int($slotMap) ? [$slotMap => 0] : $slotMap); + $this->entries[$k] = new InventoryManagerEntry( + $window, $complexSlotMap ); - $inventory->getListeners()->add($this); + $window->getInventory()->getListeners()->add($this); foreach($complexSlotMap->getSlotMap() as $netSlot => $coreSlot){ - $this->complexSlotToInventoryMap[$netSlot] = $complexSlotMap; + $this->complexSlotToWindowMap[$netSlot] = $complexSlotMap; } } @@ -184,7 +201,7 @@ private function addComplex(array|int $slotMap, Inventory $inventory) : void{ * @param int[]|int $slotMap * @phpstan-param array|int $slotMap */ - private function addComplexDynamic(array|int $slotMap, Inventory $inventory) : int{ + private function addComplexDynamic(array|int $slotMap, InventoryWindow $inventory) : int{ $this->addComplex($slotMap, $inventory); $id = $this->getNewWindowId(); $this->associateIdWithInventory($id, $inventory); @@ -192,49 +209,52 @@ private function addComplexDynamic(array|int $slotMap, Inventory $inventory) : i } private function remove(int $id) : void{ - $inventory = $this->networkIdToInventoryMap[$id]; - unset($this->networkIdToInventoryMap[$id]); - if($this->getWindowId($inventory) === null){ + $window = $this->networkIdToWindowMap[$id]; + $inventory = $window->getInventory(); + unset($this->networkIdToWindowMap[$id]); + if($this->getWindowId($window) === null){ $inventory->getListeners()->remove($this); - unset($this->inventories[spl_object_id($inventory)]); - foreach($this->complexSlotToInventoryMap as $netSlot => $entry){ - if($entry->getInventory() === $inventory){ - unset($this->complexSlotToInventoryMap[$netSlot]); + unset($this->entries[spl_object_id($inventory)]); + foreach($this->complexSlotToWindowMap as $netSlot => $entry){ + if($entry->getWindow() === $window){ + unset($this->complexSlotToWindowMap[$netSlot]); } } } } - public function getWindowId(Inventory $inventory) : ?int{ - return ($id = array_search($inventory, $this->networkIdToInventoryMap, true)) !== false ? $id : null; + public function getWindowId(InventoryWindow $window) : ?int{ + return ($id = array_search($window, $this->networkIdToWindowMap, true)) !== false ? $id : null; } public function getCurrentWindowId() : int{ - return $this->lastInventoryNetworkId; + return $this->lastWindowNetworkId; } /** - * @phpstan-return array{Inventory, int}|null + * @phpstan-return array{InventoryWindow, int}|null */ public function locateWindowAndSlot(int $windowId, int $netSlotId) : ?array{ if($windowId === ContainerIds::UI){ - $entry = $this->complexSlotToInventoryMap[$netSlotId] ?? null; + $entry = $this->complexSlotToWindowMap[$netSlotId] ?? null; if($entry === null){ return null; } - $inventory = $entry->getInventory(); + $window = $entry->getWindow(); $coreSlotId = $entry->mapNetToCore($netSlotId); - return $coreSlotId !== null && $inventory->slotExists($coreSlotId) ? [$inventory, $coreSlotId] : null; + return $coreSlotId !== null && $window->getInventory()->slotExists($coreSlotId) ? [$window, $coreSlotId] : null; } - $inventory = $this->networkIdToInventoryMap[$windowId] ?? null; - if($inventory !== null && $inventory->slotExists($netSlotId)){ - return [$inventory, $netSlotId]; + $window = $this->networkIdToWindowMap[$windowId] ?? null; + if($window !== null && $window->getInventory()->slotExists($netSlotId)){ + return [$window, $netSlotId]; } return null; } - private function addPredictedSlotChange(Inventory $inventory, int $slot, ItemStack $item) : void{ - $this->inventories[spl_object_id($inventory)]->predictions[$slot] = $item; + private function addPredictedSlotChange(InventoryWindow $window, int $slot, ItemStack $item) : void{ + //TODO: does this need a null check? + $entry = $this->getEntry($window->getInventory()) ?? throw new AssumptionFailedError("Assume this should never be null"); + $entry->predictions[$slot] = $item; } public function addTransactionPredictedSlotChanges(InventoryTransaction $tx) : void{ @@ -243,7 +263,7 @@ public function addTransactionPredictedSlotChanges(InventoryTransaction $tx) : v if($action instanceof SlotChangeAction){ //TODO: ItemStackRequestExecutor can probably build these predictions with much lower overhead $itemStack = $typeConverter->coreItemStackToNet($action->getTargetItem()); - $this->addPredictedSlotChange($action->getInventory(), $action->getSlot(), $itemStack); + $this->addPredictedSlotChange($action->getInventoryWindow(), $action->getSlot(), $itemStack); } } } @@ -271,8 +291,8 @@ public function addRawPredictedSlotChanges(array $networkInventoryActions) : voi continue; } - [$inventory, $slot] = $info; - $this->addPredictedSlotChange($inventory, $slot, $action->newItem->getItemStack()); + [$window, $slot] = $info; + $this->addPredictedSlotChange($window, $slot, $action->newItem->getItemStack()); } } @@ -307,32 +327,32 @@ private function openWindowDeferred(\Closure $func) : void{ * @return int[]|null * @phpstan-return array|null */ - private function createComplexSlotMapping(Inventory $inventory) : ?array{ + private function createComplexSlotMapping(InventoryWindow $inventory) : ?array{ //TODO: make this dynamic so plugins can add mappings for stuff not implemented by PM return match(true){ - $inventory instanceof AnvilInventory => UIInventorySlotOffset::ANVIL, - $inventory instanceof EnchantInventory => UIInventorySlotOffset::ENCHANTING_TABLE, - $inventory instanceof LoomInventory => UIInventorySlotOffset::LOOM, - $inventory instanceof StonecutterInventory => [UIInventorySlotOffset::STONE_CUTTER_INPUT => StonecutterInventory::SLOT_INPUT], - $inventory instanceof CraftingTableInventory => UIInventorySlotOffset::CRAFTING3X3_INPUT, - $inventory instanceof CartographyTableInventory => UIInventorySlotOffset::CARTOGRAPHY_TABLE, - $inventory instanceof SmithingTableInventory => UIInventorySlotOffset::SMITHING_TABLE, + $inventory instanceof AnvilInventoryWindow => UIInventorySlotOffset::ANVIL, + $inventory instanceof EnchantingTableInventoryWindow => UIInventorySlotOffset::ENCHANTING_TABLE, + $inventory instanceof LoomInventoryWindow => UIInventorySlotOffset::LOOM, + $inventory instanceof StonecutterInventoryWindow => [UIInventorySlotOffset::STONE_CUTTER_INPUT => StonecutterInventoryWindow::SLOT_INPUT], + $inventory instanceof CraftingTableInventoryWindow => UIInventorySlotOffset::CRAFTING3X3_INPUT, + $inventory instanceof CartographyTableInventoryWindow => UIInventorySlotOffset::CARTOGRAPHY_TABLE, + $inventory instanceof SmithingTableInventoryWindow => UIInventorySlotOffset::SMITHING_TABLE, default => null, }; } - public function onCurrentWindowChange(Inventory $inventory) : void{ + public function onCurrentWindowChange(InventoryWindow $window) : void{ $this->onCurrentWindowRemove(); - $this->openWindowDeferred(function() use ($inventory) : void{ - if(($slotMap = $this->createComplexSlotMapping($inventory)) !== null){ - $windowId = $this->addComplexDynamic($slotMap, $inventory); + $this->openWindowDeferred(function() use ($window) : void{ + if(($slotMap = $this->createComplexSlotMapping($window)) !== null){ + $windowId = $this->addComplexDynamic($slotMap, $window); }else{ - $windowId = $this->addDynamic($inventory); + $windowId = $this->addDynamic($window); } foreach($this->containerOpenCallbacks as $callback){ - $pks = $callback($windowId, $inventory); + $pks = $callback($windowId, $window); if($pks !== null){ $windowType = null; foreach($pks as $pk){ @@ -343,7 +363,7 @@ public function onCurrentWindowChange(Inventory $inventory) : void{ $this->session->sendDataPacket($pk); } $this->currentWindowType = $windowType ?? WindowTypes::CONTAINER; - $this->syncContents($inventory); + $this->syncContents($window); return; } } @@ -358,27 +378,27 @@ public function getContainerOpenCallbacks() : ObjectSet{ return $this->container * @return ClientboundPacket[]|null * @phpstan-return list|null */ - protected static function createContainerOpen(int $id, Inventory $inv) : ?array{ + protected static function createContainerOpen(int $id, InventoryWindow $window) : ?array{ //TODO: we should be using some kind of tagging system to identify the types. Instanceof is flaky especially //if the class isn't final, not to mention being inflexible. - if($inv instanceof BlockInventory){ - $blockPosition = BlockPosition::fromVector3($inv->getHolder()); + if($window instanceof BlockInventoryWindow){ + $blockPosition = BlockPosition::fromVector3($window->getHolder()); $windowType = match(true){ - $inv instanceof LoomInventory => WindowTypes::LOOM, - $inv instanceof FurnaceInventory => match($inv->getFurnaceType()){ + $window instanceof LoomInventoryWindow => WindowTypes::LOOM, + $window instanceof FurnaceInventoryWindow => match($window->getFurnaceType()){ FurnaceType::FURNACE => WindowTypes::FURNACE, FurnaceType::BLAST_FURNACE => WindowTypes::BLAST_FURNACE, FurnaceType::SMOKER => WindowTypes::SMOKER, FurnaceType::CAMPFIRE, FurnaceType::SOUL_CAMPFIRE => throw new \LogicException("Campfire inventory cannot be displayed to a player") }, - $inv instanceof EnchantInventory => WindowTypes::ENCHANTMENT, - $inv instanceof BrewingStandInventory => WindowTypes::BREWING_STAND, - $inv instanceof AnvilInventory => WindowTypes::ANVIL, - $inv instanceof HopperInventory => WindowTypes::HOPPER, - $inv instanceof CraftingTableInventory => WindowTypes::WORKBENCH, - $inv instanceof StonecutterInventory => WindowTypes::STONECUTTER, - $inv instanceof CartographyTableInventory => WindowTypes::CARTOGRAPHY, - $inv instanceof SmithingTableInventory => WindowTypes::SMITHING_TABLE, + $window instanceof EnchantingTableInventoryWindow => WindowTypes::ENCHANTMENT, + $window instanceof BrewingStandInventoryWindow => WindowTypes::BREWING_STAND, + $window instanceof AnvilInventoryWindow => WindowTypes::ANVIL, + $window instanceof HopperInventoryWindow => WindowTypes::HOPPER, + $window instanceof CraftingTableInventoryWindow => WindowTypes::WORKBENCH, + $window instanceof StonecutterInventoryWindow => WindowTypes::STONECUTTER, + $window instanceof CartographyTableInventoryWindow => WindowTypes::CARTOGRAPHY, + $window instanceof SmithingTableInventoryWindow => WindowTypes::SMITHING_TABLE, default => WindowTypes::CONTAINER }; return [ContainerOpenPacket::blockInv($id, $windowType, $blockPosition)]; @@ -391,7 +411,8 @@ public function onClientOpenMainInventory() : void{ $this->openWindowDeferred(function() : void{ $windowId = $this->getNewWindowId(); - $this->associateIdWithInventory($windowId, $this->player->getInventory()); + $window = $this->getInventoryWindow($this->player->getInventory()) ?? throw new AssumptionFailedError("This should never be null"); + $this->associateIdWithInventory($windowId, $window); $this->currentWindowType = WindowTypes::INVENTORY; $this->session->sendDataPacket(ContainerOpenPacket::entityInv( @@ -403,25 +424,25 @@ public function onClientOpenMainInventory() : void{ } public function onCurrentWindowRemove() : void{ - if(isset($this->networkIdToInventoryMap[$this->lastInventoryNetworkId])){ - $this->remove($this->lastInventoryNetworkId); - $this->session->sendDataPacket(ContainerClosePacket::create($this->lastInventoryNetworkId, $this->currentWindowType, true)); + if(isset($this->networkIdToWindowMap[$this->lastWindowNetworkId])){ + $this->remove($this->lastWindowNetworkId); + $this->session->sendDataPacket(ContainerClosePacket::create($this->lastWindowNetworkId, $this->currentWindowType, true)); if($this->pendingCloseWindowId !== null){ throw new AssumptionFailedError("We should not have opened a new window while a window was waiting to be closed"); } - $this->pendingCloseWindowId = $this->lastInventoryNetworkId; + $this->pendingCloseWindowId = $this->lastWindowNetworkId; $this->enchantingTableOptions = []; } } public function onClientRemoveWindow(int $id) : void{ - if($id === $this->lastInventoryNetworkId){ - if(isset($this->networkIdToInventoryMap[$id]) && $id !== $this->pendingCloseWindowId){ + if($id === $this->lastWindowNetworkId){ + if(isset($this->networkIdToWindowMap[$id]) && $id !== $this->pendingCloseWindowId){ $this->remove($id); $this->player->removeCurrentWindow(); } }else{ - $this->session->getLogger()->debug("Attempted to close inventory with network ID $id, but current is $this->lastInventoryNetworkId"); + $this->session->getLogger()->debug("Attempted to close inventory with network ID $id, but current is $this->lastWindowNetworkId"); } //Always send this, even if no window matches. If we told the client to close a window, it will behave as if it @@ -474,12 +495,24 @@ private function itemStacksEqual(ItemStack $left, ItemStack $right) : bool{ } public function onSlotChange(Inventory $inventory, int $slot, Item $oldItem) : void{ - $inventoryEntry = $this->inventories[spl_object_id($inventory)] ?? null; + $window = $this->getInventoryWindow($inventory); + if($window === null){ + //this can happen when an inventory changed during InventoryCloseEvent, or when a temporary inventory + //is cleared before removal. + return; + } + $this->requestSyncSlot($window, $slot); + } + + public function requestSyncSlot(InventoryWindow $window, int $slot) : void{ + $inventory = $window->getInventory(); + $inventoryEntry = $this->getEntry($inventory); if($inventoryEntry === null){ //this can happen when an inventory changed during InventoryCloseEvent, or when a temporary inventory //is cleared before removal. return; } + $currentItem = $this->session->getTypeConverter()->coreItemStackToNet($inventory->getItem($slot)); $clientSideItem = $inventoryEntry->predictions[$slot] ?? null; if($clientSideItem === null || !$this->itemStacksEqual($currentItem, $clientSideItem)){ @@ -507,7 +540,7 @@ private function sendInventorySlotPackets(int $windowId, int $netSlot, ItemStack $this->session->sendDataPacket(InventorySlotPacket::create( $windowId, $netSlot, - new FullContainerName($this->lastInventoryNetworkId), + new FullContainerName($this->lastWindowNetworkId), new ItemStackWrapper(0, ItemStack::null()), new ItemStackWrapper(0, ItemStack::null()) )); @@ -516,7 +549,7 @@ private function sendInventorySlotPackets(int $windowId, int $netSlot, ItemStack $this->session->sendDataPacket(InventorySlotPacket::create( $windowId, $netSlot, - new FullContainerName($this->lastInventoryNetworkId), + new FullContainerName($this->lastWindowNetworkId), new ItemStackWrapper(0, ItemStack::null()), $itemStackWrapper )); @@ -537,18 +570,15 @@ private function sendInventoryContentPackets(int $windowId, array $itemStackWrap $this->session->sendDataPacket(InventoryContentPacket::create( $windowId, array_fill_keys(array_keys($itemStackWrappers), new ItemStackWrapper(0, ItemStack::null())), - new FullContainerName($this->lastInventoryNetworkId), + new FullContainerName($this->lastWindowNetworkId), new ItemStackWrapper(0, ItemStack::null()) )); //now send the real contents - $this->session->sendDataPacket(InventoryContentPacket::create($windowId, $itemStackWrappers, new FullContainerName($this->lastInventoryNetworkId), new ItemStackWrapper(0, ItemStack::null()))); + $this->session->sendDataPacket(InventoryContentPacket::create($windowId, $itemStackWrappers, new FullContainerName($this->lastWindowNetworkId), new ItemStackWrapper(0, ItemStack::null()))); } - public function syncSlot(Inventory $inventory, int $slot, ItemStack $itemStack) : void{ - $entry = $this->inventories[spl_object_id($inventory)] ?? null; - if($entry === null){ - throw new \LogicException("Cannot sync an untracked inventory"); - } + private function syncSlot(InventoryWindow $window, int $slot, ItemStack $itemStack) : void{ + $entry = $this->getEntry($window->getInventory()) ?? throw new \LogicException("Cannot sync an untracked inventory"); $itemStackInfo = $entry->itemStackInfos[$slot]; if($itemStackInfo === null){ throw new \LogicException("Cannot sync an untracked inventory slot"); @@ -557,7 +587,7 @@ public function syncSlot(Inventory $inventory, int $slot, ItemStack $itemStack) $windowId = ContainerIds::UI; $netSlot = $entry->complexSlotMap->mapCoreToNet($slot) ?? throw new AssumptionFailedError("We already have an ItemStackInfo, so this should not be null"); }else{ - $windowId = $this->getWindowId($inventory) ?? throw new AssumptionFailedError("We already have an ItemStackInfo, so this should not be null"); + $windowId = $this->getWindowId($window) ?? throw new AssumptionFailedError("We already have an ItemStackInfo, so this should not be null"); $netSlot = $slot; } @@ -576,11 +606,16 @@ public function syncSlot(Inventory $inventory, int $slot, ItemStack $itemStack) } public function onContentChange(Inventory $inventory, array $oldContents) : void{ - $this->syncContents($inventory); + //this can be null when an inventory changed during InventoryCloseEvent, or when a temporary inventory + //is cleared before removal. + $window = $this->getInventoryWindow($inventory); + if($window !== null){ + $this->syncContents($window); + } } - public function syncContents(Inventory $inventory) : void{ - $entry = $this->inventories[spl_object_id($inventory)] ?? null; + private function syncContents(InventoryWindow $window) : void{ + $entry = $this->getEntry($window->getInventory()); if($entry === null){ //this can happen when an inventory changed during InventoryCloseEvent, or when a temporary inventory //is cleared before removal. @@ -589,14 +624,14 @@ public function syncContents(Inventory $inventory) : void{ if($entry->complexSlotMap !== null){ $windowId = ContainerIds::UI; }else{ - $windowId = $this->getWindowId($inventory); + $windowId = $this->getWindowId($window); } if($windowId !== null){ $entry->predictions = []; $entry->pendingSyncs = []; $contents = []; $typeConverter = $this->session->getTypeConverter(); - foreach($inventory->getContents(true) as $slot => $item){ + foreach($window->getInventory()->getContents(true) as $slot => $item){ $itemStack = $typeConverter->coreItemStackToNet($item); $info = $this->trackItemStack($entry, $slot, $itemStack, null); $contents[] = new ItemStackWrapper($info->getStackId(), $itemStack); @@ -617,8 +652,8 @@ public function syncContents(Inventory $inventory) : void{ } public function syncAll() : void{ - foreach($this->inventories as $entry){ - $this->syncContents($entry->inventory); + foreach($this->entries as $entry){ + $this->syncContents($entry->window); } } @@ -628,8 +663,8 @@ public function requestSyncAll() : void{ public function syncMismatchedPredictedSlotChanges() : void{ $typeConverter = $this->session->getTypeConverter(); - foreach($this->inventories as $entry){ - $inventory = $entry->inventory; + foreach($this->entries as $entry){ + $inventory = $entry->window->getInventory(); foreach($entry->predictions as $slot => $expectedItem){ if(!$inventory->slotExists($slot) || $entry->itemStackInfos[$slot] === null){ continue; //TODO: size desync ??? @@ -647,14 +682,14 @@ public function syncMismatchedPredictedSlotChanges() : void{ public function flushPendingUpdates() : void{ if($this->fullSyncRequested){ $this->fullSyncRequested = false; - $this->session->getLogger()->debug("Full inventory sync requested, sending contents of " . count($this->inventories) . " inventories"); + $this->session->getLogger()->debug("Full inventory sync requested, sending contents of " . count($this->entries) . " inventories"); $this->syncAll(); }else{ - foreach($this->inventories as $entry){ + foreach($this->entries as $entry){ if(count($entry->pendingSyncs) === 0){ continue; } - $inventory = $entry->inventory; + $inventory = $entry->window; $this->session->getLogger()->debug("Syncing slots " . implode(", ", array_keys($entry->pendingSyncs)) . " in inventory " . get_class($inventory) . "#" . spl_object_id($inventory)); foreach($entry->pendingSyncs as $slot => $itemStack){ $this->syncSlot($inventory, $slot, $itemStack); @@ -665,7 +700,13 @@ public function flushPendingUpdates() : void{ } public function syncData(Inventory $inventory, int $propertyId, int $value) : void{ - $windowId = $this->getWindowId($inventory); + //TODO: the handling of this data has always kinda sucked. Probably ought to route it through InventoryWindow + //somehow, but I'm not sure exactly how that should look. + $window = $this->getInventoryWindow($inventory); + if($window === null){ + return; + } + $windowId = $this->getWindowId($window); if($windowId !== null){ $this->session->sendDataPacket(ContainerSetDataPacket::create($windowId, $propertyId, $value)); } @@ -679,10 +720,7 @@ public function syncSelectedHotbarSlot() : void{ $playerInventory = $this->player->getInventory(); $selected = $this->player->getHotbar()->getSelectedIndex(); if($selected !== $this->clientSelectedHotbarSlot){ - $inventoryEntry = $this->inventories[spl_object_id($playerInventory)] ?? null; - if($inventoryEntry === null){ - throw new AssumptionFailedError("Player inventory should always be tracked"); - } + $inventoryEntry = $this->getEntry($playerInventory) ?? throw new AssumptionFailedError("Player inventory should always be tracked"); $itemStackInfo = $inventoryEntry->itemStackInfos[$selected] ?? null; if($itemStackInfo === null){ throw new AssumptionFailedError("Untracked player inventory slot $selected"); @@ -741,8 +779,7 @@ private function newItemStackId() : int{ } public function getItemStackInfo(Inventory $inventory, int $slot) : ?ItemStackInfo{ - $entry = $this->inventories[spl_object_id($inventory)] ?? null; - return $entry?->itemStackInfos[$slot] ?? null; + return $this->getEntry($inventory)?->itemStackInfos[$slot] ?? null; } private function trackItemStack(InventoryManagerEntry $entry, int $slotId, ItemStack $itemStack, ?int $itemStackRequestId) : ItemStackInfo{ diff --git a/src/network/mcpe/InventoryManagerEntry.php b/src/network/mcpe/InventoryManagerEntry.php index deb2e8e4db4..8f7c07de232 100644 --- a/src/network/mcpe/InventoryManagerEntry.php +++ b/src/network/mcpe/InventoryManagerEntry.php @@ -23,8 +23,8 @@ namespace pocketmine\network\mcpe; -use pocketmine\inventory\Inventory; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; +use pocketmine\player\InventoryWindow; final class InventoryManagerEntry{ /** @@ -46,7 +46,7 @@ final class InventoryManagerEntry{ public array $pendingSyncs = []; public function __construct( - public Inventory $inventory, - public ?ComplexInventoryMapEntry $complexSlotMap = null + public InventoryWindow $window, + public ?ComplexWindowMapEntry $complexSlotMap = null ){} } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 8c101853f63..6a61a5c0fea 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -101,6 +101,7 @@ use pocketmine\network\mcpe\protocol\types\PlayerBlockActionWithBlockInfo; use pocketmine\network\PacketHandlingException; use pocketmine\player\Player; +use pocketmine\player\PlayerInventoryWindow; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Limits; use pocketmine\utils\TextFormat; @@ -355,7 +356,7 @@ public function handleInventoryTransaction(InventoryTransactionPacket $packet) : [$windowId, $slot] = ItemStackContainerIdTranslator::translate($containerInfo->getContainerId(), $this->inventoryManager->getCurrentWindowId(), $netSlot); $inventoryAndSlot = $this->inventoryManager->locateWindowAndSlot($windowId, $slot); if($inventoryAndSlot !== null){ //trigger the normal slot sync logic - $this->inventoryManager->onSlotChange($inventoryAndSlot[0], $inventoryAndSlot[1]); + $this->inventoryManager->requestSyncSlot($inventoryAndSlot[0], $inventoryAndSlot[1]); } } } @@ -461,7 +462,8 @@ private function handleNormalTransaction(NormalTransactionData $data, int $itemS $droppedItem = $sourceSlotItem->pop($droppedCount); $builder = new TransactionBuilder(); - $builder->getInventory($inventory)->setItem($sourceSlot, $sourceSlotItem); + //TODO: this probably shouldn't be creating an ephemeral window here - it works, but no idea what side effects it might have on the permanent window + $builder->getActionBuilder(new PlayerInventoryWindow($this->player, $inventory, PlayerInventoryWindow::TYPE_INVENTORY))->setItem($sourceSlot, $sourceSlotItem); $builder->addAction(new DropItemAction($droppedItem)); $transaction = new InventoryTransaction($this->player, $builder->generateActions()); diff --git a/src/network/mcpe/handler/ItemStackRequestExecutor.php b/src/network/mcpe/handler/ItemStackRequestExecutor.php index 54a19259010..d58ec622300 100644 --- a/src/network/mcpe/handler/ItemStackRequestExecutor.php +++ b/src/network/mcpe/handler/ItemStackRequestExecutor.php @@ -23,7 +23,7 @@ namespace pocketmine\network\mcpe\handler; -use pocketmine\block\inventory\EnchantInventory; +use pocketmine\block\inventory\EnchantingTableInventoryWindow; use pocketmine\inventory\Inventory; use pocketmine\inventory\transaction\action\CreateItemAction; use pocketmine\inventory\transaction\action\DestroyItemAction; @@ -31,8 +31,8 @@ use pocketmine\inventory\transaction\CraftingTransaction; use pocketmine\inventory\transaction\EnchantingTransaction; use pocketmine\inventory\transaction\InventoryTransaction; +use pocketmine\inventory\transaction\SlotChangeActionBuilder; use pocketmine\inventory\transaction\TransactionBuilder; -use pocketmine\inventory\transaction\TransactionBuilderInventory; use pocketmine\item\Item; use pocketmine\network\mcpe\InventoryManager; use pocketmine\network\mcpe\protocol\types\inventory\ContainerUIIds; @@ -52,6 +52,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\TakeStackRequestAction; use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponse; use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; use pocketmine\utils\AssumptionFailedError; use function array_key_first; @@ -81,25 +82,22 @@ public function __construct( $this->builder = new TransactionBuilder(); } - protected function prettyInventoryAndSlot(Inventory $inventory, int $slot) : string{ - if($inventory instanceof TransactionBuilderInventory){ - $inventory = $inventory->getActualInventory(); - } + protected function prettyWindowAndSlot(InventoryWindow $inventory, int $slot) : string{ return (new \ReflectionClass($inventory))->getShortName() . "#" . spl_object_id($inventory) . ", slot: $slot"; } /** * @throws ItemStackRequestProcessException */ - private function matchItemStack(Inventory $inventory, int $slotId, int $clientItemStackId) : void{ - $info = $this->inventoryManager->getItemStackInfo($inventory, $slotId); + private function matchItemStack(InventoryWindow $window, int $slotId, int $clientItemStackId) : void{ + $info = $this->inventoryManager->getItemStackInfo($window->getInventory(), $slotId); if($info === null){ throw new AssumptionFailedError("The inventory is tracked and the slot is valid, so this should not be null"); } if(!($clientItemStackId < 0 ? $info->getRequestId() === $clientItemStackId : $info->getStackId() === $clientItemStackId)){ throw new ItemStackRequestProcessException( - $this->prettyInventoryAndSlot($inventory, $slotId) . ": " . + $this->prettyWindowAndSlot($window, $slotId) . ": " . "Mismatched expected itemstack, " . "client expected: $clientItemStackId, server actual: " . $info->getStackId() . ", last modified by request: " . ($info->getRequestId() ?? "none") ); @@ -107,7 +105,7 @@ private function matchItemStack(Inventory $inventory, int $slotId, int $clientIt } /** - * @phpstan-return array{TransactionBuilderInventory, int} + * @phpstan-return array{SlotChangeActionBuilder, int} * * @throws ItemStackRequestProcessException */ @@ -117,16 +115,17 @@ protected function getBuilderInventoryAndSlot(ItemStackRequestSlotInfo $info) : if($windowAndSlot === null){ throw new ItemStackRequestProcessException("No open inventory matches container UI ID: " . $info->getContainerName()->getContainerId() . ", slot ID: " . $info->getSlotId()); } - [$inventory, $slot] = $windowAndSlot; + [$window, $slot] = $windowAndSlot; + $inventory = $window->getInventory(); if(!$inventory->slotExists($slot)){ - throw new ItemStackRequestProcessException("No such inventory slot :" . $this->prettyInventoryAndSlot($inventory, $slot)); + throw new ItemStackRequestProcessException("No such inventory slot :" . $this->prettyWindowAndSlot($window, $slot)); } if($info->getStackId() !== $this->request->getRequestId()){ //the itemstack may have been modified by the current request - $this->matchItemStack($inventory, $slot, $info->getStackId()); + $this->matchItemStack($window, $slot, $info->getStackId()); } - return [$this->builder->getInventory($inventory), $slot]; + return [$this->builder->getActionBuilder($window), $slot]; } /** @@ -151,12 +150,12 @@ protected function removeItemFromSlot(ItemStackRequestSlotInfo $slotInfo, int $c [$inventory, $slot] = $this->getBuilderInventoryAndSlot($slotInfo); if($count < 1){ //this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits - throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take less than 1 items from a stack"); + throw new ItemStackRequestProcessException($this->prettyWindowAndSlot($inventory->getInventoryWindow(), $slot) . ": Cannot take less than 1 items from a stack"); } $existingItem = $inventory->getItem($slot); if($existingItem->getCount() < $count){ - throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take $count items from a stack of " . $existingItem->getCount()); + throw new ItemStackRequestProcessException($this->prettyWindowAndSlot($inventory->getInventoryWindow(), $slot) . ": Cannot take $count items from a stack of " . $existingItem->getCount()); } $removed = $existingItem->pop($count); @@ -174,12 +173,12 @@ protected function addItemToSlot(ItemStackRequestSlotInfo $slotInfo, Item $item, [$inventory, $slot] = $this->getBuilderInventoryAndSlot($slotInfo); if($count < 1){ //this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits - throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take less than 1 items from a stack"); + throw new ItemStackRequestProcessException($this->prettyWindowAndSlot($inventory->getInventoryWindow(), $slot) . ": Cannot take less than 1 items from a stack"); } $existingItem = $inventory->getItem($slot); if(!$existingItem->isNull() && !$existingItem->canStackWith($item)){ - throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Can only add items to an empty slot, or a slot containing the same item"); + throw new ItemStackRequestProcessException($this->prettyWindowAndSlot($inventory->getInventoryWindow(), $slot) . ": Can only add items to an empty slot, or a slot containing the same item"); } //we can't use the existing item here; it may be an empty stack @@ -336,7 +335,7 @@ protected function processItemStackRequestAction(ItemStackRequestAction $action) $this->setNextCreatedItem($item, true); }elseif($action instanceof CraftRecipeStackRequestAction){ $window = $this->player->getCurrentWindow(); - if($window instanceof EnchantInventory){ + if($window instanceof EnchantingTableInventoryWindow){ $optionId = $this->inventoryManager->getEnchantingTableOptionIndex($action->getRecipeId()); if($optionId !== null && ($option = $window->getOption($optionId)) !== null){ $this->specialTransaction = new EnchantingTransaction($this->player, $option, $optionId + 1); diff --git a/src/network/mcpe/handler/ItemStackResponseBuilder.php b/src/network/mcpe/handler/ItemStackResponseBuilder.php index 1369e3ba722..75c740a5f28 100644 --- a/src/network/mcpe/handler/ItemStackResponseBuilder.php +++ b/src/network/mcpe/handler/ItemStackResponseBuilder.php @@ -59,7 +59,8 @@ private function getInventoryAndSlot(int $containerInterfaceId, int $slotId) : ? if($windowAndSlot === null){ return null; } - [$inventory, $slot] = $windowAndSlot; + [$window, $slot] = $windowAndSlot; + $inventory = $window->getInventory(); if(!$inventory->slotExists($slot)){ return null; } diff --git a/src/player/Player.php b/src/player/Player.php index 3dba79931b0..3b54a2b869a 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -85,9 +85,7 @@ use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\CreativeInventory; use pocketmine\inventory\Inventory; -use pocketmine\inventory\PlayerCraftingInventory; -use pocketmine\inventory\PlayerCursorInventory; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\inventory\transaction\action\DropItemAction; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\TransactionBuilder; @@ -218,11 +216,11 @@ public static function isValidUserName(?string $name) : bool{ protected bool $authenticated; protected PlayerInfo $playerInfo; - protected ?Inventory $currentWindow = null; - /** @var Inventory[] */ + protected ?InventoryWindow $currentWindow = null; + /** @var PlayerInventoryWindow[] */ protected array $permanentWindows = []; - protected PlayerCursorInventory $cursorInventory; - protected PlayerCraftingInventory $craftingGrid; + protected Inventory $cursorInventory; + protected CraftingGrid $craftingGrid; protected CreativeInventory $creativeInventory; protected int $messageCounter = 2; @@ -2318,7 +2316,7 @@ public function onPostDisconnect(Translatable|string $reason, Translatable|strin $this->loadQueue = []; $this->removeCurrentWindow(); - $this->removePermanentInventories(); + $this->removePermanentWindows(); $this->perm->getPermissionRecalculationCallbacks()->clear(); @@ -2334,8 +2332,6 @@ protected function onDispose() : void{ protected function destroyCycles() : void{ $this->networkSession = null; - unset($this->cursorInventory); - unset($this->craftingGrid); $this->spawnPosition = null; $this->blockBreakHandler = null; parent::destroyCycles(); @@ -2589,15 +2585,19 @@ public function teleport(Vector3 $pos, ?float $yaw = null, ?float $pitch = null) } protected function addDefaultWindows() : void{ - $this->cursorInventory = new PlayerCursorInventory($this); - $this->craftingGrid = new PlayerCraftingInventory($this); + $this->cursorInventory = new SimpleInventory(1); + $this->craftingGrid = new CraftingGrid(CraftingGrid::SIZE_SMALL); - $this->addPermanentInventories($this->inventory, $this->armorInventory, $this->cursorInventory, $this->offHandInventory, $this->craftingGrid); - - //TODO: more windows + $this->addPermanentWindows([ + new PlayerInventoryWindow($this, $this->inventory, PlayerInventoryWindow::TYPE_INVENTORY), + new PlayerInventoryWindow($this, $this->armorInventory, PlayerInventoryWindow::TYPE_ARMOR), + new PlayerInventoryWindow($this, $this->cursorInventory, PlayerInventoryWindow::TYPE_CURSOR), + new PlayerInventoryWindow($this, $this->offHandInventory, PlayerInventoryWindow::TYPE_OFFHAND), + new PlayerInventoryWindow($this, $this->craftingGrid, PlayerInventoryWindow::TYPE_CRAFTING), + ]); } - public function getCursorInventory() : PlayerCursorInventory{ + public function getCursorInventory() : Inventory{ return $this->cursorInventory; } @@ -2628,22 +2628,37 @@ public function setCreativeInventory(CreativeInventory $inventory) : void{ * inventory. */ private function doCloseInventory() : void{ - $inventories = [$this->craftingGrid, $this->cursorInventory]; - if($this->currentWindow instanceof TemporaryInventory){ - $inventories[] = $this->currentWindow; + $windowsToClear = []; + $mainInventoryWindow = null; + foreach($this->permanentWindows as $window){ + if($window->getType() === PlayerInventoryWindow::TYPE_CRAFTING || $window->getType() === PlayerInventoryWindow::TYPE_CURSOR){ + $windowsToClear[] = $window; + }elseif($window->getType() === PlayerInventoryWindow::TYPE_INVENTORY){ + $mainInventoryWindow = $window; + } + } + if($mainInventoryWindow === null){ + //TODO: in the future this might not be the case, if we implement support for the player closing their + //inventory window outside the protocol layer + //in that case we'd have to create a new ephemeral window here + throw new AssumptionFailedError("This should never be null"); + } + + if($this->currentWindow instanceof TemporaryInventoryWindow){ + $windowsToClear[] = $this->currentWindow; } $builder = new TransactionBuilder(); - foreach($inventories as $inventory){ - $contents = $inventory->getContents(); + foreach($windowsToClear as $window){ + $contents = $window->getInventory()->getContents(); if(count($contents) > 0){ - $drops = $builder->getInventory($this->inventory)->addItem(...$contents); + $drops = $builder->getActionBuilder($mainInventoryWindow)->addItem(...$contents); foreach($drops as $drop){ $builder->addAction(new DropItemAction($drop)); } - $builder->getInventory($inventory)->clearAll(); + $builder->getActionBuilder($window)->clearAll(); } } @@ -2655,8 +2670,8 @@ private function doCloseInventory() : void{ $this->logger->debug("Successfully evacuated items from temporary inventories"); }catch(TransactionCancelledException){ $this->logger->debug("Plugin cancelled transaction evacuating items from temporary inventories; items will be destroyed"); - foreach($inventories as $inventory){ - $inventory->clearAll(); + foreach($windowsToClear as $window){ + $window->getInventory()->clearAll(); } }catch(TransactionValidationException $e){ throw new AssumptionFailedError("This server-generated transaction should never be invalid", 0, $e); @@ -2667,18 +2682,18 @@ private function doCloseInventory() : void{ /** * Returns the inventory the player is currently viewing. This might be a chest, furnace, or any other container. */ - public function getCurrentWindow() : ?Inventory{ + public function getCurrentWindow() : ?InventoryWindow{ return $this->currentWindow; } /** * Opens an inventory window to the player. Returns if it was successful. */ - public function setCurrentWindow(Inventory $inventory) : bool{ - if($inventory === $this->currentWindow){ + public function setCurrentWindow(InventoryWindow $window) : bool{ + if($window === $this->currentWindow){ return true; } - $ev = new InventoryOpenEvent($inventory, $this); + $ev = new InventoryOpenEvent($window, $this); $ev->call(); if($ev->isCancelled()){ return false; @@ -2689,10 +2704,10 @@ public function setCurrentWindow(Inventory $inventory) : bool{ if(($inventoryManager = $this->getNetworkSession()->getInvManager()) === null){ throw new \InvalidArgumentException("Player cannot open inventories in this state"); } - $this->logger->debug("Opening inventory " . get_class($inventory) . "#" . spl_object_id($inventory)); - $inventoryManager->onCurrentWindowChange($inventory); - $inventory->onOpen($this); - $this->currentWindow = $inventory; + $this->logger->debug("Opening inventory " . get_class($window) . "#" . spl_object_id($window)); + $inventoryManager->onCurrentWindowChange($window); + $window->onOpen(); + $this->currentWindow = $window; return true; } @@ -2701,7 +2716,7 @@ public function removeCurrentWindow() : void{ if($this->currentWindow !== null){ $currentWindow = $this->currentWindow; $this->logger->debug("Closing inventory " . get_class($this->currentWindow) . "#" . spl_object_id($this->currentWindow)); - $this->currentWindow->onClose($this); + $this->currentWindow->onClose(); if(($inventoryManager = $this->getNetworkSession()->getInvManager()) !== null){ $inventoryManager->onCurrentWindowRemove(); } @@ -2710,20 +2725,31 @@ public function removeCurrentWindow() : void{ } } - protected function addPermanentInventories(Inventory ...$inventories) : void{ - foreach($inventories as $inventory){ - $inventory->onOpen($this); - $this->permanentWindows[spl_object_id($inventory)] = $inventory; + /** + * @param PlayerInventoryWindow[] $windows + */ + protected function addPermanentWindows(array $windows) : void{ + foreach($windows as $window){ + $window->onOpen(); + $this->permanentWindows[spl_object_id($window)] = $window; } } - protected function removePermanentInventories() : void{ - foreach($this->permanentWindows as $inventory){ - $inventory->onClose($this); + protected function removePermanentWindows() : void{ + foreach($this->permanentWindows as $window){ + $window->onClose(); } $this->permanentWindows = []; } + /** + * @return PlayerInventoryWindow[] + * @internal + */ + public function getPermanentWindows() : array{ + return $this->permanentWindows; + } + /** * Opens the player's sign editor GUI for the sign at the given position. * TODO: add support for editing the rear side of the sign (not currently supported due to technical limitations) diff --git a/src/inventory/TemporaryInventory.php b/src/player/TemporaryInventoryWindow.php similarity index 90% rename from src/inventory/TemporaryInventory.php rename to src/player/TemporaryInventoryWindow.php index 26a53b1718c..c4238656415 100644 --- a/src/inventory/TemporaryInventory.php +++ b/src/player/TemporaryInventoryWindow.php @@ -21,8 +21,8 @@ declare(strict_types=1); -namespace pocketmine\inventory; +namespace pocketmine\player; -interface TemporaryInventory extends Inventory{ +interface TemporaryInventoryWindow{ } From dcbf1c706a4f2f70887106db43c253198bd55f1a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Nov 2024 21:45:45 +0000 Subject: [PATCH 04/28] this bites me every single time --- src/player/InventoryWindow.php | 50 +++++++++++++++++++++++++++ src/player/PlayerInventoryWindow.php | 51 ++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/player/InventoryWindow.php create mode 100644 src/player/PlayerInventoryWindow.php diff --git a/src/player/InventoryWindow.php b/src/player/InventoryWindow.php new file mode 100644 index 00000000000..78dcef64af7 --- /dev/null +++ b/src/player/InventoryWindow.php @@ -0,0 +1,50 @@ +viewer; + } + + public function getInventory() : Inventory{ + return $this->inventory; + } + + public function onOpen() : void{ + $this->inventory->onOpen($this->viewer); + } + + public function onClose() : void{ + $this->inventory->onClose($this->viewer); + } +} diff --git a/src/player/PlayerInventoryWindow.php b/src/player/PlayerInventoryWindow.php new file mode 100644 index 00000000000..e9bdf8f7439 --- /dev/null +++ b/src/player/PlayerInventoryWindow.php @@ -0,0 +1,51 @@ +type; } +} From 9c5df90e9b61a5d30f6da8c668650efc3335af7a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Nov 2024 22:11:38 +0000 Subject: [PATCH 05/28] Burn more junk --- src/inventory/DelegateInventory.php | 107 ---------------------------- 1 file changed, 107 deletions(-) delete mode 100644 src/inventory/DelegateInventory.php diff --git a/src/inventory/DelegateInventory.php b/src/inventory/DelegateInventory.php deleted file mode 100644 index 5bf9b908ae0..00000000000 --- a/src/inventory/DelegateInventory.php +++ /dev/null @@ -1,107 +0,0 @@ -backingInventory->getListeners()->add($this->inventoryListener = new CallbackInventoryListener( - static function(Inventory $unused, int $slot, Item $oldItem) use ($weakThis) : void{ - if(($strongThis = $weakThis->get()) !== null){ - $strongThis->backingInventoryChanging = true; - try{ - $strongThis->onSlotChange($slot, $oldItem); - }finally{ - $strongThis->backingInventoryChanging = false; - } - } - }, - static function(Inventory $unused, array $oldContents) use ($weakThis) : void{ - if(($strongThis = $weakThis->get()) !== null){ - $strongThis->backingInventoryChanging = true; - try{ - $strongThis->onContentChange($oldContents); - }finally{ - $strongThis->backingInventoryChanging = false; - } - } - } - )); - } - - public function __destruct(){ - $this->backingInventory->getListeners()->remove($this->inventoryListener); - } - - public function getSize() : int{ - return $this->backingInventory->getSize(); - } - - public function getItem(int $index) : Item{ - return $this->backingInventory->getItem($index); - } - - protected function internalSetItem(int $index, Item $item) : void{ - $this->backingInventory->setItem($index, $item); - } - - public function getContents(bool $includeEmpty = false) : array{ - return $this->backingInventory->getContents($includeEmpty); - } - - protected function internalSetContents(array $items) : void{ - $this->backingInventory->setContents($items); - } - - public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ - return $this->backingInventory->getMatchingItemCount($slot, $test, $checkTags); - } - - public function isSlotEmpty(int $index) : bool{ - return $this->backingInventory->isSlotEmpty($index); - } - - protected function onSlotChange(int $index, Item $before) : void{ - if($this->backingInventoryChanging){ - parent::onSlotChange($index, $before); - } - } - - protected function onContentChange(array $itemsBefore) : void{ - if($this->backingInventoryChanging){ - parent::onContentChange($itemsBefore); - } - } -} From 1d2b52732e3c475ddc2bab4e45726d22850e3d5c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Nov 2024 22:47:35 +0000 Subject: [PATCH 06/28] Chest block now has responsibility for configuring double chest inventories it already needs to locate the correct pair anyway to know the left/right for DoubleChestInventoryWindow, so we might as well use this logic for initializing the DoubleChestInventory itself too. The old logic in tile/Chest didn't work correctly. --- src/block/Chest.php | 30 ++++++++++++++++++---------- src/block/tile/Barrel.php | 4 ---- src/block/tile/BrewingStand.php | 4 ---- src/block/tile/Campfire.php | 4 ---- src/block/tile/Chest.php | 26 +++++++----------------- src/block/tile/ChiseledBookshelf.php | 8 ++------ src/block/tile/Container.php | 3 --- src/block/tile/ContainerTrait.php | 9 +++------ src/block/tile/Furnace.php | 4 ---- src/block/tile/Hopper.php | 4 ---- src/block/tile/ShulkerBox.php | 4 ---- 11 files changed, 31 insertions(+), 69 deletions(-) diff --git a/src/block/Chest.php b/src/block/Chest.php index e823187c3d3..8e0616cd40c 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -24,6 +24,7 @@ namespace pocketmine\block; use pocketmine\block\inventory\ChestInventoryWindow; +use pocketmine\block\inventory\DoubleChestInventory; use pocketmine\block\inventory\DoubleChestInventoryWindow; use pocketmine\block\tile\Chest as TileChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; @@ -87,18 +88,25 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } - foreach([false, true] as $clockwise){ - $sideFacing = Facing::rotateY($this->facing, $clockwise); - $side = $this->position->getSide($sideFacing); - $pair = $world->getTile($side); - if($pair instanceof TileChest && $pair->getPair() === $chest){ - [$left, $right] = $clockwise ? [$side, $this->position] : [$this->position, $side]; + $window = null; + if($chest->isPaired()){ + foreach([false, true] as $clockwise){ + $sideFacing = Facing::rotateY($this->facing, $clockwise); + $side = $this->position->getSide($sideFacing); + $pair = $world->getTile($side); + if($pair instanceof TileChest && $pair->getPair() === $chest){ + [$left, $right] = $clockwise ? [$pair, $chest] : [$chest, $pair]; - //TODO: we should probably construct DoubleChestInventory here directly too using the same logic - //right now it uses some weird logic in TileChest which produces incorrect results - //however I'm not sure if this is currently possible - $window = new DoubleChestInventoryWindow($player, $chest->getInventory(), $left, $right); - break; + $doubleInventory = $left->getDoubleInventory() ?? $right->getDoubleInventory(); + if($doubleInventory === null){ + $doubleInventory = new DoubleChestInventory($left->getInventory(), $right->getInventory()); + $left->setDoubleInventory($doubleInventory); + $right->setDoubleInventory($doubleInventory); + } + + $window = new DoubleChestInventoryWindow($player, $doubleInventory, $left->getPosition(), $right->getPosition()); + break; + } } } diff --git a/src/block/tile/Barrel.php b/src/block/tile/Barrel.php index f642d6cac09..9b64bbf321d 100644 --- a/src/block/tile/Barrel.php +++ b/src/block/tile/Barrel.php @@ -61,10 +61,6 @@ public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : Inventory{ - return $this->inventory; - } - public function getDefaultName() : string{ return "Barrel"; } diff --git a/src/block/tile/BrewingStand.php b/src/block/tile/BrewingStand.php index 0e4289a8487..4a324935009 100644 --- a/src/block/tile/BrewingStand.php +++ b/src/block/tile/BrewingStand.php @@ -117,10 +117,6 @@ public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : Inventory{ - return $this->inventory; - } - private function checkFuel(Item $item) : void{ $ev = new BrewingFuelUseEvent($this); if(!$item->equals(VanillaItems::BLAZE_POWDER(), true, false)){ diff --git a/src/block/tile/Campfire.php b/src/block/tile/Campfire.php index 89e42fa06d0..a9ad30351f6 100644 --- a/src/block/tile/Campfire.php +++ b/src/block/tile/Campfire.php @@ -68,10 +68,6 @@ public function getInventory() : CampfireInventory{ return $this->inventory; } - public function getRealInventory() : CampfireInventory{ - return $this->inventory; - } - /** * @return int[] * @phpstan-return array diff --git a/src/block/tile/Chest.php b/src/block/tile/Chest.php index 35e9425e1f6..9bc3fdffb3c 100644 --- a/src/block/tile/Chest.php +++ b/src/block/tile/Chest.php @@ -115,15 +115,14 @@ protected function onBlockDestroyedHook() : void{ $this->containerTraitBlockDestroyedHook(); } - public function getInventory() : Inventory|DoubleChestInventory{ - if($this->isPaired() && $this->doubleInventory === null){ - $this->checkPairing(); - } - return $this->doubleInventory instanceof DoubleChestInventory ? $this->doubleInventory : $this->inventory; + public function getInventory() : Inventory{ + return $this->inventory; } - public function getRealInventory() : Inventory{ - return $this->inventory; + public function getDoubleInventory() : ?DoubleChestInventory{ return $this->doubleInventory; } + + public function setDoubleInventory(?DoubleChestInventory $doubleChestInventory) : void{ + $this->doubleInventory = $doubleChestInventory; } protected function checkPairing() : void{ @@ -134,18 +133,7 @@ protected function checkPairing() : void{ }elseif(($pair = $this->getPair()) instanceof Chest){ if(!$pair->isPaired()){ $pair->createPair($this); - $pair->checkPairing(); - } - if($this->doubleInventory === null){ - if($pair->doubleInventory !== null){ - $this->doubleInventory = $pair->doubleInventory; - }else{ - if(($pair->position->x + ($pair->position->z << 15)) > ($this->position->x + ($this->position->z << 15))){ //Order them correctly - $this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($pair->inventory, $this->inventory); - }else{ - $this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($this->inventory, $pair->inventory); - } - } + $this->doubleInventory = $pair->doubleInventory = null; } }else{ $this->doubleInventory = null; diff --git a/src/block/tile/ChiseledBookshelf.php b/src/block/tile/ChiseledBookshelf.php index 06175e27f4d..da7e42c0df4 100644 --- a/src/block/tile/ChiseledBookshelf.php +++ b/src/block/tile/ChiseledBookshelf.php @@ -55,10 +55,6 @@ public function getInventory() : SimpleInventory{ return $this->inventory; } - public function getRealInventory() : SimpleInventory{ - return $this->inventory; - } - public function getLastInteractedSlot() : ?ChiseledBookshelfSlot{ return $this->lastInteractedSlot; } @@ -87,7 +83,7 @@ protected function writeSaveData(CompoundTag $nbt) : void{ protected function loadItems(CompoundTag $tag) : void{ if(($inventoryTag = $tag->getTag(Container::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ - $inventory = $this->getRealInventory(); + $inventory = $this->inventory; $listeners = $inventory->getListeners()->toArray(); $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization @@ -118,7 +114,7 @@ protected function loadItems(CompoundTag $tag) : void{ protected function saveItems(CompoundTag $tag) : void{ $items = []; - foreach($this->getRealInventory()->getContents(true) as $slot => $item){ + foreach($this->inventory->getContents(true) as $slot => $item){ if($item->isNull()){ $items[$slot] = CompoundTag::create() ->setByte(SavedItemStackData::TAG_COUNT, 0) diff --git a/src/block/tile/Container.php b/src/block/tile/Container.php index dd257dd9c5b..1c4e37fec43 100644 --- a/src/block/tile/Container.php +++ b/src/block/tile/Container.php @@ -23,15 +23,12 @@ namespace pocketmine\block\tile; -use pocketmine\inventory\Inventory; use pocketmine\inventory\InventoryHolder; interface Container extends InventoryHolder{ public const TAG_ITEMS = "Items"; public const TAG_LOCK = "Lock"; - public function getRealInventory() : Inventory; - /** * Returns whether this container can be opened by an item with the given custom name. */ diff --git a/src/block/tile/ContainerTrait.php b/src/block/tile/ContainerTrait.php index fdd050a4169..cf69f37b513 100644 --- a/src/block/tile/ContainerTrait.php +++ b/src/block/tile/ContainerTrait.php @@ -25,7 +25,6 @@ use pocketmine\data\bedrock\item\SavedItemStackData; use pocketmine\data\SavedDataLoadingException; -use pocketmine\inventory\Inventory; use pocketmine\item\Item; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; @@ -40,11 +39,9 @@ trait ContainerTrait{ /** @var string|null */ private $lock = null; - abstract public function getRealInventory() : Inventory; - protected function loadItems(CompoundTag $tag) : void{ if(($inventoryTag = $tag->getTag(Container::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ - $inventory = $this->getRealInventory(); + $inventory = $this->getInventory(); $listeners = $inventory->getListeners()->toArray(); $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization @@ -71,7 +68,7 @@ protected function loadItems(CompoundTag $tag) : void{ protected function saveItems(CompoundTag $tag) : void{ $items = []; - foreach($this->getRealInventory()->getContents() as $slot => $item){ + foreach($this->getInventory()->getContents() as $slot => $item){ $items[] = $item->nbtSerialize($slot); } @@ -98,7 +95,7 @@ abstract protected function getPosition() : Position; * @see Tile::onBlockDestroyedHook() */ protected function onBlockDestroyedHook() : void{ - $inv = $this->getRealInventory(); + $inv = $this->getInventory(); $pos = $this->getPosition(); $world = $pos->getWorld(); diff --git a/src/block/tile/Furnace.php b/src/block/tile/Furnace.php index f744e6b41f8..eefc8eb24a6 100644 --- a/src/block/tile/Furnace.php +++ b/src/block/tile/Furnace.php @@ -109,10 +109,6 @@ public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : Inventory{ - return $this->getInventory(); - } - protected function checkFuel(Item $fuel) : void{ $ev = new FurnaceBurnEvent($this, $fuel, $fuel->getFuelTime()); $ev->call(); diff --git a/src/block/tile/Hopper.php b/src/block/tile/Hopper.php index 8e43fa15b73..4a427332356 100644 --- a/src/block/tile/Hopper.php +++ b/src/block/tile/Hopper.php @@ -73,8 +73,4 @@ public function getDefaultName() : string{ public function getInventory() : Inventory{ return $this->inventory; } - - public function getRealInventory() : Inventory{ - return $this->inventory; - } } diff --git a/src/block/tile/ShulkerBox.php b/src/block/tile/ShulkerBox.php index 1c5b74a0806..2e75659157f 100644 --- a/src/block/tile/ShulkerBox.php +++ b/src/block/tile/ShulkerBox.php @@ -111,10 +111,6 @@ public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : Inventory{ - return $this->inventory; - } - public function getDefaultName() : string{ return "Shulker Box"; } From fe1a8d98e7187c43dd830a18133b8d6147eba965 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Nov 2024 23:29:13 +0000 Subject: [PATCH 07/28] namespace tidy --- src/block/Anvil.php | 2 +- src/block/Barrel.php | 2 +- src/block/BrewingStand.php | 2 +- src/block/CartographyTable.php | 2 +- src/block/Chest.php | 4 ++-- src/block/CraftingTable.php | 2 +- src/block/EnchantingTable.php | 2 +- src/block/EnderChest.php | 2 +- src/block/Furnace.php | 2 +- src/block/Hopper.php | 2 +- src/block/Loom.php | 2 +- src/block/ShulkerBox.php | 2 +- src/block/SmithingTable.php | 2 +- src/block/Stonecutter.php | 2 +- .../AnimatedBlockInventoryWindow.php | 2 +- .../{ => window}/AnvilInventoryWindow.php | 2 +- .../{ => window}/BarrelInventoryWindow.php | 2 +- .../{ => window}/BlockInventoryWindow.php | 2 +- .../BrewingStandInventoryWindow.php | 2 +- .../CartographyTableInventoryWindow.php | 2 +- .../{ => window}/ChestInventoryWindow.php | 2 +- .../CraftingTableInventoryWindow.php | 2 +- .../DoubleChestInventoryWindow.php | 2 +- .../EnchantingTableInventoryWindow.php | 2 +- .../EnderChestInventoryWindow.php | 2 +- .../{ => window}/FurnaceInventoryWindow.php | 2 +- .../{ => window}/HopperInventoryWindow.php | 2 +- .../{ => window}/LoomInventoryWindow.php | 2 +- .../ShulkerBoxInventoryWindow.php | 2 +- .../SmithingTableInventoryWindow.php | 2 +- .../StonecutterInventoryWindow.php | 2 +- src/block/tile/BrewingStand.php | 2 +- src/block/tile/Furnace.php | 2 +- src/block/utils/BrewingStandSlot.php | 2 +- .../PlayerEnchantingOptionsRequestEvent.php | 2 +- src/network/mcpe/InventoryManager.php | 22 +++++++++---------- .../mcpe/handler/ItemStackRequestExecutor.php | 3 +-- 37 files changed, 48 insertions(+), 49 deletions(-) rename src/block/inventory/{ => window}/AnimatedBlockInventoryWindow.php (97%) rename src/block/inventory/{ => window}/AnvilInventoryWindow.php (96%) rename src/block/inventory/{ => window}/BarrelInventoryWindow.php (96%) rename src/block/inventory/{ => window}/BlockInventoryWindow.php (96%) rename src/block/inventory/{ => window}/BrewingStandInventoryWindow.php (95%) rename src/block/inventory/{ => window}/CartographyTableInventoryWindow.php (96%) rename src/block/inventory/{ => window}/ChestInventoryWindow.php (97%) rename src/block/inventory/{ => window}/CraftingTableInventoryWindow.php (96%) rename src/block/inventory/{ => window}/DoubleChestInventoryWindow.php (97%) rename src/block/inventory/{ => window}/EnchantingTableInventoryWindow.php (98%) rename src/block/inventory/{ => window}/EnderChestInventoryWindow.php (98%) rename src/block/inventory/{ => window}/FurnaceInventoryWindow.php (97%) rename src/block/inventory/{ => window}/HopperInventoryWindow.php (94%) rename src/block/inventory/{ => window}/LoomInventoryWindow.php (96%) rename src/block/inventory/{ => window}/ShulkerBoxInventoryWindow.php (97%) rename src/block/inventory/{ => window}/SmithingTableInventoryWindow.php (96%) rename src/block/inventory/{ => window}/StonecutterInventoryWindow.php (96%) diff --git a/src/block/Anvil.php b/src/block/Anvil.php index 84013baaddb..1fb71025ccb 100644 --- a/src/block/Anvil.php +++ b/src/block/Anvil.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\AnvilInventoryWindow; +use pocketmine\block\inventory\window\AnvilInventoryWindow; use pocketmine\block\utils\Fallable; use pocketmine\block\utils\FallableTrait; use pocketmine\block\utils\HorizontalFacingTrait; diff --git a/src/block/Barrel.php b/src/block/Barrel.php index b1dcd8124e5..2b781a17d1a 100644 --- a/src/block/Barrel.php +++ b/src/block/Barrel.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\BarrelInventoryWindow; +use pocketmine\block\inventory\window\BarrelInventoryWindow; use pocketmine\block\tile\Barrel as TileBarrel; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; diff --git a/src/block/BrewingStand.php b/src/block/BrewingStand.php index 2ea276e7577..59e439b91e2 100644 --- a/src/block/BrewingStand.php +++ b/src/block/BrewingStand.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\BrewingStandInventoryWindow; +use pocketmine\block\inventory\window\BrewingStandInventoryWindow; use pocketmine\block\tile\BrewingStand as TileBrewingStand; use pocketmine\block\utils\BrewingStandSlot; use pocketmine\block\utils\SupportType; diff --git a/src/block/CartographyTable.php b/src/block/CartographyTable.php index 3338f219b0d..1c3e94096aa 100644 --- a/src/block/CartographyTable.php +++ b/src/block/CartographyTable.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\CartographyTableInventoryWindow; +use pocketmine\block\inventory\window\CartographyTableInventoryWindow; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; diff --git a/src/block/Chest.php b/src/block/Chest.php index 8e0616cd40c..539626535b5 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -23,9 +23,9 @@ namespace pocketmine\block; -use pocketmine\block\inventory\ChestInventoryWindow; use pocketmine\block\inventory\DoubleChestInventory; -use pocketmine\block\inventory\DoubleChestInventoryWindow; +use pocketmine\block\inventory\window\ChestInventoryWindow; +use pocketmine\block\inventory\window\DoubleChestInventoryWindow; use pocketmine\block\tile\Chest as TileChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; diff --git a/src/block/CraftingTable.php b/src/block/CraftingTable.php index 7fd0e43fdff..2b73d221aeb 100644 --- a/src/block/CraftingTable.php +++ b/src/block/CraftingTable.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\CraftingTableInventoryWindow; +use pocketmine\block\inventory\window\CraftingTableInventoryWindow; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; diff --git a/src/block/EnchantingTable.php b/src/block/EnchantingTable.php index 36ec15c8a4f..46ecc070277 100644 --- a/src/block/EnchantingTable.php +++ b/src/block/EnchantingTable.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\EnchantingTableInventoryWindow; +use pocketmine\block\inventory\window\EnchantingTableInventoryWindow; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; diff --git a/src/block/EnderChest.php b/src/block/EnderChest.php index f4634e20095..675bdef58f7 100644 --- a/src/block/EnderChest.php +++ b/src/block/EnderChest.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\EnderChestInventoryWindow; +use pocketmine\block\inventory\window\EnderChestInventoryWindow; use pocketmine\block\tile\EnderChest as TileEnderChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; diff --git a/src/block/Furnace.php b/src/block/Furnace.php index d8394fcc91c..2c4433413bd 100644 --- a/src/block/Furnace.php +++ b/src/block/Furnace.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\FurnaceInventoryWindow; +use pocketmine\block\inventory\window\FurnaceInventoryWindow; use pocketmine\block\tile\Furnace as TileFurnace; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\LightableTrait; diff --git a/src/block/Hopper.php b/src/block/Hopper.php index 45d8b6112e7..8c65e836c9d 100644 --- a/src/block/Hopper.php +++ b/src/block/Hopper.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\HopperInventoryWindow; +use pocketmine\block\inventory\window\HopperInventoryWindow; use pocketmine\block\tile\Hopper as TileHopper; use pocketmine\block\utils\PoweredByRedstoneTrait; use pocketmine\block\utils\SupportType; diff --git a/src/block/Loom.php b/src/block/Loom.php index e9e634ff7b5..d19fc9449d9 100644 --- a/src/block/Loom.php +++ b/src/block/Loom.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\LoomInventoryWindow; +use pocketmine\block\inventory\window\LoomInventoryWindow; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\item\Item; use pocketmine\math\Vector3; diff --git a/src/block/ShulkerBox.php b/src/block/ShulkerBox.php index e6f208ba0d8..a21a373b6f4 100644 --- a/src/block/ShulkerBox.php +++ b/src/block/ShulkerBox.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\ShulkerBoxInventoryWindow; +use pocketmine\block\inventory\window\ShulkerBoxInventoryWindow; use pocketmine\block\tile\ShulkerBox as TileShulkerBox; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\block\utils\SupportType; diff --git a/src/block/SmithingTable.php b/src/block/SmithingTable.php index 5d0f7632495..b96a582d100 100644 --- a/src/block/SmithingTable.php +++ b/src/block/SmithingTable.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\SmithingTableInventoryWindow; +use pocketmine\block\inventory\window\SmithingTableInventoryWindow; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; diff --git a/src/block/Stonecutter.php b/src/block/Stonecutter.php index 0c739e36a36..3c22e74a83e 100644 --- a/src/block/Stonecutter.php +++ b/src/block/Stonecutter.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\StonecutterInventoryWindow; +use pocketmine\block\inventory\window\StonecutterInventoryWindow; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; diff --git a/src/block/inventory/AnimatedBlockInventoryWindow.php b/src/block/inventory/window/AnimatedBlockInventoryWindow.php similarity index 97% rename from src/block/inventory/AnimatedBlockInventoryWindow.php rename to src/block/inventory/window/AnimatedBlockInventoryWindow.php index 3dcf9207fd6..29c70029845 100644 --- a/src/block/inventory/AnimatedBlockInventoryWindow.php +++ b/src/block/inventory/window/AnimatedBlockInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\world\Position; use pocketmine\world\sound\Sound; diff --git a/src/block/inventory/AnvilInventoryWindow.php b/src/block/inventory/window/AnvilInventoryWindow.php similarity index 96% rename from src/block/inventory/AnvilInventoryWindow.php rename to src/block/inventory/window/AnvilInventoryWindow.php index 50705d4f5d3..2b994a90da7 100644 --- a/src/block/inventory/AnvilInventoryWindow.php +++ b/src/block/inventory/window/AnvilInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\inventory\SimpleInventory; use pocketmine\player\Player; diff --git a/src/block/inventory/BarrelInventoryWindow.php b/src/block/inventory/window/BarrelInventoryWindow.php similarity index 96% rename from src/block/inventory/BarrelInventoryWindow.php rename to src/block/inventory/window/BarrelInventoryWindow.php index 89c8b70c773..7dd4d0dde96 100644 --- a/src/block/inventory/BarrelInventoryWindow.php +++ b/src/block/inventory/window/BarrelInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\block\Barrel; use pocketmine\world\Position; diff --git a/src/block/inventory/BlockInventoryWindow.php b/src/block/inventory/window/BlockInventoryWindow.php similarity index 96% rename from src/block/inventory/BlockInventoryWindow.php rename to src/block/inventory/window/BlockInventoryWindow.php index 23d27647e4b..b2a84a85121 100644 --- a/src/block/inventory/BlockInventoryWindow.php +++ b/src/block/inventory/window/BlockInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\inventory\Inventory; use pocketmine\player\InventoryWindow; diff --git a/src/block/inventory/BrewingStandInventoryWindow.php b/src/block/inventory/window/BrewingStandInventoryWindow.php similarity index 95% rename from src/block/inventory/BrewingStandInventoryWindow.php rename to src/block/inventory/window/BrewingStandInventoryWindow.php index eae68f60c6e..ee9af83c653 100644 --- a/src/block/inventory/BrewingStandInventoryWindow.php +++ b/src/block/inventory/window/BrewingStandInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; final class BrewingStandInventoryWindow extends BlockInventoryWindow{ public const SLOT_INGREDIENT = 0; diff --git a/src/block/inventory/CartographyTableInventoryWindow.php b/src/block/inventory/window/CartographyTableInventoryWindow.php similarity index 96% rename from src/block/inventory/CartographyTableInventoryWindow.php rename to src/block/inventory/window/CartographyTableInventoryWindow.php index 90f06edb145..22d2d5c430f 100644 --- a/src/block/inventory/CartographyTableInventoryWindow.php +++ b/src/block/inventory/window/CartographyTableInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\inventory\SimpleInventory; use pocketmine\player\Player; diff --git a/src/block/inventory/ChestInventoryWindow.php b/src/block/inventory/window/ChestInventoryWindow.php similarity index 97% rename from src/block/inventory/ChestInventoryWindow.php rename to src/block/inventory/window/ChestInventoryWindow.php index be61fc22b1f..6b3dda978c0 100644 --- a/src/block/inventory/ChestInventoryWindow.php +++ b/src/block/inventory/window/ChestInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\network\mcpe\protocol\BlockEventPacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; diff --git a/src/block/inventory/CraftingTableInventoryWindow.php b/src/block/inventory/window/CraftingTableInventoryWindow.php similarity index 96% rename from src/block/inventory/CraftingTableInventoryWindow.php rename to src/block/inventory/window/CraftingTableInventoryWindow.php index 905cd1ccc54..46e5485e5fb 100644 --- a/src/block/inventory/CraftingTableInventoryWindow.php +++ b/src/block/inventory/window/CraftingTableInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\crafting\CraftingGrid; use pocketmine\player\Player; diff --git a/src/block/inventory/DoubleChestInventoryWindow.php b/src/block/inventory/window/DoubleChestInventoryWindow.php similarity index 97% rename from src/block/inventory/DoubleChestInventoryWindow.php rename to src/block/inventory/window/DoubleChestInventoryWindow.php index e65312b1cf7..5455b1f5135 100644 --- a/src/block/inventory/DoubleChestInventoryWindow.php +++ b/src/block/inventory/window/DoubleChestInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\inventory\Inventory; use pocketmine\player\Player; diff --git a/src/block/inventory/EnchantingTableInventoryWindow.php b/src/block/inventory/window/EnchantingTableInventoryWindow.php similarity index 98% rename from src/block/inventory/EnchantingTableInventoryWindow.php rename to src/block/inventory/window/EnchantingTableInventoryWindow.php index 660d5c707b3..68ef0084c51 100644 --- a/src/block/inventory/EnchantingTableInventoryWindow.php +++ b/src/block/inventory/window/EnchantingTableInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\event\player\PlayerEnchantingOptionsRequestEvent; use pocketmine\inventory\CallbackInventoryListener; diff --git a/src/block/inventory/EnderChestInventoryWindow.php b/src/block/inventory/window/EnderChestInventoryWindow.php similarity index 98% rename from src/block/inventory/EnderChestInventoryWindow.php rename to src/block/inventory/window/EnderChestInventoryWindow.php index ad290134d64..97d951c9321 100644 --- a/src/block/inventory/EnderChestInventoryWindow.php +++ b/src/block/inventory/window/EnderChestInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\block\tile\EnderChest; use pocketmine\network\mcpe\protocol\BlockEventPacket; diff --git a/src/block/inventory/FurnaceInventoryWindow.php b/src/block/inventory/window/FurnaceInventoryWindow.php similarity index 97% rename from src/block/inventory/FurnaceInventoryWindow.php rename to src/block/inventory/window/FurnaceInventoryWindow.php index 98e2ac360c4..11d36324c76 100644 --- a/src/block/inventory/FurnaceInventoryWindow.php +++ b/src/block/inventory/window/FurnaceInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\crafting\FurnaceType; use pocketmine\inventory\Inventory; diff --git a/src/block/inventory/HopperInventoryWindow.php b/src/block/inventory/window/HopperInventoryWindow.php similarity index 94% rename from src/block/inventory/HopperInventoryWindow.php rename to src/block/inventory/window/HopperInventoryWindow.php index 4ddf981c942..b89f688720f 100644 --- a/src/block/inventory/HopperInventoryWindow.php +++ b/src/block/inventory/window/HopperInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; final class HopperInventoryWindow extends BlockInventoryWindow{ diff --git a/src/block/inventory/LoomInventoryWindow.php b/src/block/inventory/window/LoomInventoryWindow.php similarity index 96% rename from src/block/inventory/LoomInventoryWindow.php rename to src/block/inventory/window/LoomInventoryWindow.php index 1140cdf1a85..ceb27ac1ac6 100644 --- a/src/block/inventory/LoomInventoryWindow.php +++ b/src/block/inventory/window/LoomInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\inventory\SimpleInventory; use pocketmine\player\Player; diff --git a/src/block/inventory/ShulkerBoxInventoryWindow.php b/src/block/inventory/window/ShulkerBoxInventoryWindow.php similarity index 97% rename from src/block/inventory/ShulkerBoxInventoryWindow.php rename to src/block/inventory/window/ShulkerBoxInventoryWindow.php index a6e7b0c70cf..19d1cba18e3 100644 --- a/src/block/inventory/ShulkerBoxInventoryWindow.php +++ b/src/block/inventory/window/ShulkerBoxInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\network\mcpe\protocol\BlockEventPacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; diff --git a/src/block/inventory/SmithingTableInventoryWindow.php b/src/block/inventory/window/SmithingTableInventoryWindow.php similarity index 96% rename from src/block/inventory/SmithingTableInventoryWindow.php rename to src/block/inventory/window/SmithingTableInventoryWindow.php index 1d5bcf7ddef..7cb850ef0be 100644 --- a/src/block/inventory/SmithingTableInventoryWindow.php +++ b/src/block/inventory/window/SmithingTableInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\inventory\SimpleInventory; use pocketmine\player\Player; diff --git a/src/block/inventory/StonecutterInventoryWindow.php b/src/block/inventory/window/StonecutterInventoryWindow.php similarity index 96% rename from src/block/inventory/StonecutterInventoryWindow.php rename to src/block/inventory/window/StonecutterInventoryWindow.php index c37b3bb9416..a7a29a97430 100644 --- a/src/block/inventory/StonecutterInventoryWindow.php +++ b/src/block/inventory/window/StonecutterInventoryWindow.php @@ -21,7 +21,7 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\inventory\SimpleInventory; use pocketmine\player\Player; diff --git a/src/block/tile/BrewingStand.php b/src/block/tile/BrewingStand.php index 4a324935009..87b53eefbcb 100644 --- a/src/block/tile/BrewingStand.php +++ b/src/block/tile/BrewingStand.php @@ -23,7 +23,7 @@ namespace pocketmine\block\tile; -use pocketmine\block\inventory\BrewingStandInventoryWindow; +use pocketmine\block\inventory\window\BrewingStandInventoryWindow; use pocketmine\crafting\BrewingRecipe; use pocketmine\event\block\BrewingFuelUseEvent; use pocketmine\event\block\BrewItemEvent; diff --git a/src/block/tile/Furnace.php b/src/block/tile/Furnace.php index eefc8eb24a6..e036a606e3f 100644 --- a/src/block/tile/Furnace.php +++ b/src/block/tile/Furnace.php @@ -24,7 +24,7 @@ namespace pocketmine\block\tile; use pocketmine\block\Furnace as BlockFurnace; -use pocketmine\block\inventory\FurnaceInventoryWindow; +use pocketmine\block\inventory\window\FurnaceInventoryWindow; use pocketmine\crafting\FurnaceRecipe; use pocketmine\crafting\FurnaceType; use pocketmine\event\inventory\FurnaceBurnEvent; diff --git a/src/block/utils/BrewingStandSlot.php b/src/block/utils/BrewingStandSlot.php index edf8374fa3c..51f0df50190 100644 --- a/src/block/utils/BrewingStandSlot.php +++ b/src/block/utils/BrewingStandSlot.php @@ -23,7 +23,7 @@ namespace pocketmine\block\utils; -use pocketmine\block\inventory\BrewingStandInventoryWindow; +use pocketmine\block\inventory\window\BrewingStandInventoryWindow; enum BrewingStandSlot{ case EAST; diff --git a/src/event/player/PlayerEnchantingOptionsRequestEvent.php b/src/event/player/PlayerEnchantingOptionsRequestEvent.php index e2918352097..14e0ed73c4a 100644 --- a/src/event/player/PlayerEnchantingOptionsRequestEvent.php +++ b/src/event/player/PlayerEnchantingOptionsRequestEvent.php @@ -23,7 +23,7 @@ namespace pocketmine\event\player; -use pocketmine\block\inventory\EnchantingTableInventoryWindow; +use pocketmine\block\inventory\window\EnchantingTableInventoryWindow; use pocketmine\event\Cancellable; use pocketmine\event\CancellableTrait; use pocketmine\item\enchantment\EnchantingOption; diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index 91be5336207..b9c095f8c5c 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -23,17 +23,17 @@ namespace pocketmine\network\mcpe; -use pocketmine\block\inventory\AnvilInventoryWindow; -use pocketmine\block\inventory\BlockInventoryWindow; -use pocketmine\block\inventory\BrewingStandInventoryWindow; -use pocketmine\block\inventory\CartographyTableInventoryWindow; -use pocketmine\block\inventory\CraftingTableInventoryWindow; -use pocketmine\block\inventory\EnchantingTableInventoryWindow; -use pocketmine\block\inventory\FurnaceInventoryWindow; -use pocketmine\block\inventory\HopperInventoryWindow; -use pocketmine\block\inventory\LoomInventoryWindow; -use pocketmine\block\inventory\SmithingTableInventoryWindow; -use pocketmine\block\inventory\StonecutterInventoryWindow; +use pocketmine\block\inventory\window\AnvilInventoryWindow; +use pocketmine\block\inventory\window\BlockInventoryWindow; +use pocketmine\block\inventory\window\BrewingStandInventoryWindow; +use pocketmine\block\inventory\window\CartographyTableInventoryWindow; +use pocketmine\block\inventory\window\CraftingTableInventoryWindow; +use pocketmine\block\inventory\window\EnchantingTableInventoryWindow; +use pocketmine\block\inventory\window\FurnaceInventoryWindow; +use pocketmine\block\inventory\window\HopperInventoryWindow; +use pocketmine\block\inventory\window\LoomInventoryWindow; +use pocketmine\block\inventory\window\SmithingTableInventoryWindow; +use pocketmine\block\inventory\window\StonecutterInventoryWindow; use pocketmine\crafting\FurnaceType; use pocketmine\data\bedrock\EnchantmentIdMap; use pocketmine\inventory\Inventory; diff --git a/src/network/mcpe/handler/ItemStackRequestExecutor.php b/src/network/mcpe/handler/ItemStackRequestExecutor.php index d58ec622300..08b56a891fc 100644 --- a/src/network/mcpe/handler/ItemStackRequestExecutor.php +++ b/src/network/mcpe/handler/ItemStackRequestExecutor.php @@ -23,8 +23,7 @@ namespace pocketmine\network\mcpe\handler; -use pocketmine\block\inventory\EnchantingTableInventoryWindow; -use pocketmine\inventory\Inventory; +use pocketmine\block\inventory\window\EnchantingTableInventoryWindow; use pocketmine\inventory\transaction\action\CreateItemAction; use pocketmine\inventory\transaction\action\DestroyItemAction; use pocketmine\inventory\transaction\action\DropItemAction; From 17383553577679d47a44ed495e2da30372d25a6e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Nov 2024 23:43:32 +0000 Subject: [PATCH 08/28] Remove potentially problematic ephemeral window creation this is *probably* fine, but best avoided. --- src/network/mcpe/handler/InGamePacketHandler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 6a61a5c0fea..cd2b6ae66cd 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -462,8 +462,8 @@ private function handleNormalTransaction(NormalTransactionData $data, int $itemS $droppedItem = $sourceSlotItem->pop($droppedCount); $builder = new TransactionBuilder(); - //TODO: this probably shouldn't be creating an ephemeral window here - it works, but no idea what side effects it might have on the permanent window - $builder->getActionBuilder(new PlayerInventoryWindow($this->player, $inventory, PlayerInventoryWindow::TYPE_INVENTORY))->setItem($sourceSlot, $sourceSlotItem); + $window = $this->inventoryManager->getInventoryWindow($inventory) ?? throw new AssumptionFailedError("This should never happen"); + $builder->getActionBuilder($window)->setItem($sourceSlot, $sourceSlotItem); $builder->addAction(new DropItemAction($droppedItem)); $transaction = new InventoryTransaction($this->player, $builder->generateActions()); From 7f58122ac68e1237afbf23f8e4d32f5795aea472 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Nov 2024 23:43:51 +0000 Subject: [PATCH 09/28] Avoid unnecessary repeated calls --- src/inventory/transaction/SlotChangeActionBuilder.php | 3 ++- src/network/mcpe/InventoryManager.php | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/inventory/transaction/SlotChangeActionBuilder.php b/src/inventory/transaction/SlotChangeActionBuilder.php index 90fe2111aac..7fe2490d9a7 100644 --- a/src/inventory/transaction/SlotChangeActionBuilder.php +++ b/src/inventory/transaction/SlotChangeActionBuilder.php @@ -106,9 +106,10 @@ public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : i */ public function generateActions() : array{ $result = []; + $inventory = $this->inventoryWindow->getInventory(); foreach($this->changedSlots as $index => $newItem){ if($newItem !== null){ - $oldItem = $this->inventoryWindow->getInventory()->getItem($index); + $oldItem = $inventory->getItem($index); if(!$newItem->equalsExact($oldItem)){ $result[] = new SlotChangeAction($this->inventoryWindow, $index, $oldItem, $newItem); } diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index b9c095f8c5c..0334527af75 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -615,7 +615,8 @@ public function onContentChange(Inventory $inventory, array $oldContents) : void } private function syncContents(InventoryWindow $window) : void{ - $entry = $this->getEntry($window->getInventory()); + $inventory = $window->getInventory(); + $entry = $this->getEntry($inventory); if($entry === null){ //this can happen when an inventory changed during InventoryCloseEvent, or when a temporary inventory //is cleared before removal. @@ -631,7 +632,7 @@ private function syncContents(InventoryWindow $window) : void{ $entry->pendingSyncs = []; $contents = []; $typeConverter = $this->session->getTypeConverter(); - foreach($window->getInventory()->getContents(true) as $slot => $item){ + foreach($inventory->getContents(true) as $slot => $item){ $itemStack = $typeConverter->coreItemStackToNet($item); $info = $this->trackItemStack($entry, $slot, $itemStack, null); $contents[] = new ItemStackWrapper($info->getStackId(), $itemStack); From 6aa4e4c21f2b4a004a9e9926e39f1bd27730df15 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Nov 2024 23:51:41 +0000 Subject: [PATCH 10/28] CS --- src/network/mcpe/handler/InGamePacketHandler.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index cd2b6ae66cd..3dfc29d78d5 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -101,7 +101,6 @@ use pocketmine\network\mcpe\protocol\types\PlayerBlockActionWithBlockInfo; use pocketmine\network\PacketHandlingException; use pocketmine\player\Player; -use pocketmine\player\PlayerInventoryWindow; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Limits; use pocketmine\utils\TextFormat; From 4dcc14e0a1c7610815ee3824854f0009821480af Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 25 Nov 2024 10:55:35 +0000 Subject: [PATCH 11/28] Integrate block container animations and SFX into Block classes by way of AnimatedContainer interface this allows getting rid of several container window classes. we should probably look into adding more info to the BlockInventoryWindow to make the type easier to identify, though. now that holder is tracked by an ephemeral window, we can put whatever we like in there. --- src/block/Barrel.php | 29 +++++- src/block/Chest.php | 98 +++++++++++++++---- src/block/EnderChest.php | 57 ++++++++++- src/block/ShulkerBox.php | 29 +++++- .../window/AnimatedBlockInventoryWindow.php | 65 ------------ .../window/BarrelInventoryWindow.php | 49 ---------- .../inventory/window/BlockInventoryWindow.php | 17 ++++ .../inventory/window/ChestInventoryWindow.php | 48 --------- .../window/DoubleChestInventoryWindow.php | 10 +- .../window/EnderChestInventoryWindow.php | 73 -------------- .../window/ShulkerBoxInventoryWindow.php | 46 --------- src/player/Player.php | 3 + 12 files changed, 206 insertions(+), 318 deletions(-) delete mode 100644 src/block/inventory/window/AnimatedBlockInventoryWindow.php delete mode 100644 src/block/inventory/window/BarrelInventoryWindow.php delete mode 100644 src/block/inventory/window/ChestInventoryWindow.php delete mode 100644 src/block/inventory/window/EnderChestInventoryWindow.php delete mode 100644 src/block/inventory/window/ShulkerBoxInventoryWindow.php diff --git a/src/block/Barrel.php b/src/block/Barrel.php index 2b781a17d1a..1b64e814c58 100644 --- a/src/block/Barrel.php +++ b/src/block/Barrel.php @@ -23,8 +23,10 @@ namespace pocketmine\block; -use pocketmine\block\inventory\window\BarrelInventoryWindow; +use pocketmine\block\inventory\window\BlockInventoryWindow; use pocketmine\block\tile\Barrel as TileBarrel; +use pocketmine\block\utils\AnimatedContainer; +use pocketmine\block\utils\AnimatedContainerTrait; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -32,9 +34,14 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; +use pocketmine\world\Position; +use pocketmine\world\sound\BarrelCloseSound; +use pocketmine\world\sound\BarrelOpenSound; +use pocketmine\world\sound\Sound; use function abs; -class Barrel extends Opaque{ +class Barrel extends Opaque implements AnimatedContainer{ + use AnimatedContainerTrait; use AnyFacingTrait; protected bool $open = false; @@ -82,7 +89,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } - $player->setCurrentWindow(new BarrelInventoryWindow($player, $barrel->getInventory(), $this->position)); + $player->setCurrentWindow(new BlockInventoryWindow($player, $barrel->getInventory(), $this->position)); } } @@ -92,4 +99,20 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player public function getFuelTime() : int{ return 300; } + + protected function getContainerOpenSound() : Sound{ + return new BarrelOpenSound(); + } + + protected function getContainerCloseSound() : Sound{ + return new BarrelCloseSound(); + } + + protected function doContainerAnimation(Position $position, bool $isOpen) : void{ + $world = $position->getWorld(); + $block = $world->getBlock($position); + if($block instanceof Barrel){ + $world->setBlock($position, $block->setOpen($isOpen)); + } + } } diff --git a/src/block/Chest.php b/src/block/Chest.php index 539626535b5..a688df0bd43 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -24,9 +24,11 @@ namespace pocketmine\block; use pocketmine\block\inventory\DoubleChestInventory; -use pocketmine\block\inventory\window\ChestInventoryWindow; +use pocketmine\block\inventory\window\BlockInventoryWindow; use pocketmine\block\inventory\window\DoubleChestInventoryWindow; use pocketmine\block\tile\Chest as TileChest; +use pocketmine\block\utils\AnimatedContainer; +use pocketmine\block\utils\AnimatedContainerTrait; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; use pocketmine\event\block\ChestPairEvent; @@ -34,9 +36,17 @@ use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; +use pocketmine\network\mcpe\protocol\BlockEventPacket; +use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\player\Player; +use pocketmine\world\Position; +use pocketmine\world\sound\ChestCloseSound; +use pocketmine\world\sound\ChestOpenSound; +use pocketmine\world\sound\Sound; +use function count; -class Chest extends Transparent{ +class Chest extends Transparent implements AnimatedContainer{ + use AnimatedContainerTrait; use FacesOppositePlacingPlayerTrait; /** @@ -51,6 +61,25 @@ public function getSupportType(int $facing) : SupportType{ return SupportType::NONE; } + /** + * @phpstan-return array{bool, TileChest}|null + */ + private function locatePair(Position $position) : ?array{ + $world = $position->getWorld(); + $tile = $world->getTile($position); + if($tile instanceof TileChest){ + foreach([false, true] as $clockwise){ + $side = Facing::rotateY($this->facing, $clockwise); + $c = $position->getSide($side); + $pair = $world->getTile($c); + if($pair instanceof TileChest && $pair->isPaired() && $pair->getPair() === $tile){ + return [$clockwise, $pair]; + } + } + } + return null; + } + public function onPostPlace() : void{ $world = $this->position->getWorld(); $tile = $world->getTile($this->position); @@ -90,27 +119,23 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $window = null; if($chest->isPaired()){ - foreach([false, true] as $clockwise){ - $sideFacing = Facing::rotateY($this->facing, $clockwise); - $side = $this->position->getSide($sideFacing); - $pair = $world->getTile($side); - if($pair instanceof TileChest && $pair->getPair() === $chest){ - [$left, $right] = $clockwise ? [$pair, $chest] : [$chest, $pair]; - - $doubleInventory = $left->getDoubleInventory() ?? $right->getDoubleInventory(); - if($doubleInventory === null){ - $doubleInventory = new DoubleChestInventory($left->getInventory(), $right->getInventory()); - $left->setDoubleInventory($doubleInventory); - $right->setDoubleInventory($doubleInventory); - } - - $window = new DoubleChestInventoryWindow($player, $doubleInventory, $left->getPosition(), $right->getPosition()); - break; + $info = $this->locatePair($this->position); + if($info !== null){ + [$clockwise, $pair] = $info; + [$left, $right] = $clockwise ? [$pair, $chest] : [$chest, $pair]; + + $doubleInventory = $left->getDoubleInventory() ?? $right->getDoubleInventory(); + if($doubleInventory === null){ + $doubleInventory = new DoubleChestInventory($left->getInventory(), $right->getInventory()); + $left->setDoubleInventory($doubleInventory); + $right->setDoubleInventory($doubleInventory); } + + $window = new DoubleChestInventoryWindow($player, $doubleInventory, $left->getPosition(), $right->getPosition()); } } - $player->setCurrentWindow($window ?? new ChestInventoryWindow($player, $chest->getInventory(), $this->position)); + $player->setCurrentWindow($window ?? new BlockInventoryWindow($player, $chest->getInventory(), $this->position)); } } @@ -120,4 +145,39 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player public function getFuelTime() : int{ return 300; } + + protected function getContainerViewerCount() : int{ + $tile = $this->position->getWorld()->getTile($this->position); + if($tile instanceof TileChest){ + $inventory = $tile->getDoubleInventory() ?? $tile->getInventory(); + return count($inventory->getViewers()); + } + return 0; + } + + protected function getContainerOpenSound() : Sound{ + return new ChestOpenSound(); + } + + protected function getContainerCloseSound() : Sound{ + return new ChestCloseSound(); + } + + protected function doContainerAnimation(Position $position, bool $isOpen) : void{ + //event ID is always 1 for a chest + //TODO: we probably shouldn't be sending a packet directly here, but it doesn't fit anywhere into existing systems + $position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); + } + + protected function doContainerEffects(bool $isOpen) : void{ + $this->doContainerAnimation($this->position, $isOpen); + $this->playContainerSound($this->position, $isOpen); + + $pairInfo = $this->locatePair($this->position); + if($pairInfo !== null){ + [, $pair] = $pairInfo; + $this->doContainerAnimation($pair->getPosition(), $isOpen); + $this->playContainerSound($pair->getPosition(), $isOpen); + } + } } diff --git a/src/block/EnderChest.php b/src/block/EnderChest.php index 675bdef58f7..c5901d3f4bb 100644 --- a/src/block/EnderChest.php +++ b/src/block/EnderChest.php @@ -23,17 +23,29 @@ namespace pocketmine\block; -use pocketmine\block\inventory\window\EnderChestInventoryWindow; +use pocketmine\block\inventory\window\BlockInventoryWindow; use pocketmine\block\tile\EnderChest as TileEnderChest; +use pocketmine\block\utils\AnimatedContainer; +use pocketmine\block\utils\AnimatedContainerTrait; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; +use pocketmine\network\mcpe\protocol\BlockEventPacket; +use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\player\Player; +use pocketmine\world\Position; +use pocketmine\world\sound\EnderChestCloseSound; +use pocketmine\world\sound\EnderChestOpenSound; +use pocketmine\world\sound\Sound; -class EnderChest extends Transparent{ +class EnderChest extends Transparent implements AnimatedContainer{ + use AnimatedContainerTrait { + onContainerOpen as private traitOnContainerOpen; + onContainerClose as private traitOnContainerClose; + } use FacesOppositePlacingPlayerTrait; public function getLightLevel() : int{ @@ -56,7 +68,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player if($player instanceof Player){ $enderChest = $this->position->getWorld()->getTile($this->position); if($enderChest instanceof TileEnderChest && $this->getSide(Facing::UP)->isTransparent()){ - $player->setCurrentWindow(new EnderChestInventoryWindow($player, $player->getEnderInventory(), $this->position)); + $player->setCurrentWindow(new BlockInventoryWindow($player, $player->getEnderInventory(), $this->position)); } } @@ -72,4 +84,43 @@ public function getDropsForCompatibleTool(Item $item) : array{ public function isAffectedBySilkTouch() : bool{ return true; } + + protected function getContainerViewerCount() : int{ + $enderChest = $this->position->getWorld()->getTile($this->position); + if(!$enderChest instanceof TileEnderChest){ + return 0; + } + return $enderChest->getViewerCount(); + } + + private function updateContainerViewerCount(int $amount) : void{ + $enderChest = $this->position->getWorld()->getTile($this->position); + if($enderChest instanceof TileEnderChest){ + $enderChest->setViewerCount($enderChest->getViewerCount() + $amount); + } + } + + protected function getContainerOpenSound() : Sound{ + return new EnderChestOpenSound(); + } + + protected function getContainerCloseSound() : Sound{ + return new EnderChestCloseSound(); + } + + protected function doContainerAnimation(Position $position, bool $isOpen) : void{ + //event ID is always 1 for a chest + //TODO: we probably shouldn't be sending a packet directly here, but it doesn't fit anywhere into existing systems + $position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); + } + + public function onContainerOpen() : void{ + $this->updateContainerViewerCount(1); + $this->traitOnContainerOpen(); + } + + public function onContainerClose() : void{ + $this->traitOnContainerClose(); + $this->updateContainerViewerCount(-1); + } } diff --git a/src/block/ShulkerBox.php b/src/block/ShulkerBox.php index a21a373b6f4..17003396336 100644 --- a/src/block/ShulkerBox.php +++ b/src/block/ShulkerBox.php @@ -23,17 +23,26 @@ namespace pocketmine\block; -use pocketmine\block\inventory\window\ShulkerBoxInventoryWindow; +use pocketmine\block\inventory\window\BlockInventoryWindow; use pocketmine\block\tile\ShulkerBox as TileShulkerBox; +use pocketmine\block\utils\AnimatedContainer; +use pocketmine\block\utils\AnimatedContainerTrait; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; use pocketmine\math\Vector3; +use pocketmine\network\mcpe\protocol\BlockEventPacket; +use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; +use pocketmine\world\Position; +use pocketmine\world\sound\ShulkerBoxCloseSound; +use pocketmine\world\sound\ShulkerBoxOpenSound; +use pocketmine\world\sound\Sound; -class ShulkerBox extends Opaque{ +class ShulkerBox extends Opaque implements AnimatedContainer{ + use AnimatedContainerTrait; use AnyFacingTrait; protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ @@ -106,7 +115,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } - $player->setCurrentWindow(new ShulkerBoxInventoryWindow($player, $shulker->getInventory(), $this->position)); + $player->setCurrentWindow(new BlockInventoryWindow($player, $shulker->getInventory(), $this->position)); } } @@ -116,4 +125,18 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player public function getSupportType(int $facing) : SupportType{ return SupportType::NONE; } + + protected function getContainerOpenSound() : Sound{ + return new ShulkerBoxOpenSound(); + } + + protected function getContainerCloseSound() : Sound{ + return new ShulkerBoxCloseSound(); + } + + protected function doContainerAnimation(Position $position, bool $isOpen) : void{ + //event ID is always 1 for a chest + //TODO: we probably shouldn't be sending a packet directly here, but it doesn't fit anywhere into existing systems + $position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); + } } diff --git a/src/block/inventory/window/AnimatedBlockInventoryWindow.php b/src/block/inventory/window/AnimatedBlockInventoryWindow.php deleted file mode 100644 index 29c70029845..00000000000 --- a/src/block/inventory/window/AnimatedBlockInventoryWindow.php +++ /dev/null @@ -1,65 +0,0 @@ -inventory->getViewers()); - } - - abstract protected function getOpenSound() : Sound; - - abstract protected function getCloseSound() : Sound; - - abstract protected function animateBlock(Position $position, bool $isOpen) : void; - - protected function playSound(Position $position, bool $isOpen) : void{ - $position->getWorld()->addSound($position->add(0.5, 0.5, 0.5), $isOpen ? $this->getOpenSound() : $this->getCloseSound()); - } - - protected function doBlockEffects(bool $isOpen) : void{ - $position = $this->holder; - $this->animateBlock($position, $isOpen); - $this->playSound($position, $isOpen); - } - - public function onOpen() : void{ - parent::onOpen(); - if($this->getViewerCount() === 1){ - $this->doBlockEffects(true); - } - } - - public function onClose() : void{ - if($this->getViewerCount() === 1){ - $this->doBlockEffects(false); - } - parent::onClose(); - } -} diff --git a/src/block/inventory/window/BarrelInventoryWindow.php b/src/block/inventory/window/BarrelInventoryWindow.php deleted file mode 100644 index 7dd4d0dde96..00000000000 --- a/src/block/inventory/window/BarrelInventoryWindow.php +++ /dev/null @@ -1,49 +0,0 @@ -getWorld(); - $block = $world->getBlock($position); - if($block instanceof Barrel){ - $world->setBlock($position, $block->setOpen($isOpen)); - } - } -} diff --git a/src/block/inventory/window/BlockInventoryWindow.php b/src/block/inventory/window/BlockInventoryWindow.php index b2a84a85121..149c2e81426 100644 --- a/src/block/inventory/window/BlockInventoryWindow.php +++ b/src/block/inventory/window/BlockInventoryWindow.php @@ -23,6 +23,7 @@ namespace pocketmine\block\inventory\window; +use pocketmine\block\utils\AnimatedContainer; use pocketmine\inventory\Inventory; use pocketmine\player\InventoryWindow; use pocketmine\player\Player; @@ -39,4 +40,20 @@ public function __construct( } public function getHolder() : Position{ return $this->holder; } + + public function onOpen() : void{ + parent::onOpen(); + $block = $this->holder->getWorld()->getBlock($this->holder); + if($block instanceof AnimatedContainer){ + $block->onContainerOpen(); + } + } + + public function onClose() : void{ + $block = $this->holder->getWorld()->getBlock($this->holder); + if($block instanceof AnimatedContainer){ + $block->onContainerClose(); + } + parent::onClose(); + } } diff --git a/src/block/inventory/window/ChestInventoryWindow.php b/src/block/inventory/window/ChestInventoryWindow.php deleted file mode 100644 index 6b3dda978c0..00000000000 --- a/src/block/inventory/window/ChestInventoryWindow.php +++ /dev/null @@ -1,48 +0,0 @@ -getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); - } -} diff --git a/src/block/inventory/window/DoubleChestInventoryWindow.php b/src/block/inventory/window/DoubleChestInventoryWindow.php index 5455b1f5135..801a6ff9d0f 100644 --- a/src/block/inventory/window/DoubleChestInventoryWindow.php +++ b/src/block/inventory/window/DoubleChestInventoryWindow.php @@ -27,7 +27,7 @@ use pocketmine\player\Player; use pocketmine\world\Position; -final class DoubleChestInventoryWindow extends ChestInventoryWindow{ +final class DoubleChestInventoryWindow extends BlockInventoryWindow{ public function __construct( Player $viewer, @@ -41,12 +41,4 @@ public function __construct( public function getLeft() : Position{ return $this->left; } public function getRight() : Position{ return $this->right; } - - protected function doBlockEffects(bool $isOpen) : void{ - $this->animateBlock($this->left, $isOpen); - $this->animateBlock($this->right, $isOpen); - - $this->playSound($this->left, $isOpen); - $this->playSound($this->right, $isOpen); - } } diff --git a/src/block/inventory/window/EnderChestInventoryWindow.php b/src/block/inventory/window/EnderChestInventoryWindow.php deleted file mode 100644 index 97d951c9321..00000000000 --- a/src/block/inventory/window/EnderChestInventoryWindow.php +++ /dev/null @@ -1,73 +0,0 @@ -holder->getWorld()->getTile($this->getHolder()); - if(!$enderChest instanceof EnderChest){ - return 0; - } - return $enderChest->getViewerCount(); - } - - private function updateViewerCount(int $amount) : void{ - $enderChest = $this->holder->getWorld()->getTile($this->getHolder()); - if($enderChest instanceof EnderChest){ - $enderChest->setViewerCount($enderChest->getViewerCount() + $amount); - } - } - - protected function getOpenSound() : Sound{ - return new EnderChestOpenSound(); - } - - protected function getCloseSound() : Sound{ - return new EnderChestCloseSound(); - } - - protected function animateBlock(Position $position, bool $isOpen) : void{ - //event ID is always 1 for a chest - $position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); - } - - public function onOpen() : void{ - parent::onOpen(); - $this->updateViewerCount(1); - } - - public function onClose() : void{ - parent::onClose(); - $this->updateViewerCount(-1); - } -} diff --git a/src/block/inventory/window/ShulkerBoxInventoryWindow.php b/src/block/inventory/window/ShulkerBoxInventoryWindow.php deleted file mode 100644 index 19d1cba18e3..00000000000 --- a/src/block/inventory/window/ShulkerBoxInventoryWindow.php +++ /dev/null @@ -1,46 +0,0 @@ -getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); - } -} diff --git a/src/player/Player.php b/src/player/Player.php index 3b54a2b869a..e87fb662d6f 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -2693,6 +2693,9 @@ public function setCurrentWindow(InventoryWindow $window) : bool{ if($window === $this->currentWindow){ return true; } + if($window->getViewer() !== $this){ + throw new \InvalidArgumentException("Cannot reuse InventoryWindow instances, please create a new one for each player"); + } $ev = new InventoryOpenEvent($window, $this); $ev->call(); if($ev->isCancelled()){ From edf4e9d3335e92f15852868f20ce532767c1f8af Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 25 Nov 2024 10:56:26 +0000 Subject: [PATCH 12/28] ... --- src/block/utils/AnimatedContainer.php | 38 ++++++++++++ src/block/utils/AnimatedContainerTrait.php | 71 ++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/block/utils/AnimatedContainer.php create mode 100644 src/block/utils/AnimatedContainerTrait.php diff --git a/src/block/utils/AnimatedContainer.php b/src/block/utils/AnimatedContainer.php new file mode 100644 index 00000000000..9af901f18c0 --- /dev/null +++ b/src/block/utils/AnimatedContainer.php @@ -0,0 +1,38 @@ +getPosition(); + $tile = $position->getWorld()->getTile($position); + if($tile instanceof InventoryHolder){ + return count($tile->getInventory()->getViewers()); + } + return 0; + } + + abstract protected function getContainerOpenSound() : Sound; + + abstract protected function getContainerCloseSound() : Sound; + + abstract protected function doContainerAnimation(Position $position, bool $isOpen) : void; + + protected function playContainerSound(Position $position, bool $isOpen) : void{ + $position->getWorld()->addSound($position->add(0.5, 0.5, 0.5), $isOpen ? $this->getContainerOpenSound() : $this->getContainerCloseSound()); + } + + abstract protected function getPosition() : Position; + + protected function doContainerEffects(bool $isOpen) : void{ + $position = $this->getPosition(); + $this->doContainerAnimation($position, $isOpen); + $this->playContainerSound($position, $isOpen); + } + + public function onContainerOpen() : void{ + if($this->getContainerViewerCount() === 1){ + $this->doContainerEffects(true); + } + } + + public function onContainerClose() : void{ + if($this->getContainerViewerCount() === 1){ + $this->doContainerEffects(false); + } + } +} From d69d8d6217bc57774769768c59b063f9bd14bfd0 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 25 Nov 2024 11:28:54 +0000 Subject: [PATCH 13/28] EnchantingTableInventoryWindow: cleanup weakref mess this really needs a shorter name --- .../window/EnchantingTableInventoryWindow.php | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/block/inventory/window/EnchantingTableInventoryWindow.php b/src/block/inventory/window/EnchantingTableInventoryWindow.php index 68ef0084c51..294c1818908 100644 --- a/src/block/inventory/window/EnchantingTableInventoryWindow.php +++ b/src/block/inventory/window/EnchantingTableInventoryWindow.php @@ -43,8 +43,7 @@ final class EnchantingTableInventoryWindow extends BlockInventoryWindow{ /** @var EnchantingOption[] $options */ private array $options = []; - /** @phpstan-var \WeakReference */ - private \WeakReference $listener; + private InventoryListener $listener; public function __construct( Player $viewer, @@ -54,7 +53,7 @@ public function __construct( /** @phpstan-var \WeakReference $weakThis */ $weakThis = \WeakReference::create($this); - $listener = new CallbackInventoryListener( + $this->listener = new CallbackInventoryListener( onSlotChange: static function(Inventory $_, int $slot) use ($weakThis) : void{ //remaining params unneeded if($slot === self::SLOT_INPUT && ($strongThis = $weakThis->get()) !== null){ $strongThis->regenerateOptions(); @@ -66,16 +65,11 @@ public function __construct( } } ); - $this->inventory->getListeners()->add($listener); - - $this->listener = \WeakReference::create($listener); + $this->inventory->getListeners()->add($this->listener); } public function __destruct(){ - $listener = $this->listener->get(); - if($listener !== null){ - $this->inventory->getListeners()->remove($listener); - } + $this->inventory->getListeners()->remove($this->listener); } private function regenerateOptions() : void{ From ce4d3aef9e53ecf9dda38976f384ae66ee68fdfd Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 6 Dec 2024 15:34:32 +0000 Subject: [PATCH 14/28] Rename Container(Trait) -> ContainerTile(Trait) this allows introducing block variations of these without name conflicts --- src/block/tile/Barrel.php | 4 ++-- src/block/tile/BrewingStand.php | 4 ++-- src/block/tile/Campfire.php | 4 ++-- src/block/tile/Chest.php | 4 ++-- src/block/tile/ChiseledBookshelf.php | 12 ++++++------ .../tile/{Container.php => ContainerTile.php} | 2 +- .../{ContainerTrait.php => ContainerTileTrait.php} | 14 +++++++------- src/block/tile/Furnace.php | 4 ++-- src/block/tile/Hopper.php | 4 ++-- src/block/tile/ShulkerBox.php | 4 ++-- 10 files changed, 28 insertions(+), 28 deletions(-) rename src/block/tile/{Container.php => ContainerTile.php} (95%) rename src/block/tile/{ContainerTrait.php => ContainerTileTrait.php} (83%) diff --git a/src/block/tile/Barrel.php b/src/block/tile/Barrel.php index 9b64bbf321d..d70f5a405db 100644 --- a/src/block/tile/Barrel.php +++ b/src/block/tile/Barrel.php @@ -29,9 +29,9 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\world\World; -class Barrel extends Spawnable implements Container, Nameable{ +class Barrel extends Spawnable implements ContainerTile, Nameable{ use NameableTrait; - use ContainerTrait; + use ContainerTileTrait; protected Inventory $inventory; diff --git a/src/block/tile/BrewingStand.php b/src/block/tile/BrewingStand.php index 87b53eefbcb..30b94bc5fa9 100644 --- a/src/block/tile/BrewingStand.php +++ b/src/block/tile/BrewingStand.php @@ -41,11 +41,11 @@ use function array_map; use function count; -class BrewingStand extends Spawnable implements Container, Nameable{ +class BrewingStand extends Spawnable implements ContainerTile, Nameable{ use NameableTrait { addAdditionalSpawnData as addNameSpawnData; } - use ContainerTrait; + use ContainerTileTrait; public const BREW_TIME_TICKS = 400; // Brew time in ticks diff --git a/src/block/tile/Campfire.php b/src/block/tile/Campfire.php index a9ad30351f6..44c7a9e6e77 100644 --- a/src/block/tile/Campfire.php +++ b/src/block/tile/Campfire.php @@ -34,8 +34,8 @@ use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\world\World; -class Campfire extends Spawnable implements Container{ - use ContainerTrait; +class Campfire extends Spawnable implements ContainerTile{ + use ContainerTileTrait; private const TAG_FIRST_INPUT_ITEM = "Item1"; //TAG_Compound private const TAG_SECOND_INPUT_ITEM = "Item2"; //TAG_Compound diff --git a/src/block/tile/Chest.php b/src/block/tile/Chest.php index 9bc3fdffb3c..8f5ee48fa30 100644 --- a/src/block/tile/Chest.php +++ b/src/block/tile/Chest.php @@ -33,11 +33,11 @@ use pocketmine\world\World; use function abs; -class Chest extends Spawnable implements Container, Nameable{ +class Chest extends Spawnable implements ContainerTile, Nameable{ use NameableTrait { addAdditionalSpawnData as addNameSpawnData; } - use ContainerTrait { + use ContainerTileTrait { onBlockDestroyedHook as containerTraitBlockDestroyedHook; } diff --git a/src/block/tile/ChiseledBookshelf.php b/src/block/tile/ChiseledBookshelf.php index da7e42c0df4..d18b606d766 100644 --- a/src/block/tile/ChiseledBookshelf.php +++ b/src/block/tile/ChiseledBookshelf.php @@ -37,8 +37,8 @@ use pocketmine\world\World; use function count; -class ChiseledBookshelf extends Tile implements Container{ - use ContainerTrait; +class ChiseledBookshelf extends Tile implements ContainerTile{ + use ContainerTileTrait; private const TAG_LAST_INTERACTED_SLOT = "LastInteractedSlot"; //TAG_Int @@ -82,7 +82,7 @@ protected function writeSaveData(CompoundTag $nbt) : void{ } protected function loadItems(CompoundTag $tag) : void{ - if(($inventoryTag = $tag->getTag(Container::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ + if(($inventoryTag = $tag->getTag(ContainerTile::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ $inventory = $this->inventory; $listeners = $inventory->getListeners()->toArray(); $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization @@ -107,7 +107,7 @@ protected function loadItems(CompoundTag $tag) : void{ $inventory->getListeners()->add(...$listeners); } - if(($lockTag = $tag->getTag(Container::TAG_LOCK)) instanceof StringTag){ + if(($lockTag = $tag->getTag(ContainerTile::TAG_LOCK)) instanceof StringTag){ $this->lock = $lockTag->getValue(); } } @@ -126,10 +126,10 @@ protected function saveItems(CompoundTag $tag) : void{ } } - $tag->setTag(Container::TAG_ITEMS, new ListTag($items, NBT::TAG_Compound)); + $tag->setTag(ContainerTile::TAG_ITEMS, new ListTag($items, NBT::TAG_Compound)); if($this->lock !== null){ - $tag->setString(Container::TAG_LOCK, $this->lock); + $tag->setString(ContainerTile::TAG_LOCK, $this->lock); } } } diff --git a/src/block/tile/Container.php b/src/block/tile/ContainerTile.php similarity index 95% rename from src/block/tile/Container.php rename to src/block/tile/ContainerTile.php index 1c4e37fec43..51c7d375952 100644 --- a/src/block/tile/Container.php +++ b/src/block/tile/ContainerTile.php @@ -25,7 +25,7 @@ use pocketmine\inventory\InventoryHolder; -interface Container extends InventoryHolder{ +interface ContainerTile extends InventoryHolder{ public const TAG_ITEMS = "Items"; public const TAG_LOCK = "Lock"; diff --git a/src/block/tile/ContainerTrait.php b/src/block/tile/ContainerTileTrait.php similarity index 83% rename from src/block/tile/ContainerTrait.php rename to src/block/tile/ContainerTileTrait.php index cf69f37b513..f3e015ae453 100644 --- a/src/block/tile/ContainerTrait.php +++ b/src/block/tile/ContainerTileTrait.php @@ -33,14 +33,14 @@ use pocketmine\world\Position; /** - * This trait implements most methods in the {@link Container} interface. It should only be used by Tiles. + * This trait implements most methods in the {@link ContainerTile} interface. It should only be used by Tiles. */ -trait ContainerTrait{ +trait ContainerTileTrait{ /** @var string|null */ private $lock = null; protected function loadItems(CompoundTag $tag) : void{ - if(($inventoryTag = $tag->getTag(Container::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ + if(($inventoryTag = $tag->getTag(ContainerTile::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ $inventory = $this->getInventory(); $listeners = $inventory->getListeners()->toArray(); $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization @@ -61,7 +61,7 @@ protected function loadItems(CompoundTag $tag) : void{ $inventory->getListeners()->add(...$listeners); } - if(($lockTag = $tag->getTag(Container::TAG_LOCK)) instanceof StringTag){ + if(($lockTag = $tag->getTag(ContainerTile::TAG_LOCK)) instanceof StringTag){ $this->lock = $lockTag->getValue(); } } @@ -72,15 +72,15 @@ protected function saveItems(CompoundTag $tag) : void{ $items[] = $item->nbtSerialize($slot); } - $tag->setTag(Container::TAG_ITEMS, new ListTag($items, NBT::TAG_Compound)); + $tag->setTag(ContainerTile::TAG_ITEMS, new ListTag($items, NBT::TAG_Compound)); if($this->lock !== null){ - $tag->setString(Container::TAG_LOCK, $this->lock); + $tag->setString(ContainerTile::TAG_LOCK, $this->lock); } } /** - * @see Container::canOpenWith() + * @see ContainerTile::canOpenWith() */ public function canOpenWith(string $key) : bool{ return $this->lock === null || $this->lock === $key; diff --git a/src/block/tile/Furnace.php b/src/block/tile/Furnace.php index e036a606e3f..b6d18b0baea 100644 --- a/src/block/tile/Furnace.php +++ b/src/block/tile/Furnace.php @@ -41,9 +41,9 @@ use function array_map; use function max; -abstract class Furnace extends Spawnable implements Container, Nameable{ +abstract class Furnace extends Spawnable implements ContainerTile, Nameable{ use NameableTrait; - use ContainerTrait; + use ContainerTileTrait; public const TAG_BURN_TIME = "BurnTime"; public const TAG_COOK_TIME = "CookTime"; diff --git a/src/block/tile/Hopper.php b/src/block/tile/Hopper.php index 4a427332356..ed0c2c4794c 100644 --- a/src/block/tile/Hopper.php +++ b/src/block/tile/Hopper.php @@ -29,9 +29,9 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\world\World; -class Hopper extends Spawnable implements Container, Nameable{ +class Hopper extends Spawnable implements ContainerTile, Nameable{ - use ContainerTrait; + use ContainerTileTrait; use NameableTrait; private const TAG_TRANSFER_COOLDOWN = "TransferCooldown"; diff --git a/src/block/tile/ShulkerBox.php b/src/block/tile/ShulkerBox.php index 2e75659157f..c30e1696e96 100644 --- a/src/block/tile/ShulkerBox.php +++ b/src/block/tile/ShulkerBox.php @@ -35,11 +35,11 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\world\World; -class ShulkerBox extends Spawnable implements Container, Nameable{ +class ShulkerBox extends Spawnable implements ContainerTile, Nameable{ use NameableTrait { addAdditionalSpawnData as addNameSpawnData; } - use ContainerTrait; + use ContainerTileTrait; public const TAG_FACING = "facing"; From 40574be3336289a813fda403ee858fb372afe067 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 6 Dec 2024 16:14:41 +0000 Subject: [PATCH 15/28] Shift inventory management responsibility to World this removes a bunch of problematic Position usages from Tile, as well as getting rid of a bunch of code duplication. --- src/block/Campfire.php | 3 +++ src/block/tile/Barrel.php | 7 ----- src/block/tile/BrewingStand.php | 12 --------- src/block/tile/Campfire.php | 11 -------- src/block/tile/Chest.php | 2 -- src/block/tile/Furnace.php | 18 ------------- src/block/tile/Hopper.php | 8 ------ src/block/tile/ShulkerBox.php | 7 ----- src/world/World.php | 46 ++++++++++++++++++++++++++++++++- 9 files changed, 48 insertions(+), 66 deletions(-) diff --git a/src/block/Campfire.php b/src/block/Campfire.php index af167b52fcc..dcb0e3fa867 100644 --- a/src/block/Campfire.php +++ b/src/block/Campfire.php @@ -276,6 +276,9 @@ public function onScheduledUpdate() : void{ $this->position->getWorld()->addSound($this->position, $furnaceType->getCookSound()); } $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, self::UPDATE_INTERVAL_TICKS); + }else{ + //make sure the visual state is updated when items are added + $this->position->getWorld()->setBlock($this->position, $this); } } diff --git a/src/block/tile/Barrel.php b/src/block/tile/Barrel.php index d70f5a405db..bf3913b7be3 100644 --- a/src/block/tile/Barrel.php +++ b/src/block/tile/Barrel.php @@ -50,13 +50,6 @@ protected function writeSaveData(CompoundTag $nbt) : void{ $this->saveItems($nbt); } - public function close() : void{ - if(!$this->closed){ - $this->inventory->removeAllViewers(); - parent::close(); - } - } - public function getInventory() : Inventory{ return $this->inventory; } diff --git a/src/block/tile/BrewingStand.php b/src/block/tile/BrewingStand.php index 30b94bc5fa9..8c70b4d034a 100644 --- a/src/block/tile/BrewingStand.php +++ b/src/block/tile/BrewingStand.php @@ -27,7 +27,6 @@ use pocketmine\crafting\BrewingRecipe; use pocketmine\event\block\BrewingFuelUseEvent; use pocketmine\event\block\BrewItemEvent; -use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\Inventory; use pocketmine\inventory\SimpleInventory; use pocketmine\item\Item; @@ -64,9 +63,6 @@ class BrewingStand extends Spawnable implements ContainerTile, Nameable{ public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); $this->inventory = new SimpleInventory(5); - $this->inventory->getListeners()->add(CallbackInventoryListener::onAnyChange(static function(Inventory $unused) use ($world, $pos) : void{ - $world->scheduleDelayedBlockUpdate($pos, 1); - })); } public function readSaveData(CompoundTag $nbt) : void{ @@ -105,14 +101,6 @@ public function getDefaultName() : string{ return "Brewing Stand"; } - public function close() : void{ - if(!$this->closed){ - $this->inventory->removeAllViewers(); - - parent::close(); - } - } - public function getInventory() : Inventory{ return $this->inventory; } diff --git a/src/block/tile/Campfire.php b/src/block/tile/Campfire.php index 44c7a9e6e77..c237c5c7896 100644 --- a/src/block/tile/Campfire.php +++ b/src/block/tile/Campfire.php @@ -23,10 +23,7 @@ namespace pocketmine\block\tile; -use pocketmine\block\Campfire as BlockCampfire; use pocketmine\block\inventory\CampfireInventory; -use pocketmine\inventory\CallbackInventoryListener; -use pocketmine\inventory\Inventory; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; @@ -54,14 +51,6 @@ class Campfire extends Spawnable implements ContainerTile{ public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); $this->inventory = new CampfireInventory(); - $this->inventory->getListeners()->add(CallbackInventoryListener::onAnyChange( - static function(Inventory $unused) use ($world, $pos) : void{ - $block = $world->getBlock($pos); - if($block instanceof BlockCampfire){ - $world->setBlock($pos, $block); - } - }) - ); } public function getInventory() : CampfireInventory{ diff --git a/src/block/tile/Chest.php b/src/block/tile/Chest.php index 8f5ee48fa30..67581290b05 100644 --- a/src/block/tile/Chest.php +++ b/src/block/tile/Chest.php @@ -94,8 +94,6 @@ public function getCleanedNBT() : ?CompoundTag{ public function close() : void{ if(!$this->closed){ - $this->inventory->removeAllViewers(); - if($this->doubleInventory !== null){ if($this->isPaired() && $this->position->getWorld()->isChunkLoaded($this->pairX >> Chunk::COORD_BIT_SIZE, $this->pairZ >> Chunk::COORD_BIT_SIZE)){ $this->doubleInventory->removeAllViewers(); diff --git a/src/block/tile/Furnace.php b/src/block/tile/Furnace.php index b6d18b0baea..4a7713f364c 100644 --- a/src/block/tile/Furnace.php +++ b/src/block/tile/Furnace.php @@ -29,7 +29,6 @@ use pocketmine\crafting\FurnaceType; use pocketmine\event\inventory\FurnaceBurnEvent; use pocketmine\event\inventory\FurnaceSmeltEvent; -use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\Inventory; use pocketmine\inventory\SimpleInventory; use pocketmine\item\Item; @@ -57,11 +56,6 @@ abstract class Furnace extends Spawnable implements ContainerTile, Nameable{ public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); $this->inventory = new SimpleInventory(3); - $this->inventory->getListeners()->add(CallbackInventoryListener::onAnyChange( - static function(Inventory $unused) use ($world, $pos) : void{ - $world->scheduleDelayedBlockUpdate($pos, 1); - }) - ); } public function readSaveData(CompoundTag $nbt) : void{ @@ -79,10 +73,6 @@ public function readSaveData(CompoundTag $nbt) : void{ $this->loadName($nbt); $this->loadItems($nbt); - - if($this->remainingFuelTime > 0){ - $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1); - } } protected function writeSaveData(CompoundTag $nbt) : void{ @@ -97,14 +87,6 @@ public function getDefaultName() : string{ return "Furnace"; } - public function close() : void{ - if(!$this->closed){ - $this->inventory->removeAllViewers(); - - parent::close(); - } - } - public function getInventory() : Inventory{ return $this->inventory; } diff --git a/src/block/tile/Hopper.php b/src/block/tile/Hopper.php index ed0c2c4794c..d001b872663 100644 --- a/src/block/tile/Hopper.php +++ b/src/block/tile/Hopper.php @@ -58,14 +58,6 @@ protected function writeSaveData(CompoundTag $nbt) : void{ $nbt->setInt(self::TAG_TRANSFER_COOLDOWN, $this->transferCooldown); } - public function close() : void{ - if(!$this->closed){ - $this->inventory->removeAllViewers(); - - parent::close(); - } - } - public function getDefaultName() : string{ return "Hopper"; } diff --git a/src/block/tile/ShulkerBox.php b/src/block/tile/ShulkerBox.php index c30e1696e96..104f1b944d8 100644 --- a/src/block/tile/ShulkerBox.php +++ b/src/block/tile/ShulkerBox.php @@ -80,13 +80,6 @@ public function copyDataFromItem(Item $item) : void{ } } - public function close() : void{ - if(!$this->closed){ - $this->inventory->removeAllViewers(); - parent::close(); - } - } - protected function onBlockDestroyedHook() : void{ //NOOP override of ContainerTrait - shulker boxes retain their contents when destroyed } diff --git a/src/world/World.php b/src/world/World.php index ff65377c094..03ceca29c64 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -30,6 +30,7 @@ use pocketmine\block\Block; use pocketmine\block\BlockTypeIds; use pocketmine\block\RuntimeBlockStateRegistry; +use pocketmine\block\tile\ContainerTile; use pocketmine\block\tile\Spawnable; use pocketmine\block\tile\Tile; use pocketmine\block\tile\TileFactory; @@ -57,6 +58,8 @@ use pocketmine\event\world\WorldParticleEvent; use pocketmine\event\world\WorldSaveEvent; use pocketmine\event\world\WorldSoundEvent; +use pocketmine\inventory\Inventory; +use pocketmine\inventory\InventoryListener; use pocketmine\item\Item; use pocketmine\item\ItemUseResult; use pocketmine\item\LegacyStringToItemParser; @@ -144,7 +147,7 @@ * @phpstan-type BlockPosHash int * @phpstan-type ChunkBlockPosHash int */ -class World implements ChunkManager{ +class World implements ChunkManager, InventoryListener{ private static int $worldIdCounter = 1; @@ -282,6 +285,12 @@ class World implements ChunkManager{ */ private array $chunks = []; + /** + * @var Vector3[]|\WeakMap + * @phpstan-var \WeakMap + */ + private \WeakMap $containerToBlockPositionMap; + /** * @var Vector3[][] chunkHash => [relativeBlockHash => Vector3] * @phpstan-var array> @@ -506,6 +515,8 @@ public function __construct( } }); + $this->containerToBlockPositionMap = new \WeakMap(); + $this->scheduledBlockUpdateQueue = new ReversePriorityQueue(); $this->scheduledBlockUpdateQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); @@ -2787,6 +2798,10 @@ public function addTile(Tile $tile) : void{ //delegate tile ticking to the corresponding block $this->scheduleDelayedBlockUpdate($pos->asVector3(), 1); + if($tile instanceof ContainerTile){ + $tile->getInventory()->getListeners()->add($this); + $this->containerToBlockPositionMap[$tile->getInventory()] = $pos->asVector3(); + } } /** @@ -2805,11 +2820,40 @@ public function removeTile(Tile $tile) : void{ if(isset($this->chunks[$hash = World::chunkHash($chunkX, $chunkZ)])){ $this->chunks[$hash]->removeTile($tile); } + if($tile instanceof ContainerTile){ + $inventory = $tile->getInventory(); + $inventory->removeAllViewers(); + $inventory->getListeners()->remove($this); + unset($this->containerToBlockPositionMap[$inventory]); + } foreach($this->getChunkListeners($chunkX, $chunkZ) as $listener){ $listener->onBlockChanged($pos->asVector3()); } } + private function notifyInventoryUpdate(Inventory $inventory) : void{ + $blockPosition = $this->containerToBlockPositionMap[$inventory] ?? null; + if($blockPosition !== null){ + $this->scheduleDelayedBlockUpdate($blockPosition, 1); + } + } + + /** + * @internal + * @see InventoryListener + */ + public function onSlotChange(Inventory $inventory, int $slot, Item $oldItem) : void{ + $this->notifyInventoryUpdate($inventory); + } + + /** + * @internal + * @see InventoryListener + */ + public function onContentChange(Inventory $inventory, array $oldContents) : void{ + $this->notifyInventoryUpdate($inventory); + } + public function isChunkInUse(int $x, int $z) : bool{ return isset($this->chunkLoaders[$index = World::chunkHash($x, $z)]) && count($this->chunkLoaders[$index]) > 0; } From 4850bd5538214642b2e96b3f1b4954b1f21c5cdd Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 6 Dec 2024 17:46:11 +0000 Subject: [PATCH 16/28] Allow blocks to respond to the contents of their containers being updated turns out relying on scheduled updates for this was a bad idea, since it causes a lot of unnecessary code to run every tick, as well as being problematic for campfire, which doesn't have any blockstates to compare against. --- src/block/Block.php | 10 ++++++++++ src/block/BrewingStand.php | 24 ++++++++++++++---------- src/block/Campfire.php | 21 +++++++++++++-------- src/block/ChiseledBookshelf.php | 15 +++++++++++++++ src/world/World.php | 1 + 5 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/block/Block.php b/src/block/Block.php index 89fe3926584..aabb6966566 100644 --- a/src/block/Block.php +++ b/src/block/Block.php @@ -36,6 +36,7 @@ use pocketmine\data\runtime\RuntimeDataWriter; use pocketmine\entity\Entity; use pocketmine\entity\projectile\Projectile; +use pocketmine\inventory\Inventory; use pocketmine\item\enchantment\AvailableEnchantmentRegistry; use pocketmine\item\enchantment\ItemEnchantmentTagRegistry; use pocketmine\item\enchantment\ItemEnchantmentTags; @@ -515,6 +516,15 @@ public function onScheduledUpdate() : void{ } + /** + * Called by the World when a change is detected in a container's inventory at the block's position. + * Use this to do visual updates on the block if needed. + * Don't do any expensive logic in here. It will be called every time a slot of the inventory changes. + */ + public function onContainerUpdate(Inventory $inventory) : void{ + + } + /** * Do actions when interacted by Item. Returns if it has done anything * diff --git a/src/block/BrewingStand.php b/src/block/BrewingStand.php index 59e439b91e2..dc961aa5592 100644 --- a/src/block/BrewingStand.php +++ b/src/block/BrewingStand.php @@ -28,6 +28,7 @@ use pocketmine\block\utils\BrewingStandSlot; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; +use pocketmine\inventory\Inventory; use pocketmine\item\Item; use pocketmine\math\Axis; use pocketmine\math\AxisAlignedBB; @@ -114,19 +115,22 @@ public function onScheduledUpdate() : void{ if($brewing->onUpdate()){ $world->scheduleDelayedBlockUpdate($this->position, 1); } + } + } - $changed = false; - foreach(BrewingStandSlot::cases() as $slot){ - $occupied = !$brewing->getInventory()->isSlotEmpty($slot->getSlotNumber()); - if($occupied !== $this->hasSlot($slot)){ - $this->setSlot($slot, $occupied); - $changed = true; - } + public function onContainerUpdate(Inventory $inventory) : void{ + $world = $this->position->getWorld(); + $changed = false; + foreach(BrewingStandSlot::cases() as $slot){ + $occupied = !$inventory->isSlotEmpty($slot->getSlotNumber()); + if($occupied !== $this->hasSlot($slot)){ + $this->setSlot($slot, $occupied); + $changed = true; } + } - if($changed){ - $world->setBlock($this->position, $this); - } + if($changed){ + $world->setBlock($this->position, $this); } } } diff --git a/src/block/Campfire.php b/src/block/Campfire.php index dcb0e3fa867..d5413d61017 100644 --- a/src/block/Campfire.php +++ b/src/block/Campfire.php @@ -38,6 +38,7 @@ use pocketmine\event\block\CampfireCookEvent; use pocketmine\event\entity\EntityDamageByBlockEvent; use pocketmine\event\entity\EntityDamageEvent; +use pocketmine\inventory\Inventory; use pocketmine\item\Durable; use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\Item; @@ -67,8 +68,6 @@ class Campfire extends Transparent{ LightableTrait::describeBlockOnlyState as encodeLitState; } - private const UPDATE_INTERVAL_TICKS = 10; - /** * @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block * has never been set in the world. @@ -250,7 +249,7 @@ public function onScheduledUpdate() : void{ $furnaceType = $this->getFurnaceType(); $maxCookDuration = $furnaceType->getCookDurationTicks(); foreach($items as $slot => $item){ - $this->setCookingTime($slot, min($maxCookDuration, $this->getCookingTime($slot) + self::UPDATE_INTERVAL_TICKS)); + $this->setCookingTime($slot, min($maxCookDuration, $this->getCookingTime($slot) + 1)); if($this->getCookingTime($slot) >= $maxCookDuration){ $result = ($recipe = $this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($furnaceType)->match($item)) instanceof FurnaceRecipe ? @@ -269,19 +268,25 @@ public function onScheduledUpdate() : void{ $this->position->getWorld()->dropItem($this->position->add(0.5, 1, 0.5), $ev->getResult()); } } + $tile = $this->position->getWorld()->getTile($this->position); + if($tile instanceof TileCampfire){ + //TODO: we probably need to rethink how these are tracked + $tile->setCookingTimes($this->cookingTimes); + } if(count($items) > 0){ $this->position->getWorld()->setBlock($this->position, $this); } if(mt_rand(1, 6) === 1){ $this->position->getWorld()->addSound($this->position, $furnaceType->getCookSound()); } - $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, self::UPDATE_INTERVAL_TICKS); - }else{ - //make sure the visual state is updated when items are added - $this->position->getWorld()->setBlock($this->position, $this); + $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1); } } + public function onContainerUpdate(Inventory $inventory) : void{ + $this->position->getWorld()->setBlock($this->position, $this); //update visual state + } + private function extinguish() : void{ $this->position->getWorld()->addSound($this->position, new FireExtinguishSound()); $this->position->getWorld()->setBlock($this->position, $this->setLit(false)); @@ -290,6 +295,6 @@ private function extinguish() : void{ private function ignite() : void{ $this->position->getWorld()->addSound($this->position, new FlintSteelSound()); $this->position->getWorld()->setBlock($this->position, $this->setLit(true)); - $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, self::UPDATE_INTERVAL_TICKS); + $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1); } } diff --git a/src/block/ChiseledBookshelf.php b/src/block/ChiseledBookshelf.php index 73c4861bf35..4c91f1d9404 100644 --- a/src/block/ChiseledBookshelf.php +++ b/src/block/ChiseledBookshelf.php @@ -28,6 +28,7 @@ use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; +use pocketmine\inventory\Inventory; use pocketmine\item\Book; use pocketmine\item\EnchantedBook; use pocketmine\item\Item; @@ -164,6 +165,20 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } + public function onContainerUpdate(Inventory $inventory) : void{ + $changed = false; + foreach(ChiseledBookshelfSlot::cases() as $case){ + $hasItem = !$inventory->isSlotEmpty($case->value); + if($this->hasSlot($case) !== $hasItem){ + $this->setSlot($case, $hasItem); + $changed = true; + } + } + if($changed){ + $this->position->getWorld()->setBlock($this->position, $this); + } + } + public function getDropsForCompatibleTool(Item $item) : array{ return []; } diff --git a/src/world/World.php b/src/world/World.php index 03ceca29c64..863cbd90fb8 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2834,6 +2834,7 @@ public function removeTile(Tile $tile) : void{ private function notifyInventoryUpdate(Inventory $inventory) : void{ $blockPosition = $this->containerToBlockPositionMap[$inventory] ?? null; if($blockPosition !== null){ + $this->getBlock($blockPosition)->onContainerUpdate($inventory); $this->scheduleDelayedBlockUpdate($blockPosition, 1); } } From 76528b20c11ffcc487ce7be6896fd4e98ccc9e9e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 7 Dec 2024 14:10:55 +0000 Subject: [PATCH 17/28] Remove dodgy code --- src/block/Campfire.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/block/Campfire.php b/src/block/Campfire.php index d5413d61017..4e23761b984 100644 --- a/src/block/Campfire.php +++ b/src/block/Campfire.php @@ -273,9 +273,6 @@ public function onScheduledUpdate() : void{ //TODO: we probably need to rethink how these are tracked $tile->setCookingTimes($this->cookingTimes); } - if(count($items) > 0){ - $this->position->getWorld()->setBlock($this->position, $this); - } if(mt_rand(1, 6) === 1){ $this->position->getWorld()->addSound($this->position, $furnaceType->getCookSound()); } From 15bb0c705c9b566658617bf7bb8fa9367017e0dc Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 7 Dec 2024 14:19:58 +0000 Subject: [PATCH 18/28] Remove CampfireInventory --- src/block/Campfire.php | 20 +++++++++---- src/block/inventory/CampfireInventory.php | 36 ----------------------- src/block/tile/Campfire.php | 9 +++--- 3 files changed, 20 insertions(+), 45 deletions(-) delete mode 100644 src/block/inventory/CampfireInventory.php diff --git a/src/block/Campfire.php b/src/block/Campfire.php index 4e23761b984..89661d8a7c7 100644 --- a/src/block/Campfire.php +++ b/src/block/Campfire.php @@ -23,7 +23,6 @@ namespace pocketmine\block; -use pocketmine\block\inventory\CampfireInventory; use pocketmine\block\tile\Campfire as TileCampfire; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\LightableTrait; @@ -39,6 +38,7 @@ use pocketmine\event\entity\EntityDamageByBlockEvent; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\item\Durable; use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\Item; @@ -72,13 +72,23 @@ class Campfire extends Transparent{ * @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block * has never been set in the world. */ - protected CampfireInventory $inventory; + protected Inventory $inventory; public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){ parent::__construct($idInfo, $name, $typeInfo); //TODO: this should never have been in the block - it creates problems for setting blocks in different positions //as inventories aren't designed to be cloned - $this->inventory = new CampfireInventory(); + $this->inventory = self::createInventory(); + } + + /** + * @internal + * TODO: we need to explore why this is being created in multiple places + */ + public static function createInventory() : Inventory{ + $result = new SimpleInventory(4); + $result->setMaxStackSize(1); + return $result; } /** @@ -99,7 +109,7 @@ public function readStateFromWorld() : Block{ $this->inventory = $tile->getInventory(); $this->cookingTimes = $tile->getCookingTimes(); }else{ - $this->inventory = new CampfireInventory(); + $this->inventory = self::createInventory(); } return $this; @@ -143,7 +153,7 @@ protected function recalculateCollisionBoxes() : array{ * @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block * has never been set in the world. */ - public function getInventory() : CampfireInventory{ + public function getInventory() : Inventory{ return $this->inventory; } diff --git a/src/block/inventory/CampfireInventory.php b/src/block/inventory/CampfireInventory.php deleted file mode 100644 index f3bd618c218..00000000000 --- a/src/block/inventory/CampfireInventory.php +++ /dev/null @@ -1,36 +0,0 @@ - */ private array $cookingTimes = []; public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new CampfireInventory(); + $this->inventory = BlockCampfire::createInventory(); } - public function getInventory() : CampfireInventory{ + public function getInventory() : Inventory{ return $this->inventory; } From b76db739fdc3855d3f355f682ff471c8ca969a8b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 7 Dec 2024 14:45:57 +0000 Subject: [PATCH 19/28] Campfire block's inventory is now null if it hasn't been set in the world having this created by the block was unreliable anyway. If items were set into the block's created inventory before setting the block in the world, the campfire contents would get overridden when the block was next run through readStateFromWorld() anyway. There needs to be a deeper exploration of how to handle blocks with inventories without requiring plugins to interact with tiles. For now, this isn't the worst solution, but it's not the best either. --- src/block/Campfire.php | 35 ++++++++++------------------------- src/block/tile/Campfire.php | 4 +++- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/src/block/Campfire.php b/src/block/Campfire.php index 89661d8a7c7..ebde5ba3d3e 100644 --- a/src/block/Campfire.php +++ b/src/block/Campfire.php @@ -72,24 +72,7 @@ class Campfire extends Transparent{ * @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block * has never been set in the world. */ - protected Inventory $inventory; - - public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){ - parent::__construct($idInfo, $name, $typeInfo); - //TODO: this should never have been in the block - it creates problems for setting blocks in different positions - //as inventories aren't designed to be cloned - $this->inventory = self::createInventory(); - } - - /** - * @internal - * TODO: we need to explore why this is being created in multiple places - */ - public static function createInventory() : Inventory{ - $result = new SimpleInventory(4); - $result->setMaxStackSize(1); - return $result; - } + protected ?Inventory $inventory = null; /** * @var int[] slot => ticks @@ -109,7 +92,8 @@ public function readStateFromWorld() : Block{ $this->inventory = $tile->getInventory(); $this->cookingTimes = $tile->getCookingTimes(); }else{ - $this->inventory = self::createInventory(); + $this->inventory = null; + $this->cookingTimes = []; } return $this; @@ -153,7 +137,7 @@ protected function recalculateCollisionBoxes() : array{ * @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block * has never been set in the world. */ - public function getInventory() : Inventory{ + public function getInventory() : ?Inventory{ return $this->inventory; } @@ -216,10 +200,11 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } - if($this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($this->getFurnaceType())->match($item) !== null){ + $inventory = $this->inventory; + if($inventory !== null && $this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($this->getFurnaceType())->match($item) !== null){ $ingredient = clone $item; $ingredient->setCount(1); - if(count($this->inventory->addItem($ingredient)) === 0){ + if(count($inventory->addItem($ingredient)) === 0){ $item->pop(); $this->position->getWorld()->addSound($this->position, new ItemFrameAddItemSound()); return true; @@ -254,8 +239,8 @@ public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResul } public function onScheduledUpdate() : void{ - if($this->lit){ - $items = $this->inventory->getContents(); + if($this->lit && ($inventory = $this->inventory) !== null){ + $items = $inventory->getContents(); $furnaceType = $this->getFurnaceType(); $maxCookDuration = $furnaceType->getCookDurationTicks(); foreach($items as $slot => $item){ @@ -273,7 +258,7 @@ public function onScheduledUpdate() : void{ continue; } - $this->inventory->setItem($slot, VanillaItems::AIR()); + $inventory->setItem($slot, VanillaItems::AIR()); $this->setCookingTime($slot, 0); $this->position->getWorld()->dropItem($this->position->add(0.5, 1, 0.5), $ev->getResult()); } diff --git a/src/block/tile/Campfire.php b/src/block/tile/Campfire.php index a40cf9eaf2f..d0e43569a15 100644 --- a/src/block/tile/Campfire.php +++ b/src/block/tile/Campfire.php @@ -25,6 +25,7 @@ use pocketmine\block\Campfire as BlockCampfire; use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; @@ -51,7 +52,8 @@ class Campfire extends Spawnable implements ContainerTile{ public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = BlockCampfire::createInventory(); + $this->inventory = new SimpleInventory(4); + $this->inventory->setMaxStackSize(1); } public function getInventory() : Inventory{ From b5a69c829d7cb9d7c3312b7a10927dbb4ddce2b6 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 7 Dec 2024 14:47:10 +0000 Subject: [PATCH 20/28] smh --- src/block/Campfire.php | 1 - src/block/tile/Campfire.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/block/Campfire.php b/src/block/Campfire.php index ebde5ba3d3e..b85f6d030b2 100644 --- a/src/block/Campfire.php +++ b/src/block/Campfire.php @@ -38,7 +38,6 @@ use pocketmine\event\entity\EntityDamageByBlockEvent; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\inventory\Inventory; -use pocketmine\inventory\SimpleInventory; use pocketmine\item\Durable; use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\Item; diff --git a/src/block/tile/Campfire.php b/src/block/tile/Campfire.php index d0e43569a15..d2adcfda74b 100644 --- a/src/block/tile/Campfire.php +++ b/src/block/tile/Campfire.php @@ -23,7 +23,6 @@ namespace pocketmine\block\tile; -use pocketmine\block\Campfire as BlockCampfire; use pocketmine\inventory\Inventory; use pocketmine\inventory\SimpleInventory; use pocketmine\item\Item; From ef3d16597afb7eccbebab8b1049eda10e95f79c1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 7 Dec 2024 16:04:50 +0000 Subject: [PATCH 21/28] Revert "Chest block now has responsibility for configuring double chest inventories" This reverts commit 1d2b52732e3c475ddc2bab4e45726d22850e3d5c. I hadn't considered that the likes of plugins and hoppers need to be able to interact with double chest inventories as well as players. If we were to move this logic to the Block side, we'd have to expose APIs on the Chest block to get the correct inventory lazily. I'm not sure I want to commit to having getInventory() on the block right now, as we can't guarantee it's available (see problems around Campfire inventory on the block). Short term, it'll probably be better to just expose the logic in block\Chest for deciding which side the inventories should be on. --- src/block/Chest.php | 33 +++++++-------------------- src/block/tile/Barrel.php | 4 ++++ src/block/tile/BrewingStand.php | 4 ++++ src/block/tile/Campfire.php | 4 ++++ src/block/tile/Chest.php | 26 +++++++++++++++------ src/block/tile/ChiseledBookshelf.php | 8 +++++-- src/block/tile/ContainerTile.php | 3 +++ src/block/tile/ContainerTileTrait.php | 9 +++++--- src/block/tile/Furnace.php | 4 ++++ src/block/tile/Hopper.php | 4 ++++ src/block/tile/ShulkerBox.php | 4 ++++ 11 files changed, 66 insertions(+), 37 deletions(-) diff --git a/src/block/Chest.php b/src/block/Chest.php index a688df0bd43..a28262e1afb 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -109,30 +109,22 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $world = $this->position->getWorld(); $chest = $world->getTile($this->position); if($chest instanceof TileChest){ + [$pairOnLeft, $pair] = $this->locatePair($this->position) ?? [false, null]; if( !$this->getSide(Facing::UP)->isTransparent() || - (($pair = $chest->getPair()) !== null && !$pair->getBlock()->getSide(Facing::UP)->isTransparent()) || + ($pair !== null && !$pair->getBlock()->getSide(Facing::UP)->isTransparent()) || !$chest->canOpenWith($item->getCustomName()) ){ return true; } - $window = null; - if($chest->isPaired()){ - $info = $this->locatePair($this->position); - if($info !== null){ - [$clockwise, $pair] = $info; - [$left, $right] = $clockwise ? [$pair, $chest] : [$chest, $pair]; - - $doubleInventory = $left->getDoubleInventory() ?? $right->getDoubleInventory(); - if($doubleInventory === null){ - $doubleInventory = new DoubleChestInventory($left->getInventory(), $right->getInventory()); - $left->setDoubleInventory($doubleInventory); - $right->setDoubleInventory($doubleInventory); - } + if($pair !== null){ + [$left, $right] = $pairOnLeft ? [$pair->getPosition(), $this->position] : [$this->position, $pair->getPosition()]; - $window = new DoubleChestInventoryWindow($player, $doubleInventory, $left->getPosition(), $right->getPosition()); - } + //TODO: we should probably construct DoubleChestInventory here directly too using the same logic + //right now it uses some weird logic in TileChest which produces incorrect results + //however I'm not sure if this is currently possible + $window = new DoubleChestInventoryWindow($player, $chest->getInventory(), $left, $right); } $player->setCurrentWindow($window ?? new BlockInventoryWindow($player, $chest->getInventory(), $this->position)); @@ -146,15 +138,6 @@ public function getFuelTime() : int{ return 300; } - protected function getContainerViewerCount() : int{ - $tile = $this->position->getWorld()->getTile($this->position); - if($tile instanceof TileChest){ - $inventory = $tile->getDoubleInventory() ?? $tile->getInventory(); - return count($inventory->getViewers()); - } - return 0; - } - protected function getContainerOpenSound() : Sound{ return new ChestOpenSound(); } diff --git a/src/block/tile/Barrel.php b/src/block/tile/Barrel.php index bf3913b7be3..23f7383ca7c 100644 --- a/src/block/tile/Barrel.php +++ b/src/block/tile/Barrel.php @@ -54,6 +54,10 @@ public function getInventory() : Inventory{ return $this->inventory; } + public function getRealInventory() : Inventory{ + return $this->inventory; + } + public function getDefaultName() : string{ return "Barrel"; } diff --git a/src/block/tile/BrewingStand.php b/src/block/tile/BrewingStand.php index 8c70b4d034a..42828671c58 100644 --- a/src/block/tile/BrewingStand.php +++ b/src/block/tile/BrewingStand.php @@ -105,6 +105,10 @@ public function getInventory() : Inventory{ return $this->inventory; } + public function getRealInventory() : Inventory{ + return $this->inventory; + } + private function checkFuel(Item $item) : void{ $ev = new BrewingFuelUseEvent($this); if(!$item->equals(VanillaItems::BLAZE_POWDER(), true, false)){ diff --git a/src/block/tile/Campfire.php b/src/block/tile/Campfire.php index d2adcfda74b..4efb70a0d51 100644 --- a/src/block/tile/Campfire.php +++ b/src/block/tile/Campfire.php @@ -59,6 +59,10 @@ public function getInventory() : Inventory{ return $this->inventory; } + public function getRealInventory() : Inventory{ + return $this->inventory; + } + /** * @return int[] * @phpstan-return array diff --git a/src/block/tile/Chest.php b/src/block/tile/Chest.php index 67581290b05..b66e849c7a7 100644 --- a/src/block/tile/Chest.php +++ b/src/block/tile/Chest.php @@ -113,14 +113,15 @@ protected function onBlockDestroyedHook() : void{ $this->containerTraitBlockDestroyedHook(); } - public function getInventory() : Inventory{ - return $this->inventory; + public function getInventory() : Inventory|DoubleChestInventory{ + if($this->isPaired() && $this->doubleInventory === null){ + $this->checkPairing(); + } + return $this->doubleInventory instanceof DoubleChestInventory ? $this->doubleInventory : $this->inventory; } - public function getDoubleInventory() : ?DoubleChestInventory{ return $this->doubleInventory; } - - public function setDoubleInventory(?DoubleChestInventory $doubleChestInventory) : void{ - $this->doubleInventory = $doubleChestInventory; + public function getRealInventory() : Inventory{ + return $this->inventory; } protected function checkPairing() : void{ @@ -131,7 +132,18 @@ protected function checkPairing() : void{ }elseif(($pair = $this->getPair()) instanceof Chest){ if(!$pair->isPaired()){ $pair->createPair($this); - $this->doubleInventory = $pair->doubleInventory = null; + $pair->checkPairing(); + } + if($this->doubleInventory === null){ + if($pair->doubleInventory !== null){ + $this->doubleInventory = $pair->doubleInventory; + }else{ + if(($pair->position->x + ($pair->position->z << 15)) > ($this->position->x + ($this->position->z << 15))){ //Order them correctly + $this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($pair->inventory, $this->inventory); + }else{ + $this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($this->inventory, $pair->inventory); + } + } } }else{ $this->doubleInventory = null; diff --git a/src/block/tile/ChiseledBookshelf.php b/src/block/tile/ChiseledBookshelf.php index d18b606d766..e8b5e73ec42 100644 --- a/src/block/tile/ChiseledBookshelf.php +++ b/src/block/tile/ChiseledBookshelf.php @@ -55,6 +55,10 @@ public function getInventory() : SimpleInventory{ return $this->inventory; } + public function getRealInventory() : SimpleInventory{ + return $this->inventory; + } + public function getLastInteractedSlot() : ?ChiseledBookshelfSlot{ return $this->lastInteractedSlot; } @@ -83,7 +87,7 @@ protected function writeSaveData(CompoundTag $nbt) : void{ protected function loadItems(CompoundTag $tag) : void{ if(($inventoryTag = $tag->getTag(ContainerTile::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ - $inventory = $this->inventory; + $inventory = $this->getRealInventory(); $listeners = $inventory->getListeners()->toArray(); $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization @@ -114,7 +118,7 @@ protected function loadItems(CompoundTag $tag) : void{ protected function saveItems(CompoundTag $tag) : void{ $items = []; - foreach($this->inventory->getContents(true) as $slot => $item){ + foreach($this->getRealInventory()->getContents(true) as $slot => $item){ if($item->isNull()){ $items[$slot] = CompoundTag::create() ->setByte(SavedItemStackData::TAG_COUNT, 0) diff --git a/src/block/tile/ContainerTile.php b/src/block/tile/ContainerTile.php index 51c7d375952..e5ce2dfe19b 100644 --- a/src/block/tile/ContainerTile.php +++ b/src/block/tile/ContainerTile.php @@ -23,12 +23,15 @@ namespace pocketmine\block\tile; +use pocketmine\inventory\Inventory; use pocketmine\inventory\InventoryHolder; interface ContainerTile extends InventoryHolder{ public const TAG_ITEMS = "Items"; public const TAG_LOCK = "Lock"; + public function getRealInventory() : Inventory; + /** * Returns whether this container can be opened by an item with the given custom name. */ diff --git a/src/block/tile/ContainerTileTrait.php b/src/block/tile/ContainerTileTrait.php index f3e015ae453..0f07f51d600 100644 --- a/src/block/tile/ContainerTileTrait.php +++ b/src/block/tile/ContainerTileTrait.php @@ -25,6 +25,7 @@ use pocketmine\data\bedrock\item\SavedItemStackData; use pocketmine\data\SavedDataLoadingException; +use pocketmine\inventory\Inventory; use pocketmine\item\Item; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; @@ -39,9 +40,11 @@ trait ContainerTileTrait{ /** @var string|null */ private $lock = null; + abstract public function getRealInventory() : Inventory; + protected function loadItems(CompoundTag $tag) : void{ if(($inventoryTag = $tag->getTag(ContainerTile::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ - $inventory = $this->getInventory(); + $inventory = $this->getRealInventory(); $listeners = $inventory->getListeners()->toArray(); $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization @@ -68,7 +71,7 @@ protected function loadItems(CompoundTag $tag) : void{ protected function saveItems(CompoundTag $tag) : void{ $items = []; - foreach($this->getInventory()->getContents() as $slot => $item){ + foreach($this->getRealInventory()->getContents() as $slot => $item){ $items[] = $item->nbtSerialize($slot); } @@ -95,7 +98,7 @@ abstract protected function getPosition() : Position; * @see Tile::onBlockDestroyedHook() */ protected function onBlockDestroyedHook() : void{ - $inv = $this->getInventory(); + $inv = $this->getRealInventory(); $pos = $this->getPosition(); $world = $pos->getWorld(); diff --git a/src/block/tile/Furnace.php b/src/block/tile/Furnace.php index 4a7713f364c..1657a4eed27 100644 --- a/src/block/tile/Furnace.php +++ b/src/block/tile/Furnace.php @@ -91,6 +91,10 @@ public function getInventory() : Inventory{ return $this->inventory; } + public function getRealInventory() : Inventory{ + return $this->getInventory(); + } + protected function checkFuel(Item $fuel) : void{ $ev = new FurnaceBurnEvent($this, $fuel, $fuel->getFuelTime()); $ev->call(); diff --git a/src/block/tile/Hopper.php b/src/block/tile/Hopper.php index d001b872663..988d55c42ed 100644 --- a/src/block/tile/Hopper.php +++ b/src/block/tile/Hopper.php @@ -65,4 +65,8 @@ public function getDefaultName() : string{ public function getInventory() : Inventory{ return $this->inventory; } + + public function getRealInventory() : Inventory{ + return $this->inventory; + } } diff --git a/src/block/tile/ShulkerBox.php b/src/block/tile/ShulkerBox.php index 104f1b944d8..a7d5b9617cc 100644 --- a/src/block/tile/ShulkerBox.php +++ b/src/block/tile/ShulkerBox.php @@ -104,6 +104,10 @@ public function getInventory() : Inventory{ return $this->inventory; } + public function getRealInventory() : Inventory{ + return $this->inventory; + } + public function getDefaultName() : string{ return "Shulker Box"; } From 4906f5bec25c909979da6dc3c45b25e4a93181b7 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 7 Dec 2024 16:08:54 +0000 Subject: [PATCH 22/28] ... --- src/block/Chest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/block/Chest.php b/src/block/Chest.php index a28262e1afb..26ba16a156f 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -43,7 +43,6 @@ use pocketmine\world\sound\ChestCloseSound; use pocketmine\world\sound\ChestOpenSound; use pocketmine\world\sound\Sound; -use function count; class Chest extends Transparent implements AnimatedContainer{ use AnimatedContainerTrait; From 699a85a5d67216af8abd4dc35f5356ac62587a3c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 7 Dec 2024 18:51:25 +0000 Subject: [PATCH 23/28] Replace DoubleChestInventory with a more generic CombinedInventory this could be used for a bunch of different things aside from double chests since the DoubleChestInventory no longer references anything specific about chests, I figured it was time to generalize this. --- src/block/Chest.php | 1 - src/block/inventory/DoubleChestInventory.php | 99 ---------- src/block/tile/Chest.php | 12 +- src/inventory/CombinedInventory.php | 135 +++++++++++++ .../inventory/CombinedInventoryTest.php | 180 ++++++++++++++++++ 5 files changed, 321 insertions(+), 106 deletions(-) delete mode 100644 src/block/inventory/DoubleChestInventory.php create mode 100644 src/inventory/CombinedInventory.php create mode 100644 tests/phpunit/inventory/CombinedInventoryTest.php diff --git a/src/block/Chest.php b/src/block/Chest.php index 26ba16a156f..13712cab958 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -23,7 +23,6 @@ namespace pocketmine\block; -use pocketmine\block\inventory\DoubleChestInventory; use pocketmine\block\inventory\window\BlockInventoryWindow; use pocketmine\block\inventory\window\DoubleChestInventoryWindow; use pocketmine\block\tile\Chest as TileChest; diff --git a/src/block/inventory/DoubleChestInventory.php b/src/block/inventory/DoubleChestInventory.php deleted file mode 100644 index 5c11d1a7af7..00000000000 --- a/src/block/inventory/DoubleChestInventory.php +++ /dev/null @@ -1,99 +0,0 @@ -left->getSize() + $this->right->getSize(); - } - - public function getItem(int $index) : Item{ - return $index < $this->left->getSize() ? $this->left->getItem($index) : $this->right->getItem($index - $this->left->getSize()); - } - - protected function internalSetItem(int $index, Item $item) : void{ - $index < $this->left->getSize() ? $this->left->setItem($index, $item) : $this->right->setItem($index - $this->left->getSize(), $item); - } - - public function getContents(bool $includeEmpty = false) : array{ - $result = $this->left->getContents($includeEmpty); - $leftSize = $this->left->getSize(); - - foreach($this->right->getContents($includeEmpty) as $i => $item){ - $result[$i + $leftSize] = $item; - } - - return $result; - } - - protected function internalSetContents(array $items) : void{ - $leftSize = $this->left->getSize(); - - $leftContents = []; - $rightContents = []; - - foreach($items as $i => $item){ - if($i < $this->left->getSize()){ - $leftContents[$i] = $item; - }else{ - $rightContents[$i - $leftSize] = $item; - } - } - $this->left->setContents($leftContents); - $this->right->setContents($rightContents); - } - - public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ - $leftSize = $this->left->getSize(); - return $slot < $leftSize ? - $this->left->getMatchingItemCount($slot, $test, $checkTags) : - $this->right->getMatchingItemCount($slot - $leftSize, $test, $checkTags); - } - - public function isSlotEmpty(int $index) : bool{ - $leftSize = $this->left->getSize(); - return $index < $leftSize ? - $this->left->isSlotEmpty($index) : - $this->right->isSlotEmpty($index - $leftSize); - } - - public function getLeftSide() : Inventory{ - return $this->left; - } - - public function getRightSide() : Inventory{ - return $this->right; - } -} diff --git a/src/block/tile/Chest.php b/src/block/tile/Chest.php index b66e849c7a7..1c21a650fe0 100644 --- a/src/block/tile/Chest.php +++ b/src/block/tile/Chest.php @@ -23,7 +23,7 @@ namespace pocketmine\block\tile; -use pocketmine\block\inventory\DoubleChestInventory; +use pocketmine\inventory\CombinedInventory; use pocketmine\inventory\Inventory; use pocketmine\inventory\SimpleInventory; use pocketmine\math\Vector3; @@ -46,7 +46,7 @@ class Chest extends Spawnable implements ContainerTile, Nameable{ public const TAG_PAIR_LEAD = "pairlead"; protected Inventory $inventory; - protected ?DoubleChestInventory $doubleInventory = null; + protected ?CombinedInventory $doubleInventory = null; private ?int $pairX = null; private ?int $pairZ = null; @@ -113,11 +113,11 @@ protected function onBlockDestroyedHook() : void{ $this->containerTraitBlockDestroyedHook(); } - public function getInventory() : Inventory|DoubleChestInventory{ + public function getInventory() : Inventory|CombinedInventory{ if($this->isPaired() && $this->doubleInventory === null){ $this->checkPairing(); } - return $this->doubleInventory instanceof DoubleChestInventory ? $this->doubleInventory : $this->inventory; + return $this->doubleInventory ?? $this->inventory; } public function getRealInventory() : Inventory{ @@ -139,9 +139,9 @@ protected function checkPairing() : void{ $this->doubleInventory = $pair->doubleInventory; }else{ if(($pair->position->x + ($pair->position->z << 15)) > ($this->position->x + ($this->position->z << 15))){ //Order them correctly - $this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($pair->inventory, $this->inventory); + $this->doubleInventory = $pair->doubleInventory = new CombinedInventory([$pair->inventory, $this->inventory]); }else{ - $this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($this->inventory, $pair->inventory); + $this->doubleInventory = $pair->doubleInventory = new CombinedInventory([$this->inventory, $pair->inventory]); } } } diff --git a/src/inventory/CombinedInventory.php b/src/inventory/CombinedInventory.php new file mode 100644 index 00000000000..922f1bc0d71 --- /dev/null +++ b/src/inventory/CombinedInventory.php @@ -0,0 +1,135 @@ + + */ + private array $backingInventories = []; + /** + * @var Inventory[] + * @phpstan-var array + */ + private array $slotToInventoryMap = []; + /** + * @var int[] + * @phpstan-var array + */ + private array $inventoryToOffsetMap = []; + + /** + * @phpstan-param Inventory[] $backingInventories + */ + public function __construct( + array $backingInventories + ){ + parent::__construct(); + foreach($backingInventories as $backingInventory){ + $this->backingInventories[spl_object_id($backingInventory)] = $backingInventory; + } + $combinedSize = 0; + foreach($this->backingInventories as $inventory){ + $size = $inventory->getSize(); + + $this->inventoryToOffsetMap[spl_object_id($inventory)] = $combinedSize; + for($slot = 0; $slot < $size; $slot++){ + $this->slotToInventoryMap[$combinedSize + $slot] = $inventory; + } + + $combinedSize += $size; + } + $this->size = $combinedSize; + } + + /** + * @phpstan-return array{Inventory, int} + */ + private function getInventory(int $slot) : array{ + $inventory = $this->slotToInventoryMap[$slot] ?? throw new \InvalidArgumentException("Invalid combined inventory slot $slot"); + $actualSlot = $slot - $this->inventoryToOffsetMap[spl_object_id($inventory)]; + return [$inventory, $actualSlot]; + } + + protected function internalSetItem(int $index, Item $item) : void{ + [$inventory, $actualSlot] = $this->getInventory($index); + $inventory->setItem($actualSlot, $item); + } + + protected function internalSetContents(array $items) : void{ + $contentsByInventory = array_fill_keys(array_keys($this->backingInventories), []); + foreach($items as $i => $item){ + [$inventory, $actualSlot] = $this->getInventory($i); + $contentsByInventory[spl_object_id($inventory)][$actualSlot] = $item; + } + foreach($contentsByInventory as $splObjectId => $backingInventoryContents){ + $backingInventory = $this->backingInventories[$splObjectId]; + $backingInventory->setContents($backingInventoryContents); + } + } + + public function getSize() : int{ + return $this->size; + } + + public function getItem(int $index) : Item{ + [$inventory, $actualSlot] = $this->getInventory($index); + return $inventory->getItem($actualSlot); + } + + public function getContents(bool $includeEmpty = false) : array{ + $result = []; + foreach($this->backingInventories as $inventory){ + $offset = $this->inventoryToOffsetMap[spl_object_id($inventory)]; + foreach($inventory->getContents($includeEmpty) as $i => $item){ + $result[$offset + $i] = $item; + } + } + + return $result; + } + + public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ + [$inventory, $actualSlot] = $this->getInventory($slot); + return $inventory->getMatchingItemCount($actualSlot, $test, $checkTags); + } + + public function isSlotEmpty(int $index) : bool{ + [$inventory, $actualSlot] = $this->getInventory($index); + return $inventory->isSlotEmpty($actualSlot); + } +} diff --git a/tests/phpunit/inventory/CombinedInventoryTest.php b/tests/phpunit/inventory/CombinedInventoryTest.php new file mode 100644 index 00000000000..44c65b0e0d8 --- /dev/null +++ b/tests/phpunit/inventory/CombinedInventoryTest.php @@ -0,0 +1,180 @@ + + */ + private function createInventories() : array{ + $inventory1 = new SimpleInventory(1); + $inventory1->setItem(0, VanillaItems::APPLE()); + $inventory2 = new SimpleInventory(1); + $inventory2->setItem(0, VanillaItems::PAPER()); + $inventory3 = new SimpleInventory(2); + $inventory3->setItem(1, VanillaItems::BONE()); + + return [$inventory1, $inventory2, $inventory3]; + } + + /** + * @param Item[] $items + * @phpstan-param array $items + */ + private function verifyReadItems(array $items) : void{ + self::assertSame(ItemTypeIds::APPLE, $items[0]->getTypeId()); + self::assertSame(ItemTypeIds::PAPER, $items[1]->getTypeId()); + self::assertTrue($items[2]->isNull()); + self::assertSame(ItemTypeIds::BONE, $items[3]->getTypeId()); + } + + /** + * @return Item[] + * @phpstan-return list + */ + private static function getAltItems() : array{ + return [ + VanillaItems::AMETHYST_SHARD(), + VanillaItems::AIR(), //null item + VanillaItems::BLAZE_POWDER(), + VanillaItems::BRICK() + ]; + } + + public function testGetItem() : void{ + $inventory = new CombinedInventory($this->createInventories()); + + $this->verifyReadItems([ + $inventory->getItem(0), + $inventory->getItem(1), + $inventory->getItem(2), + $inventory->getItem(3) + ]); + + $this->expectException(\InvalidArgumentException::class); + $inventory->getItem(4); + } + + public function testGetContents() : void{ + $inventory = new CombinedInventory($this->createInventories()); + + $this->verifyReadItems($inventory->getContents(includeEmpty: true)); + + $contentsWithoutEmpty = $inventory->getContents(includeEmpty: false); + self::assertFalse(isset($contentsWithoutEmpty[2]), "This index should not be set during this test"); + self::assertCount(3, $contentsWithoutEmpty); + $this->verifyReadItems([ + $contentsWithoutEmpty[0], + $contentsWithoutEmpty[1], + VanillaItems::AIR(), + $contentsWithoutEmpty[3] + ]); + } + + /** + * @param Inventory[] $backing + * @param Item[] $altItems + * + * @phpstan-param array $backing + * @phpstan-param array $altItems + */ + private function verifyWriteItems(array $backing, array $altItems) : void{ + foreach([ + 0 => [$backing[0], 0], + 1 => [$backing[1], 0], + 2 => [$backing[2], 0], + 3 => [$backing[2], 1] + ] as $combinedSlot => [$backingInventory, $backingSlot]){ + if(!isset($altItems[$combinedSlot])){ + self::assertTrue($backingInventory->isSlotEmpty($backingSlot)); + }else{ + self::assertSame($altItems[$combinedSlot]->getTypeId(), $backingInventory->getItem($backingSlot)->getTypeId()); + } + } + } + + public function testSetItem() : void{ + $backing = $this->createInventories(); + $inventory = new CombinedInventory($backing); + + $altItems = self::getAltItems(); + foreach($altItems as $slot => $item){ + $inventory->setItem($slot, $item); + } + $this->verifyWriteItems($backing, $altItems); + + $this->expectException(\InvalidArgumentException::class); + $inventory->setItem(4, VanillaItems::BRICK()); + } + + /** + * @phpstan-return \Generator}, void, void> + */ + public static function setContentsProvider() : \Generator{ + $altItems = self::getAltItems(); + + yield [$altItems]; + yield [array_filter($altItems, fn(Item $item) => !$item->isNull())]; + } + + /** + * @dataProvider setContentsProvider + * @param Item[] $altItems + * @phpstan-param array $altItems + */ + public function testSetContents(array $altItems) : void{ + $backing = $this->createInventories(); + $inventory = new CombinedInventory($backing); + $inventory->setContents($altItems); + + $this->verifyWriteItems($backing, $altItems); + } + + public function testGetSize() : void{ + self::assertSame(4, (new CombinedInventory($this->createInventories()))->getSize()); + } + + public function testGetMatchingItemCount() : void{ + $inventory = new CombinedInventory($this->createInventories()); + //we don't need to test the base functionality, only ensure that the correct delegate is called + self::assertSame(1, $inventory->getMatchingItemCount(3, VanillaItems::BONE(), true)); + self::assertNotSame(1, $inventory->getMatchingItemCount(3, VanillaItems::PAPER(), true)); + } + + public function testIsSlotEmpty() : void{ + $inventory = new CombinedInventory($this->createInventories()); + + self::assertTrue($inventory->isSlotEmpty(2)); + self::assertFalse($inventory->isSlotEmpty(0)); + self::assertFalse($inventory->isSlotEmpty(1)); + self::assertFalse($inventory->isSlotEmpty(3)); + } +} From 9e6e5082cdab56146c7c1c6d26deb8d6ba4004c7 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 7 Dec 2024 18:55:09 +0000 Subject: [PATCH 24/28] stfu --- tests/phpunit/inventory/CombinedInventoryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/inventory/CombinedInventoryTest.php b/tests/phpunit/inventory/CombinedInventoryTest.php index 44c65b0e0d8..9bd1bed65de 100644 --- a/tests/phpunit/inventory/CombinedInventoryTest.php +++ b/tests/phpunit/inventory/CombinedInventoryTest.php @@ -101,7 +101,7 @@ public function testGetContents() : void{ /** * @param Inventory[] $backing - * @param Item[] $altItems + * @param Item[] $altItems * * @phpstan-param array $backing * @phpstan-param array $altItems From 3ee78e20a5dce97859686ab205e293e3db5a00d1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 7 Dec 2024 19:28:10 +0000 Subject: [PATCH 25/28] BlockInventoryTrait: include a Block ref instead of Position --- src/block/Anvil.php | 2 +- src/block/Barrel.php | 2 +- src/block/BrewingStand.php | 2 +- src/block/CartographyTable.php | 2 +- src/block/Chest.php | 4 ++-- src/block/CraftingTable.php | 2 +- src/block/EnchantingTable.php | 2 +- src/block/EnderChest.php | 2 +- src/block/Furnace.php | 2 +- src/block/Hopper.php | 2 +- src/block/Loom.php | 2 +- src/block/ShulkerBox.php | 2 +- src/block/SmithingTable.php | 2 +- src/block/Stonecutter.php | 2 +- .../inventory/window/AnvilInventoryWindow.php | 4 ++-- .../inventory/window/BlockInventoryWindow.php | 16 +++++++--------- .../window/CartographyTableInventoryWindow.php | 4 ++-- .../window/CraftingTableInventoryWindow.php | 4 ++-- .../window/DoubleChestInventoryWindow.php | 10 +++++----- .../window/EnchantingTableInventoryWindow.php | 6 +++--- .../inventory/window/FurnaceInventoryWindow.php | 4 ++-- .../inventory/window/LoomInventoryWindow.php | 4 ++-- .../window/SmithingTableInventoryWindow.php | 4 ++-- .../window/StonecutterInventoryWindow.php | 4 ++-- src/network/mcpe/InventoryManager.php | 2 +- 25 files changed, 45 insertions(+), 47 deletions(-) diff --git a/src/block/Anvil.php b/src/block/Anvil.php index b0c7d3c8fcc..e2455da9be6 100644 --- a/src/block/Anvil.php +++ b/src/block/Anvil.php @@ -83,7 +83,7 @@ public function getSupportType(int $facing) : SupportType{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player instanceof Player){ - $player->setCurrentWindow(new AnvilInventoryWindow($player, $this->position)); + $player->setCurrentWindow(new AnvilInventoryWindow($player, $this)); } return true; diff --git a/src/block/Barrel.php b/src/block/Barrel.php index 1b64e814c58..999f82db47a 100644 --- a/src/block/Barrel.php +++ b/src/block/Barrel.php @@ -89,7 +89,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } - $player->setCurrentWindow(new BlockInventoryWindow($player, $barrel->getInventory(), $this->position)); + $player->setCurrentWindow(new BlockInventoryWindow($player, $barrel->getInventory(), $this)); } } diff --git a/src/block/BrewingStand.php b/src/block/BrewingStand.php index dc961aa5592..c43747e40de 100644 --- a/src/block/BrewingStand.php +++ b/src/block/BrewingStand.php @@ -101,7 +101,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player if($player instanceof Player){ $stand = $this->position->getWorld()->getTile($this->position); if($stand instanceof TileBrewingStand && $stand->canOpenWith($item->getCustomName())){ - $player->setCurrentWindow(new BrewingStandInventoryWindow($player, $stand->getInventory(), $this->position)); + $player->setCurrentWindow(new BrewingStandInventoryWindow($player, $stand->getInventory(), $this)); } } diff --git a/src/block/CartographyTable.php b/src/block/CartographyTable.php index 1c3e94096aa..436850e83bd 100644 --- a/src/block/CartographyTable.php +++ b/src/block/CartographyTable.php @@ -32,7 +32,7 @@ final class CartographyTable extends Opaque{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ - $player->setCurrentWindow(new CartographyTableInventoryWindow($player, $this->position)); + $player->setCurrentWindow(new CartographyTableInventoryWindow($player, $this)); } return true; diff --git a/src/block/Chest.php b/src/block/Chest.php index 13712cab958..d872693bd1f 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -117,7 +117,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player } if($pair !== null){ - [$left, $right] = $pairOnLeft ? [$pair->getPosition(), $this->position] : [$this->position, $pair->getPosition()]; + [$left, $right] = $pairOnLeft ? [$pair->getBlock(), $this] : [$this, $pair->getBlock()]; //TODO: we should probably construct DoubleChestInventory here directly too using the same logic //right now it uses some weird logic in TileChest which produces incorrect results @@ -125,7 +125,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $window = new DoubleChestInventoryWindow($player, $chest->getInventory(), $left, $right); } - $player->setCurrentWindow($window ?? new BlockInventoryWindow($player, $chest->getInventory(), $this->position)); + $player->setCurrentWindow($window ?? new BlockInventoryWindow($player, $chest->getInventory(), $this)); } } diff --git a/src/block/CraftingTable.php b/src/block/CraftingTable.php index 2b73d221aeb..06824824b76 100644 --- a/src/block/CraftingTable.php +++ b/src/block/CraftingTable.php @@ -32,7 +32,7 @@ class CraftingTable extends Opaque{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player instanceof Player){ - $player->setCurrentWindow(new CraftingTableInventoryWindow($player, $this->position)); + $player->setCurrentWindow(new CraftingTableInventoryWindow($player, $this)); } return true; diff --git a/src/block/EnchantingTable.php b/src/block/EnchantingTable.php index 46ecc070277..db052ecf5fd 100644 --- a/src/block/EnchantingTable.php +++ b/src/block/EnchantingTable.php @@ -48,7 +48,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player if($player instanceof Player){ //TODO lock - $player->setCurrentWindow(new EnchantingTableInventoryWindow($player, $this->position)); + $player->setCurrentWindow(new EnchantingTableInventoryWindow($player, $this)); } return true; diff --git a/src/block/EnderChest.php b/src/block/EnderChest.php index c5901d3f4bb..f1fee127eb1 100644 --- a/src/block/EnderChest.php +++ b/src/block/EnderChest.php @@ -68,7 +68,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player if($player instanceof Player){ $enderChest = $this->position->getWorld()->getTile($this->position); if($enderChest instanceof TileEnderChest && $this->getSide(Facing::UP)->isTransparent()){ - $player->setCurrentWindow(new BlockInventoryWindow($player, $player->getEnderInventory(), $this->position)); + $player->setCurrentWindow(new BlockInventoryWindow($player, $player->getEnderInventory(), $this)); } } diff --git a/src/block/Furnace.php b/src/block/Furnace.php index 2c4433413bd..412ac74a818 100644 --- a/src/block/Furnace.php +++ b/src/block/Furnace.php @@ -62,7 +62,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player if($player instanceof Player){ $furnace = $this->position->getWorld()->getTile($this->position); if($furnace instanceof TileFurnace && $furnace->canOpenWith($item->getCustomName())){ - $player->setCurrentWindow(new FurnaceInventoryWindow($player, $furnace->getInventory(), $this->position, $this->furnaceType)); + $player->setCurrentWindow(new FurnaceInventoryWindow($player, $furnace->getInventory(), $this, $this->furnaceType)); } } diff --git a/src/block/Hopper.php b/src/block/Hopper.php index 8c65e836c9d..67273b04dfd 100644 --- a/src/block/Hopper.php +++ b/src/block/Hopper.php @@ -85,7 +85,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player if($player !== null){ $tile = $this->position->getWorld()->getTile($this->position); if($tile instanceof TileHopper){ //TODO: find a way to have inventories open on click without this boilerplate in every block - $player->setCurrentWindow(new HopperInventoryWindow($player, $tile->getInventory(), $this->position)); + $player->setCurrentWindow(new HopperInventoryWindow($player, $tile->getInventory(), $this)); } return true; } diff --git a/src/block/Loom.php b/src/block/Loom.php index d19fc9449d9..a4d6f7da419 100644 --- a/src/block/Loom.php +++ b/src/block/Loom.php @@ -34,7 +34,7 @@ final class Loom extends Opaque{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ - $player->setCurrentWindow(new LoomInventoryWindow($player, $this->position)); + $player->setCurrentWindow(new LoomInventoryWindow($player, $this)); return true; } return false; diff --git a/src/block/ShulkerBox.php b/src/block/ShulkerBox.php index 17003396336..c575ecae1b4 100644 --- a/src/block/ShulkerBox.php +++ b/src/block/ShulkerBox.php @@ -115,7 +115,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } - $player->setCurrentWindow(new BlockInventoryWindow($player, $shulker->getInventory(), $this->position)); + $player->setCurrentWindow(new BlockInventoryWindow($player, $shulker->getInventory(), $this)); } } diff --git a/src/block/SmithingTable.php b/src/block/SmithingTable.php index b96a582d100..77816c42dee 100644 --- a/src/block/SmithingTable.php +++ b/src/block/SmithingTable.php @@ -32,7 +32,7 @@ final class SmithingTable extends Opaque{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ - $player->setCurrentWindow(new SmithingTableInventoryWindow($player, $this->position)); + $player->setCurrentWindow(new SmithingTableInventoryWindow($player, $this)); } return true; diff --git a/src/block/Stonecutter.php b/src/block/Stonecutter.php index 3c22e74a83e..20fc73da52a 100644 --- a/src/block/Stonecutter.php +++ b/src/block/Stonecutter.php @@ -37,7 +37,7 @@ class Stonecutter extends Transparent{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ - $player->setCurrentWindow(new StonecutterInventoryWindow($player, $this->position)); + $player->setCurrentWindow(new StonecutterInventoryWindow($player, $this)); } return true; } diff --git a/src/block/inventory/window/AnvilInventoryWindow.php b/src/block/inventory/window/AnvilInventoryWindow.php index 2b994a90da7..8137201514f 100644 --- a/src/block/inventory/window/AnvilInventoryWindow.php +++ b/src/block/inventory/window/AnvilInventoryWindow.php @@ -23,10 +23,10 @@ namespace pocketmine\block\inventory\window; +use pocketmine\block\Block; use pocketmine\inventory\SimpleInventory; use pocketmine\player\Player; use pocketmine\player\TemporaryInventoryWindow; -use pocketmine\world\Position; final class AnvilInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ public const SLOT_INPUT = 0; @@ -34,7 +34,7 @@ final class AnvilInventoryWindow extends BlockInventoryWindow implements Tempora public function __construct( Player $viewer, - Position $holder + Block $holder ){ parent::__construct($viewer, new SimpleInventory(2), $holder); } diff --git a/src/block/inventory/window/BlockInventoryWindow.php b/src/block/inventory/window/BlockInventoryWindow.php index 149c2e81426..edb45012bc5 100644 --- a/src/block/inventory/window/BlockInventoryWindow.php +++ b/src/block/inventory/window/BlockInventoryWindow.php @@ -23,36 +23,34 @@ namespace pocketmine\block\inventory\window; +use pocketmine\block\Block; use pocketmine\block\utils\AnimatedContainer; use pocketmine\inventory\Inventory; use pocketmine\player\InventoryWindow; use pocketmine\player\Player; -use pocketmine\world\Position; class BlockInventoryWindow extends InventoryWindow{ public function __construct( Player $viewer, Inventory $inventory, - protected Position $holder + protected Block $holder ){ parent::__construct($viewer, $inventory); } - public function getHolder() : Position{ return $this->holder; } + public function getHolder() : Block{ return $this->holder; } public function onOpen() : void{ parent::onOpen(); - $block = $this->holder->getWorld()->getBlock($this->holder); - if($block instanceof AnimatedContainer){ - $block->onContainerOpen(); + if($this->holder instanceof AnimatedContainer){ + $this->holder->onContainerOpen(); } } public function onClose() : void{ - $block = $this->holder->getWorld()->getBlock($this->holder); - if($block instanceof AnimatedContainer){ - $block->onContainerClose(); + if($this->holder instanceof AnimatedContainer){ + $this->holder->onContainerClose(); } parent::onClose(); } diff --git a/src/block/inventory/window/CartographyTableInventoryWindow.php b/src/block/inventory/window/CartographyTableInventoryWindow.php index 22d2d5c430f..c9998231c30 100644 --- a/src/block/inventory/window/CartographyTableInventoryWindow.php +++ b/src/block/inventory/window/CartographyTableInventoryWindow.php @@ -23,16 +23,16 @@ namespace pocketmine\block\inventory\window; +use pocketmine\block\Block; use pocketmine\inventory\SimpleInventory; use pocketmine\player\Player; use pocketmine\player\TemporaryInventoryWindow; -use pocketmine\world\Position; final class CartographyTableInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ public function __construct( Player $viewer, - Position $holder + Block $holder ){ parent::__construct($viewer, new SimpleInventory(2), $holder); } diff --git a/src/block/inventory/window/CraftingTableInventoryWindow.php b/src/block/inventory/window/CraftingTableInventoryWindow.php index 46e5485e5fb..914a307965c 100644 --- a/src/block/inventory/window/CraftingTableInventoryWindow.php +++ b/src/block/inventory/window/CraftingTableInventoryWindow.php @@ -23,15 +23,15 @@ namespace pocketmine\block\inventory\window; +use pocketmine\block\Block; use pocketmine\crafting\CraftingGrid; use pocketmine\player\Player; -use pocketmine\world\Position; final class CraftingTableInventoryWindow extends BlockInventoryWindow{ public function __construct( Player $viewer, - Position $holder + Block $holder ){ //TODO: generics would be good for this, since it has special methods parent::__construct($viewer, new CraftingGrid(CraftingGrid::SIZE_BIG), $holder); diff --git a/src/block/inventory/window/DoubleChestInventoryWindow.php b/src/block/inventory/window/DoubleChestInventoryWindow.php index 801a6ff9d0f..3fcf96ac5e9 100644 --- a/src/block/inventory/window/DoubleChestInventoryWindow.php +++ b/src/block/inventory/window/DoubleChestInventoryWindow.php @@ -23,22 +23,22 @@ namespace pocketmine\block\inventory\window; +use pocketmine\block\Block; use pocketmine\inventory\Inventory; use pocketmine\player\Player; -use pocketmine\world\Position; final class DoubleChestInventoryWindow extends BlockInventoryWindow{ public function __construct( Player $viewer, Inventory $inventory, - private Position $left, - private Position $right + private Block $left, + private Block $right ){ parent::__construct($viewer, $inventory, $this->left); } - public function getLeft() : Position{ return $this->left; } + public function getLeft() : Block{ return $this->left; } - public function getRight() : Position{ return $this->right; } + public function getRight() : Block{ return $this->right; } } diff --git a/src/block/inventory/window/EnchantingTableInventoryWindow.php b/src/block/inventory/window/EnchantingTableInventoryWindow.php index 294c1818908..d6ea754a90b 100644 --- a/src/block/inventory/window/EnchantingTableInventoryWindow.php +++ b/src/block/inventory/window/EnchantingTableInventoryWindow.php @@ -23,6 +23,7 @@ namespace pocketmine\block\inventory\window; +use pocketmine\block\Block; use pocketmine\event\player\PlayerEnchantingOptionsRequestEvent; use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\Inventory; @@ -32,7 +33,6 @@ use pocketmine\item\enchantment\EnchantingOption; use pocketmine\item\Item; use pocketmine\player\Player; -use pocketmine\world\Position; use function array_values; use function count; @@ -47,7 +47,7 @@ final class EnchantingTableInventoryWindow extends BlockInventoryWindow{ public function __construct( Player $viewer, - Position $holder + Block $holder ){ parent::__construct($viewer, new SimpleInventory(2), $holder); @@ -75,7 +75,7 @@ public function __destruct(){ private function regenerateOptions() : void{ $this->options = []; $item = $this->getInput(); - $options = Helper::generateOptions($this->holder, $item, $this->viewer->getEnchantmentSeed()); + $options = Helper::generateOptions($this->holder->getPosition(), $item, $this->viewer->getEnchantmentSeed()); $event = new PlayerEnchantingOptionsRequestEvent($this->viewer, $this, $options); $event->call(); diff --git a/src/block/inventory/window/FurnaceInventoryWindow.php b/src/block/inventory/window/FurnaceInventoryWindow.php index 11d36324c76..9667bf6315e 100644 --- a/src/block/inventory/window/FurnaceInventoryWindow.php +++ b/src/block/inventory/window/FurnaceInventoryWindow.php @@ -23,11 +23,11 @@ namespace pocketmine\block\inventory\window; +use pocketmine\block\Block; use pocketmine\crafting\FurnaceType; use pocketmine\inventory\Inventory; use pocketmine\item\Item; use pocketmine\player\Player; -use pocketmine\world\Position; final class FurnaceInventoryWindow extends BlockInventoryWindow{ public const SLOT_INPUT = 0; @@ -37,7 +37,7 @@ final class FurnaceInventoryWindow extends BlockInventoryWindow{ public function __construct( Player $viewer, Inventory $inventory, - Position $holder, + Block $holder, private FurnaceType $furnaceType ){ parent::__construct($viewer, $inventory, $holder); diff --git a/src/block/inventory/window/LoomInventoryWindow.php b/src/block/inventory/window/LoomInventoryWindow.php index ceb27ac1ac6..d59050ba451 100644 --- a/src/block/inventory/window/LoomInventoryWindow.php +++ b/src/block/inventory/window/LoomInventoryWindow.php @@ -23,10 +23,10 @@ namespace pocketmine\block\inventory\window; +use pocketmine\block\Block; use pocketmine\inventory\SimpleInventory; use pocketmine\player\Player; use pocketmine\player\TemporaryInventoryWindow; -use pocketmine\world\Position; final class LoomInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ @@ -36,7 +36,7 @@ final class LoomInventoryWindow extends BlockInventoryWindow implements Temporar public function __construct( Player $viewer, - Position $holder + Block $holder ){ parent::__construct($viewer, new SimpleInventory(3), $holder); } diff --git a/src/block/inventory/window/SmithingTableInventoryWindow.php b/src/block/inventory/window/SmithingTableInventoryWindow.php index 7cb850ef0be..4524ecb2a45 100644 --- a/src/block/inventory/window/SmithingTableInventoryWindow.php +++ b/src/block/inventory/window/SmithingTableInventoryWindow.php @@ -23,13 +23,13 @@ namespace pocketmine\block\inventory\window; +use pocketmine\block\Block; use pocketmine\inventory\SimpleInventory; use pocketmine\player\Player; use pocketmine\player\TemporaryInventoryWindow; -use pocketmine\world\Position; final class SmithingTableInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ - public function __construct(Player $viewer, Position $holder){ + public function __construct(Player $viewer, Block $holder){ parent::__construct($viewer, new SimpleInventory(3), $holder); } } diff --git a/src/block/inventory/window/StonecutterInventoryWindow.php b/src/block/inventory/window/StonecutterInventoryWindow.php index a7a29a97430..a17925110fa 100644 --- a/src/block/inventory/window/StonecutterInventoryWindow.php +++ b/src/block/inventory/window/StonecutterInventoryWindow.php @@ -23,15 +23,15 @@ namespace pocketmine\block\inventory\window; +use pocketmine\block\Block; use pocketmine\inventory\SimpleInventory; use pocketmine\player\Player; use pocketmine\player\TemporaryInventoryWindow; -use pocketmine\world\Position; final class StonecutterInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ public const SLOT_INPUT = 0; - public function __construct(Player $viewer, Position $holder){ + public function __construct(Player $viewer, Block $holder){ parent::__construct($viewer, new SimpleInventory(1), $holder); } } diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index c54a2fc229a..58fccbb5f59 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -382,7 +382,7 @@ protected static function createContainerOpen(int $id, InventoryWindow $window) //TODO: we should be using some kind of tagging system to identify the types. Instanceof is flaky especially //if the class isn't final, not to mention being inflexible. if($window instanceof BlockInventoryWindow){ - $blockPosition = BlockPosition::fromVector3($window->getHolder()); + $blockPosition = BlockPosition::fromVector3($window->getHolder()->getPosition()); $windowType = match(true){ $window instanceof LoomInventoryWindow => WindowTypes::LOOM, $window instanceof FurnaceInventoryWindow => match($window->getFurnaceType()){ From 9949e3815ff58962362aa8e960fe8cd8e0bf1220 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 8 Dec 2024 16:15:43 +0000 Subject: [PATCH 26/28] CombinedInventory now propagates updates if its backing inventories were directly modified this was always lacking with DoubleChestInventory and is a major factor in it being basically useless for custom use cases. --- src/inventory/CombinedInventory.php | 63 ++++++++++++- .../inventory/CombinedInventoryTest.php | 88 +++++++++++++++++++ 2 files changed, 149 insertions(+), 2 deletions(-) diff --git a/src/inventory/CombinedInventory.php b/src/inventory/CombinedInventory.php index 922f1bc0d71..aee5170cc10 100644 --- a/src/inventory/CombinedInventory.php +++ b/src/inventory/CombinedInventory.php @@ -24,8 +24,11 @@ namespace pocketmine\inventory; use pocketmine\item\Item; +use pocketmine\item\VanillaItems; +use pocketmine\utils\AssumptionFailedError; use function array_fill_keys; use function array_keys; +use function count; use function spl_object_id; /** @@ -52,6 +55,9 @@ final class CombinedInventory extends BaseInventory{ */ private array $inventoryToOffsetMap = []; + private InventoryListener $backingInventoryListener; + private bool $modifyingBackingInventory = false; + /** * @phpstan-param Inventory[] $backingInventories */ @@ -74,6 +80,45 @@ public function __construct( $combinedSize += $size; } $this->size = $combinedSize; + + $weakThis = \WeakReference::create($this); + $getThis = static fn() => $weakThis->get() ?? throw new AssumptionFailedError("Listener should've been unregistered in __destruct()"); + + $this->backingInventoryListener = new CallbackInventoryListener( + onSlotChange: static function(Inventory $inventory, int $slot, Item $oldItem) use ($getThis) : void{ + $strongThis = $getThis(); + if($strongThis->modifyingBackingInventory){ + return; + } + + $offset = $strongThis->inventoryToOffsetMap[spl_object_id($inventory)]; + $strongThis->onSlotChange($offset + $slot, $oldItem); + }, + onContentChange: static function(Inventory $inventory, array $oldContents) use ($getThis) : void{ + $strongThis = $getThis(); + if($strongThis->modifyingBackingInventory){ + return; + } + + if(count($strongThis->backingInventories) === 1){ + $strongThis->onContentChange($oldContents); + }else{ + $offset = $strongThis->inventoryToOffsetMap[spl_object_id($inventory)]; + for($slot = 0, $limit = $inventory->getSize(); $slot < $limit; $slot++){ + $strongThis->onSlotChange($offset + $slot, $oldContents[$slot] ?? VanillaItems::AIR()); + } + } + } + ); + foreach($this->backingInventories as $inventory){ + $inventory->getListeners()->add($this->backingInventoryListener); + } + } + + public function __destruct(){ + foreach($this->backingInventories as $inventory){ + $inventory->getListeners()->remove($this->backingInventoryListener); + } } /** @@ -87,7 +132,14 @@ private function getInventory(int $slot) : array{ protected function internalSetItem(int $index, Item $item) : void{ [$inventory, $actualSlot] = $this->getInventory($index); - $inventory->setItem($actualSlot, $item); + + //Make sure our backing listener doesn't dispatch double updates to our own listeners + $this->modifyingBackingInventory = true; + try{ + $inventory->setItem($actualSlot, $item); + }finally{ + $this->modifyingBackingInventory = false; + } } protected function internalSetContents(array $items) : void{ @@ -98,7 +150,14 @@ protected function internalSetContents(array $items) : void{ } foreach($contentsByInventory as $splObjectId => $backingInventoryContents){ $backingInventory = $this->backingInventories[$splObjectId]; - $backingInventory->setContents($backingInventoryContents); + + //Make sure our backing listener doesn't dispatch double updates to our own listeners + $this->modifyingBackingInventory = true; + try{ + $backingInventory->setContents($backingInventoryContents); + }finally{ + $this->modifyingBackingInventory = false; + } } } diff --git a/tests/phpunit/inventory/CombinedInventoryTest.php b/tests/phpunit/inventory/CombinedInventoryTest.php index 9bd1bed65de..b62786ffec2 100644 --- a/tests/phpunit/inventory/CombinedInventoryTest.php +++ b/tests/phpunit/inventory/CombinedInventoryTest.php @@ -177,4 +177,92 @@ public function testIsSlotEmpty() : void{ self::assertFalse($inventory->isSlotEmpty(1)); self::assertFalse($inventory->isSlotEmpty(3)); } + + public function testListenersOnProxySlotUpdate() : void{ + $inventory = new CombinedInventory($this->createInventories()); + + $numChanges = 0; + $inventory->getListeners()->add(new CallbackInventoryListener( + onSlotChange: function(Inventory $inventory, int $slot, Item $before) use (&$numChanges) : void{ + $numChanges++; + }, + onContentChange: null + )); + $inventory->setItem(0, VanillaItems::DIAMOND_SWORD()); + self::assertSame(1, $numChanges, "Inventory listener detected wrong number of changes"); + } + + public function testListenersOnProxyContentUpdate() : void{ + $inventory = new CombinedInventory($this->createInventories()); + + $numChanges = 0; + $inventory->getListeners()->add(new CallbackInventoryListener( + onSlotChange: null, + onContentChange: function(Inventory $inventory, array $oldItems) use (&$numChanges) : void{ + $numChanges++; + } + )); + $inventory->setContents(self::getAltItems()); + self::assertSame(1, $numChanges, "Expected onContentChange to be called exactly 1 time"); + } + + public function testListenersOnBackingSlotUpdate() : void{ + $backing = $this->createInventories(); + $inventory = new CombinedInventory($backing); + + $slotChangeDetected = null; + $numChanges = 0; + $inventory->getListeners()->add(new CallbackInventoryListener( + onSlotChange: function(Inventory $inventory, int $slot, Item $before) use (&$slotChangeDetected, &$numChanges) : void{ + $slotChangeDetected = $slot; + $numChanges++; + }, + onContentChange: null + )); + $backing[2]->setItem(0, VanillaItems::DIAMOND_SWORD()); + self::assertNotNull($slotChangeDetected, "Inventory listener didn't hear about backing inventory update"); + self::assertSame(2, $slotChangeDetected, "Inventory listener detected unexpected slot change"); + self::assertSame(1, $numChanges, "Inventory listener detected wrong number of changes"); + } + + /** + * When a combined inventory has multiple backing inventories, content updates of the backing inventories must be + * turned into slot updates on the proxy, to avoid syncing the entire proxy inventory. + */ + public function testListenersOnBackingContentUpdate() : void{ + $backing = $this->createInventories(); + $inventory = new CombinedInventory($backing); + + $slotChanges = []; + $inventory->getListeners()->add(new CallbackInventoryListener( + onSlotChange: function(Inventory $inventory, int $slot, Item $before) use (&$slotChanges) : void{ + $slotChanges[] = $slot; + }, + onContentChange: null + )); + $backing[2]->setContents([VanillaItems::DIAMOND_SWORD(), VanillaItems::DIAMOND()]); + self::assertCount(2, $slotChanges, "Inventory listener detected wrong number of changes"); + self::assertSame([2, 3], $slotChanges, "Incorrect slots updated"); + } + + /** + * If a combined inventory has only 1 backing inventory, content updates on the backing inventory can be directly + * processed as content updates on the proxy inventory without modification. This allows optimizations when only 1 + * backing inventory is used. + * This test verifies that this special case works as expected. + */ + public function testListenersOnSingleBackingContentUpdate() : void{ + $backing = new SimpleInventory(2); + $inventory = new CombinedInventory([$backing]); + + $numChanges = 0; + $inventory->getListeners()->add(new CallbackInventoryListener( + onSlotChange: null, + onContentChange: function(Inventory $inventory, array $oldItems) use (&$numChanges) : void{ + $numChanges++; + } + )); + $inventory->setContents([VanillaItems::DIAMOND_SWORD(), VanillaItems::DIAMOND()]); + self::assertSame(1, $numChanges, "Expected onContentChange to be called exactly 1 time"); + } } From 0fe786af4ddf7c9ea2df2ea1b9205e9f2069983c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 8 Dec 2024 16:28:00 +0000 Subject: [PATCH 27/28] Rename CombinedInventory -> CombinedInventoryProxy --- src/block/tile/Chest.php | 10 +++---- ...ventory.php => CombinedInventoryProxy.php} | 2 +- ...est.php => CombinedInventoryProxyTest.php} | 27 ++++++++++--------- 3 files changed, 20 insertions(+), 19 deletions(-) rename src/inventory/{CombinedInventory.php => CombinedInventoryProxy.php} (99%) rename tests/phpunit/inventory/{CombinedInventoryTest.php => CombinedInventoryProxyTest.php} (90%) diff --git a/src/block/tile/Chest.php b/src/block/tile/Chest.php index 1c21a650fe0..6cb523c7de2 100644 --- a/src/block/tile/Chest.php +++ b/src/block/tile/Chest.php @@ -23,7 +23,7 @@ namespace pocketmine\block\tile; -use pocketmine\inventory\CombinedInventory; +use pocketmine\inventory\CombinedInventoryProxy; use pocketmine\inventory\Inventory; use pocketmine\inventory\SimpleInventory; use pocketmine\math\Vector3; @@ -46,7 +46,7 @@ class Chest extends Spawnable implements ContainerTile, Nameable{ public const TAG_PAIR_LEAD = "pairlead"; protected Inventory $inventory; - protected ?CombinedInventory $doubleInventory = null; + protected ?CombinedInventoryProxy $doubleInventory = null; private ?int $pairX = null; private ?int $pairZ = null; @@ -113,7 +113,7 @@ protected function onBlockDestroyedHook() : void{ $this->containerTraitBlockDestroyedHook(); } - public function getInventory() : Inventory|CombinedInventory{ + public function getInventory() : Inventory|CombinedInventoryProxy{ if($this->isPaired() && $this->doubleInventory === null){ $this->checkPairing(); } @@ -139,9 +139,9 @@ protected function checkPairing() : void{ $this->doubleInventory = $pair->doubleInventory; }else{ if(($pair->position->x + ($pair->position->z << 15)) > ($this->position->x + ($this->position->z << 15))){ //Order them correctly - $this->doubleInventory = $pair->doubleInventory = new CombinedInventory([$pair->inventory, $this->inventory]); + $this->doubleInventory = $pair->doubleInventory = new CombinedInventoryProxy([$pair->inventory, $this->inventory]); }else{ - $this->doubleInventory = $pair->doubleInventory = new CombinedInventory([$this->inventory, $pair->inventory]); + $this->doubleInventory = $pair->doubleInventory = new CombinedInventoryProxy([$this->inventory, $pair->inventory]); } } } diff --git a/src/inventory/CombinedInventory.php b/src/inventory/CombinedInventoryProxy.php similarity index 99% rename from src/inventory/CombinedInventory.php rename to src/inventory/CombinedInventoryProxy.php index aee5170cc10..5b6d1a668db 100644 --- a/src/inventory/CombinedInventory.php +++ b/src/inventory/CombinedInventoryProxy.php @@ -35,7 +35,7 @@ * Allows interacting with several separate inventories via a unified interface * Mainly used for double chests, but could be used for other custom use cases */ -final class CombinedInventory extends BaseInventory{ +final class CombinedInventoryProxy extends BaseInventory{ private readonly int $size; diff --git a/tests/phpunit/inventory/CombinedInventoryTest.php b/tests/phpunit/inventory/CombinedInventoryProxyTest.php similarity index 90% rename from tests/phpunit/inventory/CombinedInventoryTest.php rename to tests/phpunit/inventory/CombinedInventoryProxyTest.php index b62786ffec2..5ab7eb3a87c 100644 --- a/tests/phpunit/inventory/CombinedInventoryTest.php +++ b/tests/phpunit/inventory/CombinedInventoryProxyTest.php @@ -23,12 +23,13 @@ namespace pocketmine\inventory; +use PHPUnit\Framework\TestCase; use pocketmine\item\Item; use pocketmine\item\ItemTypeIds; use pocketmine\item\VanillaItems; use function array_filter; -final class CombinedInventoryTest extends \PHPUnit\Framework\TestCase{ +final class CombinedInventoryProxyTest extends TestCase{ /** * @return Inventory[] @@ -70,7 +71,7 @@ private static function getAltItems() : array{ } public function testGetItem() : void{ - $inventory = new CombinedInventory($this->createInventories()); + $inventory = new CombinedInventoryProxy($this->createInventories()); $this->verifyReadItems([ $inventory->getItem(0), @@ -84,7 +85,7 @@ public function testGetItem() : void{ } public function testGetContents() : void{ - $inventory = new CombinedInventory($this->createInventories()); + $inventory = new CombinedInventoryProxy($this->createInventories()); $this->verifyReadItems($inventory->getContents(includeEmpty: true)); @@ -123,7 +124,7 @@ private function verifyWriteItems(array $backing, array $altItems) : void{ public function testSetItem() : void{ $backing = $this->createInventories(); - $inventory = new CombinedInventory($backing); + $inventory = new CombinedInventoryProxy($backing); $altItems = self::getAltItems(); foreach($altItems as $slot => $item){ @@ -152,25 +153,25 @@ public static function setContentsProvider() : \Generator{ */ public function testSetContents(array $altItems) : void{ $backing = $this->createInventories(); - $inventory = new CombinedInventory($backing); + $inventory = new CombinedInventoryProxy($backing); $inventory->setContents($altItems); $this->verifyWriteItems($backing, $altItems); } public function testGetSize() : void{ - self::assertSame(4, (new CombinedInventory($this->createInventories()))->getSize()); + self::assertSame(4, (new CombinedInventoryProxy($this->createInventories()))->getSize()); } public function testGetMatchingItemCount() : void{ - $inventory = new CombinedInventory($this->createInventories()); + $inventory = new CombinedInventoryProxy($this->createInventories()); //we don't need to test the base functionality, only ensure that the correct delegate is called self::assertSame(1, $inventory->getMatchingItemCount(3, VanillaItems::BONE(), true)); self::assertNotSame(1, $inventory->getMatchingItemCount(3, VanillaItems::PAPER(), true)); } public function testIsSlotEmpty() : void{ - $inventory = new CombinedInventory($this->createInventories()); + $inventory = new CombinedInventoryProxy($this->createInventories()); self::assertTrue($inventory->isSlotEmpty(2)); self::assertFalse($inventory->isSlotEmpty(0)); @@ -179,7 +180,7 @@ public function testIsSlotEmpty() : void{ } public function testListenersOnProxySlotUpdate() : void{ - $inventory = new CombinedInventory($this->createInventories()); + $inventory = new CombinedInventoryProxy($this->createInventories()); $numChanges = 0; $inventory->getListeners()->add(new CallbackInventoryListener( @@ -193,7 +194,7 @@ public function testListenersOnProxySlotUpdate() : void{ } public function testListenersOnProxyContentUpdate() : void{ - $inventory = new CombinedInventory($this->createInventories()); + $inventory = new CombinedInventoryProxy($this->createInventories()); $numChanges = 0; $inventory->getListeners()->add(new CallbackInventoryListener( @@ -208,7 +209,7 @@ public function testListenersOnProxyContentUpdate() : void{ public function testListenersOnBackingSlotUpdate() : void{ $backing = $this->createInventories(); - $inventory = new CombinedInventory($backing); + $inventory = new CombinedInventoryProxy($backing); $slotChangeDetected = null; $numChanges = 0; @@ -231,7 +232,7 @@ public function testListenersOnBackingSlotUpdate() : void{ */ public function testListenersOnBackingContentUpdate() : void{ $backing = $this->createInventories(); - $inventory = new CombinedInventory($backing); + $inventory = new CombinedInventoryProxy($backing); $slotChanges = []; $inventory->getListeners()->add(new CallbackInventoryListener( @@ -253,7 +254,7 @@ public function testListenersOnBackingContentUpdate() : void{ */ public function testListenersOnSingleBackingContentUpdate() : void{ $backing = new SimpleInventory(2); - $inventory = new CombinedInventory([$backing]); + $inventory = new CombinedInventoryProxy([$backing]); $numChanges = 0; $inventory->getListeners()->add(new CallbackInventoryListener( From 0b2a8d27f3610af3232f8ad5fff1f4b55f1ae935 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Tue, 4 Feb 2025 16:48:33 +0000 Subject: [PATCH 28/28] Update SurvivalBlockBreakHandler.php --- src/player/SurvivalBlockBreakHandler.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/player/SurvivalBlockBreakHandler.php b/src/player/SurvivalBlockBreakHandler.php index 97879251caf..3e7fdcfc4b9 100644 --- a/src/player/SurvivalBlockBreakHandler.php +++ b/src/player/SurvivalBlockBreakHandler.php @@ -67,7 +67,6 @@ private function calculateBreakProgressPerTick() : float{ if(!$this->block->getBreakInfo()->isBreakable()){ return 0.0; } - $breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getHotbar()->getHeldItem()) * 20; if(!$this->player->isOnGround() && !$this->player->isFlying()){ $breakTimePerTick *= 5;