From 7b21c85d3312a1ed431692988f99e8f8b2a4f273 Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Sun, 24 Mar 2024 20:28:58 +0100 Subject: [PATCH 01/15] Add slot validation at inventory level Each inventory can provide any validator for their transaction --- src/inventory/ArmorInventory.php | 9 +++- src/inventory/SlotSafeInventory.php | 33 ++++++++++++++ .../transaction/action/SlotChangeAction.php | 13 ++++++ .../validator/ArmorInventoryValidator.php | 44 +++++++++++++++++++ .../InventoryTransactionValidator.php | 31 +++++++++++++ 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/inventory/SlotSafeInventory.php create mode 100644 src/inventory/transaction/validator/ArmorInventoryValidator.php create mode 100644 src/inventory/transaction/validator/InventoryTransactionValidator.php diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index dcb3c04cb2e..cf4988f6b0c 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -24,9 +24,10 @@ namespace pocketmine\inventory; use pocketmine\entity\Living; +use pocketmine\inventory\transaction\validator\ArmorInventoryValidator; use pocketmine\item\Item; -class ArmorInventory extends SimpleInventory{ +class ArmorInventory extends SimpleInventory implements SlotSafeInventory{ public const SLOT_HEAD = 0; public const SLOT_CHEST = 1; public const SLOT_LEGS = 2; @@ -73,4 +74,10 @@ public function setLeggings(Item $leggings) : void{ public function setBoots(Item $boots) : void{ $this->setItem(self::SLOT_FEET, $boots); } + + public static function getSlotValidators() : array{ + return [ + new ArmorInventoryValidator() + ]; + } } diff --git a/src/inventory/SlotSafeInventory.php b/src/inventory/SlotSafeInventory.php new file mode 100644 index 00000000000..897fad08b46 --- /dev/null +++ b/src/inventory/SlotSafeInventory.php @@ -0,0 +1,33 @@ +targetItem->getCount() > $this->inventory->getMaxStackSize()){ throw new TransactionValidationException("Target item exceeds inventory max stack size"); } + if($this->inventory instanceof SlotSafeInventory && !$this->targetItem->equals(VanillaItems::AIR())){ + $result = false; + foreach($this->inventory::getSlotValidators() as $validator){ + if(($result = $validator->validate($this->inventory, $this->targetItem, $this->inventorySlot))){ + break; + } + } + if(!$result){ + throw new TransactionValidationException("Target item is not accepted by the inventory"); + } + } } /** diff --git a/src/inventory/transaction/validator/ArmorInventoryValidator.php b/src/inventory/transaction/validator/ArmorInventoryValidator.php new file mode 100644 index 00000000000..41fccac423f --- /dev/null +++ b/src/inventory/transaction/validator/ArmorInventoryValidator.php @@ -0,0 +1,44 @@ +getArmorSlot() === $slot) || + ($slot === ArmorInventory::SLOT_HEAD && $item instanceof ItemBlock && ( + $item->getBlock()->getTypeId() === BlockTypeIds::CARVED_PUMPKIN || + $item->getBlock() instanceof MobHead + )) + ); + } +} diff --git a/src/inventory/transaction/validator/InventoryTransactionValidator.php b/src/inventory/transaction/validator/InventoryTransactionValidator.php new file mode 100644 index 00000000000..e3923caf004 --- /dev/null +++ b/src/inventory/transaction/validator/InventoryTransactionValidator.php @@ -0,0 +1,31 @@ + Date: Sun, 24 Mar 2024 20:49:54 +0100 Subject: [PATCH 02/15] add some docs --- src/inventory/SlotSafeInventory.php | 12 ++++++++++++ .../validator/InventoryTransactionValidator.php | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/inventory/SlotSafeInventory.php b/src/inventory/SlotSafeInventory.php index 897fad08b46..c1d5989c920 100644 --- a/src/inventory/SlotSafeInventory.php +++ b/src/inventory/SlotSafeInventory.php @@ -25,8 +25,20 @@ use pocketmine\inventory\transaction\validator\InventoryTransactionValidator; +/** + * A "slot safe inventory" is an inventory that has a set of rules that determine whether or not an item can be placed in a + * particular slot. This ensures that the inventory's internal state is always valid and consistent. + */ interface SlotSafeInventory{ /** + * Return a list of validators that will be used to determine whether or not an item can be placed in a particular slot. + * All validators need to return true for the transaction to be allowed. + * If one of the validators returns false, the transaction will be cancelled. + * + * There is no guarantee that the validators will be called in any particular order. All validators need to be stateless + * and not depend on the order in which they are called. + * + * @return InventoryTransactionValidator[] * @phpstan-return InventoryTransactionValidator[] */ public static function getSlotValidators() : array; diff --git a/src/inventory/transaction/validator/InventoryTransactionValidator.php b/src/inventory/transaction/validator/InventoryTransactionValidator.php index e3923caf004..afc6e976c99 100644 --- a/src/inventory/transaction/validator/InventoryTransactionValidator.php +++ b/src/inventory/transaction/validator/InventoryTransactionValidator.php @@ -24,8 +24,18 @@ namespace pocketmine\inventory\transaction\validator; use pocketmine\inventory\Inventory; +use pocketmine\inventory\SlotSafeInventory; use pocketmine\item\Item; +/** + * This interface is used to validate the transactions of a "safe" inventory. {@see SlotSafeInventory} + * + * In this way, each inventory can determine whether or not an item has the right to be placed in a particular slot. + */ interface InventoryTransactionValidator{ + /** + * @return bool Return true, if the item CAN be placed in the given slot of the given inventory. + * Otherwise, return false and the transaction will be cancelled. + */ public function validate(Inventory $inventory, Item $item, int $slot) : bool; } From 69e4db604f64dfb3f4d6987b3a9008525927d385 Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Sun, 24 Mar 2024 21:07:53 +0100 Subject: [PATCH 03/15] Use an object set for the validators instead of an array --- src/inventory/ArmorInventory.php | 9 ++--- src/inventory/SlotSafeInventory.php | 14 ++++---- src/inventory/SlotSafeInventoryTrait.php | 45 ++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 src/inventory/SlotSafeInventoryTrait.php diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index cf4988f6b0c..d461092ab3c 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -26,8 +26,11 @@ use pocketmine\entity\Living; use pocketmine\inventory\transaction\validator\ArmorInventoryValidator; use pocketmine\item\Item; +use pocketmine\utils\ObjectSet; class ArmorInventory extends SimpleInventory implements SlotSafeInventory{ + use SlotSafeInventoryTrait; + public const SLOT_HEAD = 0; public const SLOT_CHEST = 1; public const SLOT_LEGS = 2; @@ -75,9 +78,7 @@ public function setBoots(Item $boots) : void{ $this->setItem(self::SLOT_FEET, $boots); } - public static function getSlotValidators() : array{ - return [ - new ArmorInventoryValidator() - ]; + protected static function initSlotValidators(ObjectSet $validators) : void{ + $validators->add(new ArmorInventoryValidator()); } } diff --git a/src/inventory/SlotSafeInventory.php b/src/inventory/SlotSafeInventory.php index c1d5989c920..913517aedb7 100644 --- a/src/inventory/SlotSafeInventory.php +++ b/src/inventory/SlotSafeInventory.php @@ -24,22 +24,22 @@ namespace pocketmine\inventory; use pocketmine\inventory\transaction\validator\InventoryTransactionValidator; +use pocketmine\utils\ObjectSet; /** - * A "slot safe inventory" is an inventory that has a set of rules that determine whether or not an item can be placed in a + * A "slot safe inventory" is an inventory that has a set of rules that determine whether an item can be placed in a * particular slot. This ensures that the inventory's internal state is always valid and consistent. */ interface SlotSafeInventory{ /** - * Return a list of validators that will be used to determine whether or not an item can be placed in a particular slot. + * Return 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 true for the transaction to be allowed. * If one of the validators returns false, the transaction will be cancelled. * - * There is no guarantee that the validators will be called in any particular order. All validators need to be stateless - * and not depend on the order in which they are called. + * There is no guarantee that the validators will be called in any particular order. + * All validators need to be stateless and not depend on the order in which they are called. * - * @return InventoryTransactionValidator[] - * @phpstan-return InventoryTransactionValidator[] + * @phpstan-return ObjectSet */ - public static function getSlotValidators() : array; + public static function getSlotValidators() : ObjectSet; } diff --git a/src/inventory/SlotSafeInventoryTrait.php b/src/inventory/SlotSafeInventoryTrait.php new file mode 100644 index 00000000000..972c0ed47b7 --- /dev/null +++ b/src/inventory/SlotSafeInventoryTrait.php @@ -0,0 +1,45 @@ + Date: Mon, 25 Mar 2024 17:47:22 +0100 Subject: [PATCH 04/15] use Closure and add SlotSafeInventory into BaseInventory by default --- src/inventory/ArmorInventory.php | 22 +++++---- src/inventory/BaseInventory.php | 12 ++++- src/inventory/SlotSafeInventory.php | 11 ++--- src/inventory/SlotSafeInventoryTrait.php | 45 ------------------- .../validator/ArmorInventoryValidator.php | 44 ------------------ .../InventoryTransactionValidator.php | 41 ----------------- 6 files changed, 30 insertions(+), 145 deletions(-) delete mode 100644 src/inventory/SlotSafeInventoryTrait.php delete mode 100644 src/inventory/transaction/validator/ArmorInventoryValidator.php delete mode 100644 src/inventory/transaction/validator/InventoryTransactionValidator.php diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index d461092ab3c..cba341287be 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -23,14 +23,14 @@ namespace pocketmine\inventory; +use pocketmine\block\BlockTypeIds; +use pocketmine\block\MobHead; use pocketmine\entity\Living; -use pocketmine\inventory\transaction\validator\ArmorInventoryValidator; +use pocketmine\item\Armor; use pocketmine\item\Item; -use pocketmine\utils\ObjectSet; - -class ArmorInventory extends SimpleInventory implements SlotSafeInventory{ - use SlotSafeInventoryTrait; +use pocketmine\item\ItemBlock; +class ArmorInventory extends SimpleInventory{ public const SLOT_HEAD = 0; public const SLOT_CHEST = 1; public const SLOT_LEGS = 2; @@ -40,6 +40,14 @@ public function __construct( protected Living $holder ){ parent::__construct(4); + + $this->validators->add(function (Inventory $inventory, Item $item, int $slot) : bool { + return ($item instanceof Armor && $item->getArmorSlot() === $slot) || + ($slot === ArmorInventory::SLOT_HEAD && $item instanceof ItemBlock && ( + $item->getBlock()->getTypeId() === BlockTypeIds::CARVED_PUMPKIN || + $item->getBlock() instanceof MobHead + )); + }); } public function getHolder() : Living{ @@ -77,8 +85,4 @@ public function setLeggings(Item $leggings) : void{ public function setBoots(Item $boots) : void{ $this->setItem(self::SLOT_FEET, $boots); } - - protected static function initSlotValidators(ObjectSet $validators) : void{ - $validators->add(new ArmorInventoryValidator()); - } } diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 254e44b1ea0..93b74ded2de 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -37,7 +37,7 @@ /** * This class provides everything needed to implement an inventory, minus the underlying storage system. */ -abstract class BaseInventory implements Inventory{ +abstract class BaseInventory implements Inventory, SlotSafeInventory{ protected int $maxStackSize = Inventory::MAX_STACK; /** @var Player[] */ protected array $viewers = []; @@ -46,9 +46,15 @@ abstract class BaseInventory implements Inventory{ * @phpstan-var ObjectSet */ protected ObjectSet $listeners; + /** + * @var Closure[]|ObjectSet + * @phpstan-var ObjectSet + */ + protected ObjectSet $validators; public function __construct(){ $this->listeners = new ObjectSet(); + $this->validators = new ObjectSet(); } public function getMaxStackSize() : int{ @@ -398,4 +404,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 index 913517aedb7..ace9883eddc 100644 --- a/src/inventory/SlotSafeInventory.php +++ b/src/inventory/SlotSafeInventory.php @@ -23,7 +23,7 @@ namespace pocketmine\inventory; -use pocketmine\inventory\transaction\validator\InventoryTransactionValidator; +use pocketmine\item\Item; use pocketmine\utils\ObjectSet; /** @@ -33,13 +33,14 @@ interface SlotSafeInventory{ /** * Return 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 true for the transaction to be allowed. - * If one of the validators returns false, the transaction will be cancelled. + * One validator need to return true for the transaction to be allowed. + * If all the validators returns false, the transaction will be cancelled. * * There is no guarantee that the validators will be called in any particular order. * All validators need to be stateless and not depend on the order in which they are called. * - * @phpstan-return ObjectSet + * @return \Closure[]|ObjectSet + * @phpstan-return ObjectSet */ - public static function getSlotValidators() : ObjectSet; + public function getSlotValidators() : ObjectSet; } diff --git a/src/inventory/SlotSafeInventoryTrait.php b/src/inventory/SlotSafeInventoryTrait.php deleted file mode 100644 index 972c0ed47b7..00000000000 --- a/src/inventory/SlotSafeInventoryTrait.php +++ /dev/null @@ -1,45 +0,0 @@ -getArmorSlot() === $slot) || - ($slot === ArmorInventory::SLOT_HEAD && $item instanceof ItemBlock && ( - $item->getBlock()->getTypeId() === BlockTypeIds::CARVED_PUMPKIN || - $item->getBlock() instanceof MobHead - )) - ); - } -} diff --git a/src/inventory/transaction/validator/InventoryTransactionValidator.php b/src/inventory/transaction/validator/InventoryTransactionValidator.php deleted file mode 100644 index afc6e976c99..00000000000 --- a/src/inventory/transaction/validator/InventoryTransactionValidator.php +++ /dev/null @@ -1,41 +0,0 @@ - Date: Mon, 25 Mar 2024 18:11:35 +0100 Subject: [PATCH 05/15] All validators need to approve the transaction --- src/inventory/transaction/action/SlotChangeAction.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index 8467e7b2577..3d55eba48f4 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -77,9 +77,9 @@ public function validate(Player $source) : void{ throw new TransactionValidationException("Target item exceeds inventory max stack size"); } if($this->inventory instanceof SlotSafeInventory && !$this->targetItem->equals(VanillaItems::AIR())){ - $result = false; - foreach($this->inventory::getSlotValidators() as $validator){ - if(($result = $validator->validate($this->inventory, $this->targetItem, $this->inventorySlot))){ + $result = true; + foreach($this->inventory->getSlotValidators() as $validator){ + if(($result = $validator($this->inventory, $this->targetItem, $this->inventorySlot))){ break; } } From 4637a0092a19ee2315457f831cb993a7fabf0753 Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Mon, 25 Mar 2024 18:13:03 +0100 Subject: [PATCH 06/15] use static closure --- src/inventory/ArmorInventory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index cba341287be..073e299808c 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -41,7 +41,7 @@ public function __construct( ){ parent::__construct(4); - $this->validators->add(function (Inventory $inventory, Item $item, int $slot) : bool { + $this->validators->add(static function (Inventory $inventory, Item $item, int $slot) : bool { return ($item instanceof Armor && $item->getArmorSlot() === $slot) || ($slot === ArmorInventory::SLOT_HEAD && $item instanceof ItemBlock && ( $item->getBlock()->getTypeId() === BlockTypeIds::CARVED_PUMPKIN || From f3c85769a2278bb0c681d5df5fd7712225190329 Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Mon, 25 Mar 2024 18:19:28 +0100 Subject: [PATCH 07/15] fix PHPstan --- src/inventory/ArmorInventory.php | 2 +- src/inventory/BaseInventory.php | 6 ++---- src/inventory/SlotSafeInventory.php | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index 073e299808c..e1d3f598833 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -41,7 +41,7 @@ public function __construct( ){ parent::__construct(4); - $this->validators->add(static function (Inventory $inventory, Item $item, int $slot) : bool { + $this->validators->add(static function (Inventory $inventory, Item $item, int $slot) : bool{ return ($item instanceof Armor && $item->getArmorSlot() === $slot) || ($slot === ArmorInventory::SLOT_HEAD && $item instanceof ItemBlock && ( $item->getBlock()->getTypeId() === BlockTypeIds::CARVED_PUMPKIN || diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 93b74ded2de..58201af7bb3 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -23,6 +23,7 @@ namespace pocketmine\inventory; +use Closure; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use pocketmine\player\Player; @@ -46,10 +47,7 @@ abstract class BaseInventory implements Inventory, SlotSafeInventory{ * @phpstan-var ObjectSet */ protected ObjectSet $listeners; - /** - * @var Closure[]|ObjectSet - * @phpstan-var ObjectSet - */ + /** @phpstan-var ObjectSet */ protected ObjectSet $validators; public function __construct(){ diff --git a/src/inventory/SlotSafeInventory.php b/src/inventory/SlotSafeInventory.php index ace9883eddc..009f8aa52ec 100644 --- a/src/inventory/SlotSafeInventory.php +++ b/src/inventory/SlotSafeInventory.php @@ -23,6 +23,7 @@ namespace pocketmine\inventory; +use Closure; use pocketmine\item\Item; use pocketmine\utils\ObjectSet; @@ -39,7 +40,6 @@ interface SlotSafeInventory{ * There is no guarantee that the validators will be called in any particular order. * All validators need to be stateless and not depend on the order in which they are called. * - * @return \Closure[]|ObjectSet * @phpstan-return ObjectSet */ public function getSlotValidators() : ObjectSet; From b1b51b45ca7c3ee5b7180c66e3bffc18033c7181 Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Fri, 29 Mar 2024 21:25:49 +0100 Subject: [PATCH 08/15] Many changes --- src/block/BlockTypeTags.php | 1 + src/inventory/ArmorInventory.php | 3 +-- src/inventory/SlotSafeInventory.php | 9 ++++----- src/inventory/transaction/action/SlotChangeAction.php | 3 +-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/block/BlockTypeTags.php b/src/block/BlockTypeTags.php index 19a4825d965..0aabab609a4 100644 --- a/src/block/BlockTypeTags.php +++ b/src/block/BlockTypeTags.php @@ -31,4 +31,5 @@ final class BlockTypeTags{ public const SAND = self::PREFIX . "sand"; public const POTTABLE_PLANTS = self::PREFIX . "pottable"; public const FIRE = self::PREFIX . "fire"; + public const MOB_HEAD = self::PREFIX . "mob_head"; } diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index e1d3f598833..f9e935b0191 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -24,7 +24,6 @@ namespace pocketmine\inventory; use pocketmine\block\BlockTypeIds; -use pocketmine\block\MobHead; use pocketmine\entity\Living; use pocketmine\item\Armor; use pocketmine\item\Item; @@ -45,7 +44,7 @@ public function __construct( return ($item instanceof Armor && $item->getArmorSlot() === $slot) || ($slot === ArmorInventory::SLOT_HEAD && $item instanceof ItemBlock && ( $item->getBlock()->getTypeId() === BlockTypeIds::CARVED_PUMPKIN || - $item->getBlock() instanceof MobHead + $item->getBlock()->getTypeId() === BlockTypeIds::MOB_HEAD )); }); } diff --git a/src/inventory/SlotSafeInventory.php b/src/inventory/SlotSafeInventory.php index 009f8aa52ec..62f3998c72d 100644 --- a/src/inventory/SlotSafeInventory.php +++ b/src/inventory/SlotSafeInventory.php @@ -28,17 +28,16 @@ use pocketmine\utils\ObjectSet; /** - * A "slot safe inventory" is an inventory that has a set of rules that determine whether an item can be placed in a - * particular slot. This ensures that the inventory's internal state is always valid and consistent. + * A "slot safe inventory" has validators which may restrict items + * from being placed in particular slots of the inventory. */ interface SlotSafeInventory{ /** * Return a set of validators that will be used to determine whether an item can be placed in a particular slot. - * One validator need to return true for the transaction to be allowed. - * If all the validators returns false, the transaction will be cancelled. + * All validator need to return true for the transaction to be allowed. + * If one of the validators returns false, the transaction will be cancelled. * * There is no guarantee that the validators will be called in any particular order. - * All validators need to be stateless and not depend on the order in which they are called. * * @phpstan-return ObjectSet */ diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index 3d55eba48f4..3da1cf58d24 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -28,7 +28,6 @@ use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; -use pocketmine\item\VanillaItems; use pocketmine\player\Player; /** @@ -76,7 +75,7 @@ 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->equals(VanillaItems::AIR())){ + if($this->inventory instanceof SlotSafeInventory && !$this->targetItem->isNull()){ $result = true; foreach($this->inventory->getSlotValidators() as $validator){ if(($result = $validator($this->inventory, $this->targetItem, $this->inventorySlot))){ From 80d2edb65c330723213e7a4638840f9f225590a5 Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Fri, 29 Mar 2024 21:30:32 +0100 Subject: [PATCH 09/15] use @phpstan-type --- src/inventory/BaseInventory.php | 5 +++-- src/inventory/SlotSafeInventory.php | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 58201af7bb3..5b3299bb1d7 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -23,7 +23,6 @@ namespace pocketmine\inventory; -use Closure; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use pocketmine\player\Player; @@ -37,6 +36,8 @@ /** * 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, SlotSafeInventory{ protected int $maxStackSize = Inventory::MAX_STACK; @@ -47,7 +48,7 @@ abstract class BaseInventory implements Inventory, SlotSafeInventory{ * @phpstan-var ObjectSet */ protected ObjectSet $listeners; - /** @phpstan-var ObjectSet */ + /** @phpstan-var SlotValidators */ protected ObjectSet $validators; public function __construct(){ diff --git a/src/inventory/SlotSafeInventory.php b/src/inventory/SlotSafeInventory.php index 62f3998c72d..20732f6941c 100644 --- a/src/inventory/SlotSafeInventory.php +++ b/src/inventory/SlotSafeInventory.php @@ -23,13 +23,14 @@ namespace pocketmine\inventory; -use Closure; use pocketmine\item\Item; use pocketmine\utils\ObjectSet; /** * A "slot safe inventory" has validators which may restrict items * from being placed in particular slots of the inventory. + * + * @phpstan-type SlotValidators ObjectSet<\Closure(Inventory $inventory, Item $item, int $slot): bool> */ interface SlotSafeInventory{ /** @@ -39,7 +40,7 @@ interface SlotSafeInventory{ * * There is no guarantee that the validators will be called in any particular order. * - * @phpstan-return ObjectSet + * @phpstan-return SlotValidators */ public function getSlotValidators() : ObjectSet; } From 6b061b63edbe8b9786ea9678c50ad3ca6f9d5638 Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Fri, 29 Mar 2024 21:38:46 +0100 Subject: [PATCH 10/15] all validators must validate the transaction to be allowed --- src/inventory/transaction/action/SlotChangeAction.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index 3da1cf58d24..d1573a4a02b 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -76,15 +76,11 @@ public function validate(Player $source) : void{ throw new TransactionValidationException("Target item exceeds inventory max stack size"); } if($this->inventory instanceof SlotSafeInventory && !$this->targetItem->isNull()){ - $result = true; foreach($this->inventory->getSlotValidators() as $validator){ - if(($result = $validator($this->inventory, $this->targetItem, $this->inventorySlot))){ - break; + if(!$validator($this->inventory, $this->targetItem, $this->inventorySlot)){ + throw new TransactionValidationException("Target item is not accepted by the inventory"); } } - if(!$result){ - throw new TransactionValidationException("Target item is not accepted by the inventory"); - } } } From a87398dcc171246b2f85e54d3ef1d145473dbe26 Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Fri, 29 Mar 2024 21:40:18 +0100 Subject: [PATCH 11/15] oops --- src/block/BlockTypeTags.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/block/BlockTypeTags.php b/src/block/BlockTypeTags.php index 0aabab609a4..19a4825d965 100644 --- a/src/block/BlockTypeTags.php +++ b/src/block/BlockTypeTags.php @@ -31,5 +31,4 @@ final class BlockTypeTags{ public const SAND = self::PREFIX . "sand"; public const POTTABLE_PLANTS = self::PREFIX . "pottable"; public const FIRE = self::PREFIX . "fire"; - public const MOB_HEAD = self::PREFIX . "mob_head"; } From ba1b6ea05bda6ae6baeee5d8330b5d0baf8679f8 Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Mon, 1 Apr 2024 17:30:58 +0200 Subject: [PATCH 12/15] Apply suggestions from code review Co-authored-by: Dylan T. --- src/inventory/SlotSafeInventory.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/inventory/SlotSafeInventory.php b/src/inventory/SlotSafeInventory.php index 20732f6941c..fbc04e0b038 100644 --- a/src/inventory/SlotSafeInventory.php +++ b/src/inventory/SlotSafeInventory.php @@ -34,8 +34,8 @@ */ interface SlotSafeInventory{ /** - * Return a set of validators that will be used to determine whether an item can be placed in a particular slot. - * All validator need to return true for the transaction to be allowed. + * 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 true for the transaction to be allowed. * If one of the validators returns false, the transaction will be cancelled. * * There is no guarantee that the validators will be called in any particular order. From 2c1ca33c9ebe0979f9e18858ee3e82e78a7e79fc Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Mon, 1 Apr 2024 17:39:38 +0200 Subject: [PATCH 13/15] catch TransactionValidationException --- src/inventory/transaction/action/SlotChangeAction.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index d1573a4a02b..4e238b40e7e 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -77,8 +77,12 @@ public function validate(Player $source) : void{ } if($this->inventory instanceof SlotSafeInventory && !$this->targetItem->isNull()){ foreach($this->inventory->getSlotValidators() as $validator){ - if(!$validator($this->inventory, $this->targetItem, $this->inventorySlot)){ - throw new TransactionValidationException("Target item is not accepted by the inventory"); + try{ + if(!$validator($this->inventory, $this->targetItem, $this->inventorySlot)){ + throw new TransactionValidationException("Target item is not accepted by the inventory"); + } + }catch(TransactionValidationException $e){ + throw new TransactionValidationException("Target item is not accepted by the inventory: " . $e->getMessage()); } } } From 2d8449e9aaad1859673394f9aab0128c4860e1b1 Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Wed, 3 Apr 2024 21:45:24 +0200 Subject: [PATCH 14/15] Use of an interface instead of a wild Closure --- src/inventory/ArmorInventory.php | 26 ++++++++--- src/inventory/SlotSafeInventory.php | 8 ++-- .../transaction/action/SlotChangeAction.php | 9 ++-- .../validator/CallbackSlotValidator.php | 44 +++++++++++++++++++ .../action/validator/SlotValidator.php | 38 ++++++++++++++++ 5 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 src/inventory/transaction/action/validator/CallbackSlotValidator.php create mode 100644 src/inventory/transaction/action/validator/SlotValidator.php diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index f9e935b0191..4bd6668b27e 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -25,6 +25,8 @@ 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; @@ -40,13 +42,7 @@ public function __construct( ){ parent::__construct(4); - $this->validators->add(static function (Inventory $inventory, Item $item, int $slot) : bool{ - return ($item instanceof Armor && $item->getArmorSlot() === $slot) || - ($slot === ArmorInventory::SLOT_HEAD && $item instanceof ItemBlock && ( - $item->getBlock()->getTypeId() === BlockTypeIds::CARVED_PUMPKIN || - $item->getBlock()->getTypeId() === BlockTypeIds::MOB_HEAD - )); - }); + $this->validators->add(new CallbackSlotValidator($this->validate(...))); } public function getHolder() : Living{ @@ -84,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) : null|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/SlotSafeInventory.php b/src/inventory/SlotSafeInventory.php index fbc04e0b038..b3c4b5a88d8 100644 --- a/src/inventory/SlotSafeInventory.php +++ b/src/inventory/SlotSafeInventory.php @@ -23,20 +23,20 @@ namespace pocketmine\inventory; -use pocketmine\item\Item; +use pocketmine\inventory\transaction\action\validator\SlotValidator; use pocketmine\utils\ObjectSet; /** * A "slot safe inventory" has validators which may restrict items * from being placed in particular slots of the inventory. * - * @phpstan-type SlotValidators ObjectSet<\Closure(Inventory $inventory, Item $item, int $slot): bool> + * @phpstan-type SlotValidators ObjectSet */ 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 true for the transaction to be allowed. - * If one of the validators returns false, the transaction will be cancelled. + * 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. * diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index 4e238b40e7e..3a482e5d8b4 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -77,12 +77,9 @@ public function validate(Player $source) : void{ } if($this->inventory instanceof SlotSafeInventory && !$this->targetItem->isNull()){ foreach($this->inventory->getSlotValidators() as $validator){ - try{ - if(!$validator($this->inventory, $this->targetItem, $this->inventorySlot)){ - throw new TransactionValidationException("Target item is not accepted by the inventory"); - } - }catch(TransactionValidationException $e){ - throw new TransactionValidationException("Target item is not accepted by the inventory: " . $e->getMessage()); + $ret = $validator->validate($this->inventory, $this->targetItem, $this->inventorySlot); + if($ret instanceof TransactionValidationException){ + 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 @@ + Date: Thu, 4 Apr 2024 14:39:35 +0200 Subject: [PATCH 15/15] Apply suggestions from code review Co-authored-by: ipad54 <63200545+ipad54@users.noreply.github.com> --- src/inventory/ArmorInventory.php | 2 +- src/inventory/transaction/action/SlotChangeAction.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index 4bd6668b27e..4d7ddd3e90c 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -81,7 +81,7 @@ public function setBoots(Item $boots) : void{ $this->setItem(self::SLOT_FEET, $boots); } - private function validate(Inventory $inventory, Item $item, int $slot) : null|TransactionValidationException{ + 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"); diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index 3a482e5d8b4..9cfc544d847 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -78,7 +78,7 @@ public function validate(Player $source) : void{ 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 instanceof TransactionValidationException){ + if($ret !== null){ throw new TransactionValidationException("Target item is not accepted by the inventory slot: " . $ret->getMessage(), 0, $ret); } }