mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-11 08:19:45 +00:00
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.
This commit is contained in:
parent
4906f5bec2
commit
699a85a5d6
@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
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;
|
||||
|
@ -1,99 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block\inventory;
|
||||
|
||||
use pocketmine\inventory\BaseInventory;
|
||||
use pocketmine\inventory\Inventory;
|
||||
use pocketmine\item\Item;
|
||||
|
||||
final class DoubleChestInventory extends BaseInventory{
|
||||
public function __construct(
|
||||
private Inventory $left,
|
||||
private Inventory $right
|
||||
){
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function getSize() : int{
|
||||
return $this->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;
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
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 @@ class Chest extends Spawnable implements ContainerTile, Nameable{
|
||||
$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 @@ class Chest extends Spawnable implements ContainerTile, Nameable{
|
||||
$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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
135
src/inventory/CombinedInventory.php
Normal file
135
src/inventory/CombinedInventory.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use function array_fill_keys;
|
||||
use function array_keys;
|
||||
use function spl_object_id;
|
||||
|
||||
/**
|
||||
* 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{
|
||||
|
||||
private readonly int $size;
|
||||
|
||||
/**
|
||||
* @var Inventory[]
|
||||
* @phpstan-var array<int, Inventory>
|
||||
*/
|
||||
private array $backingInventories = [];
|
||||
/**
|
||||
* @var Inventory[]
|
||||
* @phpstan-var array<int, Inventory>
|
||||
*/
|
||||
private array $slotToInventoryMap = [];
|
||||
/**
|
||||
* @var int[]
|
||||
* @phpstan-var array<int, int>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
180
tests/phpunit/inventory/CombinedInventoryTest.php
Normal file
180
tests/phpunit/inventory/CombinedInventoryTest.php
Normal file
@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemTypeIds;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use function array_filter;
|
||||
|
||||
final class CombinedInventoryTest extends \PHPUnit\Framework\TestCase{
|
||||
|
||||
/**
|
||||
* @return Inventory[]
|
||||
* @phpstan-return list<Inventory>
|
||||
*/
|
||||
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<int, Item> $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<Item>
|
||||
*/
|
||||
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<int, Inventory> $backing
|
||||
* @phpstan-param array<int, Item> $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<int, array{array<int, Item>}, 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<int, Item> $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));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user