diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index dcb3c04cb2e..4d7ddd3e90c 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -23,8 +23,13 @@ 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; use pocketmine\item\Item; +use pocketmine\item\ItemBlock; class ArmorInventory extends SimpleInventory{ public const SLOT_HEAD = 0; @@ -36,6 +41,8 @@ public function __construct( protected Living $holder ){ parent::__construct(4); + + $this->validators->add(new CallbackSlotValidator($this->validate(...))); } public function getHolder() : Living{ @@ -73,4 +80,20 @@ public function setLeggings(Item $leggings) : void{ public function setBoots(Item $boots) : void{ $this->setItem(self::SLOT_FEET, $boots); } + + private function validate(Inventory $inventory, Item $item, int $slot) : ?TransactionValidationException{ + if($item instanceof Armor && $item->getArmorSlot() === $slot){ + if($item->getArmorSlot() !== $slot){ + return new TransactionValidationException("Armor item in wrong slot"); + } + }else{ + if(!($slot === ArmorInventory::SLOT_HEAD && $item instanceof ItemBlock && ( + $item->getBlock()->getTypeId() === BlockTypeIds::CARVED_PUMPKIN || + $item->getBlock()->getTypeId() === BlockTypeIds::MOB_HEAD + ))){ + return new TransactionValidationException("Item not accepted in an armor slot"); + } + } + return null; + } } diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 254e44b1ea0..5b3299bb1d7 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -36,8 +36,10 @@ /** * This class provides everything needed to implement an inventory, minus the underlying storage system. + * + * @phpstan-import-type SlotValidators from SlotSafeInventory */ -abstract class BaseInventory implements Inventory{ +abstract class BaseInventory implements Inventory, SlotSafeInventory{ protected int $maxStackSize = Inventory::MAX_STACK; /** @var Player[] */ protected array $viewers = []; @@ -46,9 +48,12 @@ abstract class BaseInventory implements Inventory{ * @phpstan-var ObjectSet */ protected ObjectSet $listeners; + /** @phpstan-var SlotValidators */ + protected ObjectSet $validators; public function __construct(){ $this->listeners = new ObjectSet(); + $this->validators = new ObjectSet(); } public function getMaxStackSize() : int{ @@ -398,4 +403,8 @@ public function slotExists(int $slot) : bool{ public function getListeners() : ObjectSet{ return $this->listeners; } + + public function getSlotValidators() : ObjectSet{ + return $this->validators; + } } diff --git a/src/inventory/SlotSafeInventory.php b/src/inventory/SlotSafeInventory.php new file mode 100644 index 00000000000..b3c4b5a88d8 --- /dev/null +++ b/src/inventory/SlotSafeInventory.php @@ -0,0 +1,46 @@ + + */ +interface SlotSafeInventory{ + /** + * Returns a set of validators that will be used to determine whether an item can be placed in a particular slot. + * All validators need to return null for the transaction to be allowed. + * If one of the validators returns an exception, the transaction will be cancelled. + * + * There is no guarantee that the validators will be called in any particular order. + * + * @phpstan-return SlotValidators + */ + public function getSlotValidators() : ObjectSet; +} diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index 453f0c4d228..9cfc544d847 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -24,6 +24,7 @@ namespace pocketmine\inventory\transaction\action; use pocketmine\inventory\Inventory; +use pocketmine\inventory\SlotSafeInventory; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; @@ -74,6 +75,14 @@ public function validate(Player $source) : void{ if($this->targetItem->getCount() > $this->inventory->getMaxStackSize()){ throw new TransactionValidationException("Target item exceeds inventory max stack size"); } + if($this->inventory instanceof SlotSafeInventory && !$this->targetItem->isNull()){ + foreach($this->inventory->getSlotValidators() as $validator){ + $ret = $validator->validate($this->inventory, $this->targetItem, $this->inventorySlot); + if($ret !== null){ + throw new TransactionValidationException("Target item is not accepted by the inventory slot: " . $ret->getMessage(), 0, $ret); + } + } + } } /** diff --git a/src/inventory/transaction/action/validator/CallbackSlotValidator.php b/src/inventory/transaction/action/validator/CallbackSlotValidator.php new file mode 100644 index 00000000000..1670dc6232a --- /dev/null +++ b/src/inventory/transaction/action/validator/CallbackSlotValidator.php @@ -0,0 +1,44 @@ +validate)($inventory, $item, $slot); + } +} diff --git a/src/inventory/transaction/action/validator/SlotValidator.php b/src/inventory/transaction/action/validator/SlotValidator.php new file mode 100644 index 00000000000..af2e779c25a --- /dev/null +++ b/src/inventory/transaction/action/validator/SlotValidator.php @@ -0,0 +1,38 @@ +