Cleaned up implementations for EnderChestInventory/DoubleChestInventory

previously, these were forced to extend BaseInventory because of the amount of crap in Inventory's interface.
This meant that these inventories had their own slots storage, which would be _mostly_ unused because these inventories aren't real inventories, but rather just delegates.
This lead to a variety of bugs in the past, such as certain API methods on BaseInventory not working correctly for DoubleChestInventory in particular.

Now, BaseInventory just implements the functional part of the inventory implementation, leaving the storage system up to the implementation.
A SimpleInventory class is provided with a simple SplFixedArray storage backing, which is used by most inventories.
EnderChestInventory and DoubleChestInventory now extend BaseInventory directly, and implement custom methods for dealing with their delegates.
This commit is contained in:
Dylan K. Taylor
2021-05-09 20:50:40 +01:00
parent 1533789f35
commit f909557529
22 changed files with 237 additions and 99 deletions

View File

@ -26,7 +26,7 @@ namespace pocketmine\inventory;
use pocketmine\entity\Living;
use pocketmine\item\Item;
class ArmorInventory extends BaseInventory{
class ArmorInventory extends SimpleInventory{
public const SLOT_HEAD = 0;
public const SLOT_CHEST = 1;
public const SLOT_LEGS = 2;

View File

@ -27,21 +27,18 @@ use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\player\Player;
use pocketmine\utils\ObjectSet;
use function array_map;
use function array_slice;
use function count;
use function spl_object_id;
/**
* This class provides everything needed to implement an inventory, minus the underlying storage system.
*/
abstract class BaseInventory implements Inventory{
use InventoryHelpersTrait;
/** @var int */
protected $maxStackSize = Inventory::MAX_STACK;
/**
* @var \SplFixedArray|(Item|null)[]
* @phpstan-var \SplFixedArray<Item|null>
*/
protected $slots;
/** @var Player[] */
protected $viewers = [];
/**
@ -50,42 +47,18 @@ abstract class BaseInventory implements Inventory{
*/
protected $listeners;
public function __construct(int $size){
$this->slots = new \SplFixedArray($size);
public function __construct(){
$this->listeners = new ObjectSet();
}
/**
* Returns the size of the inventory.
*/
public function getSize() : int{
return $this->slots->getSize();
}
public function getMaxStackSize() : int{
return $this->maxStackSize;
}
public function getItem(int $index) : Item{
return $this->slots[$index] !== null ? clone $this->slots[$index] : ItemFactory::air();
}
/**
* @return Item[]
* @param Item[] $items
*/
public function getContents(bool $includeEmpty = false) : array{
$contents = [];
foreach($this->slots as $i => $slot){
if($slot !== null){
$contents[$i] = clone $slot;
}elseif($includeEmpty){
$contents[$i] = ItemFactory::air();
}
}
return $contents;
}
abstract protected function internalSetContents(array $items) : void;
/**
* @param Item[] $items
@ -95,22 +68,14 @@ abstract class BaseInventory implements Inventory{
$items = array_slice($items, 0, $this->getSize(), true);
}
$oldContents = array_map(function(?Item $item) : Item{
return $item ?? ItemFactory::air();
}, $this->slots->toArray());
$oldContents = $this->getContents(true);
$listeners = $this->listeners->toArray();
$this->listeners->clear();
$viewers = $this->viewers;
$this->viewers = [];
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
if(!isset($items[$i])){
$this->clear($i);
}else{
$this->setItem($i, $items[$i]);
}
}
$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){
@ -120,6 +85,8 @@ abstract class BaseInventory implements Inventory{
$this->onContentChange($oldContents);
}
abstract protected function internalSetItem(int $index, Item $item) : void;
public function setItem(int $index, Item $item) : void{
if($item->isNull()){
$item = ItemFactory::air();
@ -129,7 +96,7 @@ abstract class BaseInventory implements Inventory{
$oldItem = $this->getItem($index);
$this->slots[$index] = $item->isNull() ? null : $item;
$this->internalSetItem($index, $item);
$this->onSlotChange($index, $oldItem);
}
@ -188,7 +155,7 @@ abstract class BaseInventory implements Inventory{
}
public function slotExists(int $slot) : bool{
return $slot >= 0 and $slot < $this->slots->getSize();
return $slot >= 0 and $slot < $this->getSize();
}
public function getListeners() : ObjectSet{

View File

@ -25,7 +25,7 @@ namespace pocketmine\inventory;
use pocketmine\player\Player;
class PlayerCursorInventory extends BaseInventory{
class PlayerCursorInventory extends SimpleInventory{
/** @var Player */
protected $holder;

View File

@ -25,7 +25,7 @@ namespace pocketmine\inventory;
use pocketmine\entity\Human;
final class PlayerEnderInventory extends BaseInventory{
final class PlayerEnderInventory extends SimpleInventory{
private Human $holder;

View File

@ -28,7 +28,7 @@ use pocketmine\item\Item;
use pocketmine\player\Player;
use pocketmine\utils\ObjectSet;
class PlayerInventory extends BaseInventory{
class PlayerInventory extends SimpleInventory{
/** @var Human */
protected $holder;

View File

@ -25,7 +25,7 @@ namespace pocketmine\inventory;
use pocketmine\entity\Human;
final class PlayerOffHandInventory extends BaseInventory{
final class PlayerOffHandInventory extends SimpleInventory{
/** @var Human */
private $holder;

View File

@ -0,0 +1,85 @@
<?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\ItemFactory;
/**
* This class provides a complete implementation of a regular inventory.
*/
class SimpleInventory extends BaseInventory{
/**
* @var \SplFixedArray|(Item|null)[]
* @phpstan-var \SplFixedArray<Item|null>
*/
protected \SplFixedArray $slots;
public function __construct(int $size){
$this->slots = new \SplFixedArray($size);
parent::__construct();
}
/**
* Returns the size of the inventory.
*/
public function getSize() : int{
return $this->slots->getSize();
}
public function getItem(int $index) : Item{
return $this->slots[$index] !== null ? clone $this->slots[$index] : ItemFactory::air();
}
/**
* @return Item[]
*/
public function getContents(bool $includeEmpty = false) : array{
$contents = [];
foreach($this->slots as $i => $slot){
if($slot !== null){
$contents[$i] = clone $slot;
}elseif($includeEmpty){
$contents[$i] = ItemFactory::air();
}
}
return $contents;
}
protected function internalSetContents(array $items) : void{
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
if(!isset($items[$i])){
$this->clear($i);
}else{
$this->setItem($i, $items[$i]);
}
}
}
protected function internalSetItem(int $index, Item $item) : void{
$this->slots[$index] = $item->isNull() ? null : $item;
}
}