Removed pocketmine subdirectory, map PSR-4 style

This commit is contained in:
Dylan K. Taylor
2019-07-30 19:14:57 +01:00
parent 7a77d3dc30
commit 5499ac620c
1044 changed files with 3 additions and 3 deletions

View File

@ -0,0 +1,54 @@
<?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\player\Player;
use pocketmine\world\Position;
class AnvilInventory extends BlockInventory{
/** @var Position */
protected $holder;
public function __construct(Position $pos){
parent::__construct($pos->asPosition(), 2);
}
/**
* This override is here for documentation and code completion purposes only.
* @return Position
*/
public function getHolder(){
return $this->holder;
}
public function onClose(Player $who) : void{
parent::onClose($who);
foreach($this->getContents() as $item){
$who->dropItem($item);
}
$this->clearAll();
}
}

View File

@ -0,0 +1,78 @@
<?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\entity\Living;
use pocketmine\item\Item;
class ArmorInventory extends BaseInventory{
public const SLOT_HEAD = 0;
public const SLOT_CHEST = 1;
public const SLOT_LEGS = 2;
public const SLOT_FEET = 3;
/** @var Living */
protected $holder;
public function __construct(Living $holder){
$this->holder = $holder;
parent::__construct(4);
}
public function getHolder() : Living{
return $this->holder;
}
public function getHelmet() : Item{
return $this->getItem(self::SLOT_HEAD);
}
public function getChestplate() : Item{
return $this->getItem(self::SLOT_CHEST);
}
public function getLeggings() : Item{
return $this->getItem(self::SLOT_LEGS);
}
public function getBoots() : Item{
return $this->getItem(self::SLOT_FEET);
}
public function setHelmet(Item $helmet) : void{
$this->setItem(self::SLOT_HEAD, $helmet);
}
public function setChestplate(Item $chestplate) : void{
$this->setItem(self::SLOT_CHEST, $chestplate);
}
public function setLeggings(Item $leggings) : void{
$this->setItem(self::SLOT_LEGS, $leggings);
}
public function setBoots(Item $boots) : void{
$this->setItem(self::SLOT_FEET, $boots);
}
}

View File

@ -0,0 +1,391 @@
<?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;
use pocketmine\player\Player;
use function array_slice;
use function count;
use function max;
use function min;
use function spl_object_id;
abstract class BaseInventory implements Inventory{
/** @var int */
protected $maxStackSize = Inventory::MAX_STACK;
/** @var \SplFixedArray|Item[] */
protected $slots = [];
/** @var Player[] */
protected $viewers = [];
/** @var InventoryChangeListener[] */
protected $listeners = [];
/**
* @param int $size
*/
public function __construct(int $size){
$this->slots = new \SplFixedArray($size);
}
/**
* Returns the size of the inventory.
* @return int
*/
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();
}
/**
* @param bool $includeEmpty
*
* @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;
}
/**
* @param Item[] $items
* @param bool $send
*/
public function setContents(array $items, bool $send = true) : void{
if(count($items) > $this->getSize()){
$items = array_slice($items, 0, $this->getSize(), true);
}
$listeners = $this->listeners;
$this->listeners = [];
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
if(!isset($items[$i])){
$this->clear($i, false);
}else{
$this->setItem($i, $items[$i], false);
}
}
$this->addChangeListeners(...$listeners); //don't directly write, in case listeners were added while operation was in progress
foreach($this->listeners as $listener){
$listener->onContentChange($this);
}
if($send){
foreach($this->getViewers() as $viewer){
$viewer->getNetworkSession()->getInvManager()->syncContents($this);
}
}
}
public function setItem(int $index, Item $item, bool $send = true) : void{
if($item->isNull()){
$item = ItemFactory::air();
}else{
$item = clone $item;
}
$oldItem = $this->getItem($index);
$this->slots[$index] = $item->isNull() ? null : $item;
$this->onSlotChange($index, $oldItem, $send);
}
public function contains(Item $item) : bool{
$count = max(1, $item->getCount());
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasNamedTag();
foreach($this->getContents() as $i){
if($item->equals($i, $checkDamage, $checkTags)){
$count -= $i->getCount();
if($count <= 0){
return true;
}
}
}
return false;
}
public function all(Item $item) : array{
$slots = [];
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasNamedTag();
foreach($this->getContents() as $index => $i){
if($item->equals($i, $checkDamage, $checkTags)){
$slots[$index] = $i;
}
}
return $slots;
}
public function remove(Item $item) : void{
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasNamedTag();
foreach($this->getContents() as $index => $i){
if($item->equals($i, $checkDamage, $checkTags)){
$this->clear($index);
}
}
}
public function first(Item $item, bool $exact = false) : int{
$count = $exact ? $item->getCount() : max(1, $item->getCount());
$checkDamage = $exact || !$item->hasAnyDamageValue();
$checkTags = $exact || $item->hasNamedTag();
foreach($this->getContents() as $index => $i){
if($item->equals($i, $checkDamage, $checkTags) and ($i->getCount() === $count or (!$exact and $i->getCount() > $count))){
return $index;
}
}
return -1;
}
public function firstEmpty() : int{
foreach($this->slots as $i => $slot){
if($slot === null or $slot->isNull()){
return $i;
}
}
return -1;
}
public function isSlotEmpty(int $index) : bool{
return $this->slots[$index] === null or $this->slots[$index]->isNull();
}
public function canAddItem(Item $item) : bool{
$item = clone $item;
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$slot = $this->getItem($i);
if($item->equals($slot)){
if(($diff = $slot->getMaxStackSize() - $slot->getCount()) > 0){
$item->setCount($item->getCount() - $diff);
}
}elseif($slot->isNull()){
$item->setCount($item->getCount() - $this->getMaxStackSize());
}
if($item->getCount() <= 0){
return true;
}
}
return false;
}
public function addItem(Item ...$slots) : array{
/** @var Item[] $itemSlots */
/** @var Item[] $slots */
$itemSlots = [];
foreach($slots as $slot){
if(!$slot->isNull()){
$itemSlots[] = clone $slot;
}
}
$emptySlots = [];
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$item = $this->getItem($i);
if($item->isNull()){
$emptySlots[] = $i;
}
foreach($itemSlots as $index => $slot){
if($slot->equals($item) and $item->getCount() < $item->getMaxStackSize()){
$amount = min($item->getMaxStackSize() - $item->getCount(), $slot->getCount(), $this->getMaxStackSize());
if($amount > 0){
$slot->setCount($slot->getCount() - $amount);
$item->setCount($item->getCount() + $amount);
$this->setItem($i, $item);
if($slot->getCount() <= 0){
unset($itemSlots[$index]);
}
}
}
}
if(count($itemSlots) === 0){
break;
}
}
if(count($itemSlots) > 0 and count($emptySlots) > 0){
foreach($emptySlots as $slotIndex){
//This loop only gets the first item, then goes to the next empty slot
foreach($itemSlots as $index => $slot){
$amount = min($slot->getMaxStackSize(), $slot->getCount(), $this->getMaxStackSize());
$slot->setCount($slot->getCount() - $amount);
$item = clone $slot;
$item->setCount($amount);
$this->setItem($slotIndex, $item);
if($slot->getCount() <= 0){
unset($itemSlots[$index]);
}
break;
}
}
}
return $itemSlots;
}
public function removeItem(Item ...$slots) : array{
/** @var Item[] $itemSlots */
/** @var Item[] $slots */
$itemSlots = [];
foreach($slots as $slot){
if(!$slot->isNull()){
$itemSlots[] = clone $slot;
}
}
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$item = $this->getItem($i);
if($item->isNull()){
continue;
}
foreach($itemSlots as $index => $slot){
if($slot->equals($item, !$slot->hasAnyDamageValue(), $slot->hasNamedTag())){
$amount = min($item->getCount(), $slot->getCount());
$slot->setCount($slot->getCount() - $amount);
$item->setCount($item->getCount() - $amount);
$this->setItem($i, $item);
if($slot->getCount() <= 0){
unset($itemSlots[$index]);
}
}
}
if(count($itemSlots) === 0){
break;
}
}
return $itemSlots;
}
public function clear(int $index, bool $send = true) : void{
$this->setItem($index, ItemFactory::air(), $send);
}
public function clearAll(bool $send = true) : void{
$this->setContents([], $send);
}
public function swap(int $slot1, int $slot2) : void{
$i1 = $this->getItem($slot1);
$i2 = $this->getItem($slot2);
$this->setItem($slot1, $i2);
$this->setItem($slot2, $i1);
}
/**
* @return Player[]
*/
public function getViewers() : array{
return $this->viewers;
}
/**
* Removes the inventory window from all players currently viewing it.
*/
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
$viewer->removeCurrentWindow();
}
unset($this->viewers[$hash]);
}
}
public function setMaxStackSize(int $size) : void{
$this->maxStackSize = $size;
}
public function onOpen(Player $who) : void{
$this->viewers[spl_object_id($who)] = $who;
}
public function onClose(Player $who) : void{
unset($this->viewers[spl_object_id($who)]);
}
protected function onSlotChange(int $index, Item $before, bool $send) : void{
foreach($this->listeners as $listener){
$listener->onSlotChange($this, $index);
}
if($send){
foreach($this->viewers as $viewer){
$viewer->getNetworkSession()->getInvManager()->syncSlot($this, $index);
}
}
}
public function slotExists(int $slot) : bool{
return $slot >= 0 and $slot < $this->slots->getSize();
}
public function addChangeListeners(InventoryChangeListener ...$listeners) : void{
foreach($listeners as $listener){
$this->listeners[spl_object_id($listener)] = $listener;
}
}
public function removeChangeListeners(InventoryChangeListener ...$listeners) : void{
foreach($listeners as $listener){
unset($this->listeners[spl_object_id($listener)]);
}
}
public function getChangeListeners() : array{
return $this->listeners;
}
}

View File

@ -0,0 +1,43 @@
<?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\math\Vector3;
class BlockInventory extends BaseInventory{
/** @var Vector3 */
protected $holder;
public function __construct(Vector3 $holder, int $size){
$this->holder = $holder;
parent::__construct($size);
}
/**
* @return Vector3
*/
public function getHolder(){
return $this->holder;
}
}

View File

@ -0,0 +1,33 @@
<?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\math\Vector3;
class BrewingStandInventory extends BlockInventory{
public function __construct(Vector3 $holder, int $size = 5){
parent::__construct($holder, $size);
}
}

View File

@ -0,0 +1,65 @@
<?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\utils\Utils;
class CallbackInventoryChangeListener implements InventoryChangeListener{
/** @var \Closure|null */
private $onSlotChangeCallback;
/** @var \Closure|null */
private $onContentChangeCallback;
public function __construct(?\Closure $onSlotChange, ?\Closure $onContentChange){
if($onSlotChange !== null){
Utils::validateCallableSignature(function(Inventory $inventory, int $slot){}, $onSlotChange);
}
if($onContentChange !== null){
Utils::validateCallableSignature(function(Inventory $inventory){}, $onContentChange);
}
$this->onSlotChangeCallback = $onSlotChange;
$this->onContentChangeCallback = $onContentChange;
}
public static function onAnyChange(\Closure $onChange) : self{
return new self(
static function(Inventory $inventory, int $unused) use ($onChange) : void{
$onChange($inventory);
},
static function(Inventory $inventory) use ($onChange) : void{
$onChange($inventory);
}
);
}
public function onSlotChange(Inventory $inventory, int $slot) : void{
($this->onSlotChangeCallback)($inventory, $slot);
}
public function onContentChange(Inventory $inventory) : void{
($this->onContentChangeCallback)($inventory);
}
}

View File

@ -0,0 +1,87 @@
<?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\block\tile\Chest;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\player\Player;
use pocketmine\world\sound\ChestCloseSound;
use pocketmine\world\sound\ChestOpenSound;
use pocketmine\world\sound\Sound;
use function count;
class ChestInventory extends BlockInventory{
/** @var Chest */
protected $holder;
/**
* @param Chest $tile
*/
public function __construct(Chest $tile){
parent::__construct($tile, 27);
}
/**
* This override is here for documentation and code completion purposes only.
* @return Chest
*/
public function getHolder(){
return $this->holder;
}
protected function getOpenSound() : Sound{
return new ChestOpenSound();
}
protected function getCloseSound() : Sound{
return new ChestCloseSound();
}
public function onOpen(Player $who) : void{
parent::onOpen($who);
if(count($this->getViewers()) === 1 and $this->getHolder()->isValid()){
//TODO: this crap really shouldn't be managed by the inventory
$this->broadcastBlockEventPacket(true);
$this->getHolder()->getWorld()->addSound($this->getHolder()->add(0.5, 0.5, 0.5), $this->getOpenSound());
}
}
public function onClose(Player $who) : void{
if(count($this->getViewers()) === 1 and $this->getHolder()->isValid()){
//TODO: this crap really shouldn't be managed by the inventory
$this->broadcastBlockEventPacket(false);
$this->getHolder()->getWorld()->addSound($this->getHolder()->add(0.5, 0.5, 0.5), $this->getCloseSound());
}
parent::onClose($who);
}
protected function broadcastBlockEventPacket(bool $isOpen) : void{
$holder = $this->getHolder();
//event ID is always 1 for a chest
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(1, $isOpen ? 1 : 0, $holder->asVector3()));
}
}

View File

@ -0,0 +1,99 @@
<?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\Durable;
use pocketmine\item\Item;
use function file_get_contents;
use function json_decode;
use const DIRECTORY_SEPARATOR;
final class CreativeInventory{
/** @var Item[] */
public static $creative = [];
private function __construct(){
//NOOP
}
public static function init(){
self::clear();
$creativeItems = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla" . DIRECTORY_SEPARATOR . "creativeitems.json"), true);
foreach($creativeItems as $data){
$item = Item::jsonDeserialize($data);
if($item->getName() === "Unknown"){
continue;
}
self::add($item);
}
}
public static function clear(){
self::$creative = [];
}
/**
* @return Item[]
*/
public static function getAll() : array{
return self::$creative;
}
/**
* @param int $index
*
* @return Item|null
*/
public static function getItem(int $index) : ?Item{
return self::$creative[$index] ?? null;
}
public static function getItemIndex(Item $item) : int{
foreach(self::$creative as $i => $d){
if($item->equals($d, !($item instanceof Durable))){
return $i;
}
}
return -1;
}
public static function add(Item $item){
self::$creative[] = clone $item;
}
public static function remove(Item $item){
$index = self::getItemIndex($item);
if($index !== -1){
unset(self::$creative[$index]);
}
}
public static function contains(Item $item) : bool{
return self::getItemIndex($item) !== -1;
}
}

View File

@ -0,0 +1,108 @@
<?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\block\tile\Chest;
use pocketmine\item\Item;
use pocketmine\player\Player;
use function count;
class DoubleChestInventory extends ChestInventory implements InventoryHolder{
/** @var ChestInventory */
private $left;
/** @var ChestInventory */
private $right;
public function __construct(Chest $left, Chest $right){
$this->left = $left->getRealInventory();
$this->right = $right->getRealInventory();
BaseInventory::__construct($this->left->getSize() + $this->right->getSize());
}
public function getInventory(){
return $this;
}
/**
* @return Chest
*/
public function getHolder(){
return $this->left->getHolder();
}
public function getItem(int $index) : Item{
return $index < $this->left->getSize() ? $this->left->getItem($index) : $this->right->getItem($index - $this->left->getSize());
}
public function setItem(int $index, Item $item, bool $send = true) : void{
$old = $this->getItem($index);
$index < $this->left->getSize() ? $this->left->setItem($index, $item, $send) : $this->right->setItem($index - $this->left->getSize(), $item, $send);
$this->onSlotChange($index, $old, $send);
}
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;
}
public function onOpen(Player $who) : void{
parent::onOpen($who);
if(count($this->getViewers()) === 1 and $this->right->getHolder()->isValid()){
$this->right->broadcastBlockEventPacket(true);
}
}
public function onClose(Player $who) : void{
if(count($this->getViewers()) === 1 and $this->right->getHolder()->isValid()){
$this->right->broadcastBlockEventPacket(false);
}
parent::onClose($who);
}
/**
* @return ChestInventory
*/
public function getLeftSide() : ChestInventory{
return $this->left;
}
/**
* @return ChestInventory
*/
public function getRightSide() : ChestInventory{
return $this->right;
}
public function invalidate(){
$this->left = null;
$this->right = null;
}
}

View File

@ -0,0 +1,54 @@
<?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\player\Player;
use pocketmine\world\Position;
class EnchantInventory extends BlockInventory{
/** @var Position */
protected $holder;
public function __construct(Position $pos){
parent::__construct($pos->asPosition(), 2);
}
/**
* This override is here for documentation and code completion purposes only.
* @return Position
*/
public function getHolder(){
return $this->holder;
}
public function onClose(Player $who) : void{
parent::onClose($who);
foreach($this->getContents() as $item){
$who->dropItem($item);
}
$this->clearAll();
}
}

View File

@ -0,0 +1,66 @@
<?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\block\tile\EnderChest;
use pocketmine\world\Position;
use pocketmine\world\sound\EnderChestCloseSound;
use pocketmine\world\sound\EnderChestOpenSound;
use pocketmine\world\sound\Sound;
class EnderChestInventory extends ChestInventory{
/** @var Position */
protected $holder;
public function __construct(){
BlockInventory::__construct(new Position(), 27);
}
/**
* Set the holder's position to that of a tile
*
* @param EnderChest $enderChest
*/
public function setHolderPosition(EnderChest $enderChest) : void{
$this->holder->setComponents($enderChest->getFloorX(), $enderChest->getFloorY(), $enderChest->getFloorZ());
$this->holder->setWorld($enderChest->getWorld());
}
protected function getOpenSound() : Sound{
return new EnderChestOpenSound();
}
protected function getCloseSound() : Sound{
return new EnderChestCloseSound();
}
/**
* This override is here for documentation and code completion purposes only.
* @return Position
*/
public function getHolder(){
return $this->holder;
}
}

View File

@ -0,0 +1,86 @@
<?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\block\tile\Furnace;
use pocketmine\item\Item;
class FurnaceInventory extends BlockInventory{
/** @var Furnace */
protected $holder;
public function __construct(Furnace $tile){
parent::__construct($tile, 3);
}
/**
* This override is here for documentation and code completion purposes only.
* @return Furnace
*/
public function getHolder(){
return $this->holder;
}
/**
* @return Item
*/
public function getResult() : Item{
return $this->getItem(2);
}
/**
* @return Item
*/
public function getFuel() : Item{
return $this->getItem(1);
}
/**
* @return Item
*/
public function getSmelting() : Item{
return $this->getItem(0);
}
/**
* @param Item $item
*/
public function setResult(Item $item) : void{
$this->setItem(2, $item);
}
/**
* @param Item $item
*/
public function setFuel(Item $item) : void{
$this->setItem(1, $item);
}
/**
* @param Item $item
*/
public function setSmelting(Item $item) : void{
$this->setItem(0, $item);
}
}

View File

@ -0,0 +1,33 @@
<?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\math\Vector3;
class HopperInventory extends BlockInventory{
public function __construct(Vector3 $holder, int $size = 5){
parent::__construct($holder, $size);
}
}

229
src/inventory/Inventory.php Normal file
View File

@ -0,0 +1,229 @@
<?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);
/**
* Handles the creation of virtual inventories or mapped to an InventoryHolder
*/
namespace pocketmine\inventory;
use pocketmine\item\Item;
use pocketmine\player\Player;
interface Inventory{
public const MAX_STACK = 64;
/**
* @return int
*/
public function getSize() : int;
/**
* @return int
*/
public function getMaxStackSize() : int;
/**
* @param int $size
*/
public function setMaxStackSize(int $size) : void;
/**
* @param int $index
*
* @return Item
*/
public function getItem(int $index) : Item;
/**
* Puts an Item in a slot.
*
* @param int $index
* @param Item $item
* @param bool $send
*/
public function setItem(int $index, Item $item, bool $send = true) : void;
/**
* Stores the given Items in the inventory. This will try to fill
* existing stacks and empty slots as well as it can.
*
* Returns the Items that did not fit.
*
* @param Item ...$slots
*
* @return Item[]
*/
public function addItem(Item ...$slots) : array;
/**
* Checks if a given Item can be added to the inventory
*
* @param Item $item
*
* @return bool
*/
public function canAddItem(Item $item) : bool;
/**
* Removes the given Item from the inventory.
* It will return the Items that couldn't be removed.
*
* @param Item ...$slots
*
* @return Item[]
*/
public function removeItem(Item ...$slots) : array;
/**
* @param bool $includeEmpty
*
* @return Item[]
*/
public function getContents(bool $includeEmpty = false) : array;
/**
* @param Item[] $items
* @param bool $send
*/
public function setContents(array $items, bool $send = true) : void;
/**
* Checks if the inventory contains any Item with the same material data.
* It will check id, amount, and metadata (if not null)
*
* @param Item $item
*
* @return bool
*/
public function contains(Item $item) : bool;
/**
* Will return all the Items that has the same id and metadata (if not null).
* Won't check amount
*
* @param Item $item
*
* @return Item[]
*/
public function all(Item $item) : array;
/**
* Returns the first slot number containing an item with the same ID, damage (if not any-damage), NBT (if not empty)
* and count >= to the count of the specified item stack.
*
* If $exact is true, only items with equal ID, damage, NBT and count will match.
*
* @param Item $item
* @param bool $exact
*
* @return int
*/
public function first(Item $item, bool $exact = false) : int;
/**
* Returns the first empty slot, or -1 if not found
*
* @return int
*/
public function firstEmpty() : int;
/**
* Returns whether the given slot is empty.
*
* @param int $index
*
* @return bool
*/
public function isSlotEmpty(int $index) : bool;
/**
* Will remove all the Items that has the same id and metadata (if not null)
*
* @param Item $item
*/
public function remove(Item $item) : void;
/**
* Will clear a specific slot
*
* @param int $index
* @param bool $send
*/
public function clear(int $index, bool $send = true) : void;
/**
* Clears all the slots
*
* @param bool $send
*/
public function clearAll(bool $send = true) : void;
/**
* Swaps the specified slots.
*
* @param int $slot1
* @param int $slot2
*/
public function swap(int $slot1, int $slot2) : void;
/**
* Gets all the Players viewing the inventory
* Players will view their inventory at all times, even when not open.
*
* @return Player[]
*/
public function getViewers() : array;
/**
* Called when a player opens this inventory.
*
* @param Player $who
*/
public function onOpen(Player $who) : void;
public function onClose(Player $who) : void;
/**
* Returns whether the specified slot exists in the inventory.
*
* @param int $slot
*
* @return bool
*/
public function slotExists(int $slot) : bool;
/**
* @param InventoryChangeListener ...$listeners
*/
public function addChangeListeners(InventoryChangeListener ...$listeners) : void;
/**
* @param InventoryChangeListener ...$listeners
*/
public function removeChangeListeners(InventoryChangeListener ...$listeners) : void;
/**
* @return InventoryChangeListener[]
*/
public function getChangeListeners() : array;
}

View File

@ -0,0 +1,38 @@
<?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;
/**
* Classes implementing this interface can be injected into inventories to receive notifications when content changes
* occur.
* @see CallbackInventoryChangeListener for a closure-based listener
* @see Inventory::addChangeListeners()
* @see Inventory::removeChangeListeners()
*/
interface InventoryChangeListener{
public function onSlotChange(Inventory $inventory, int $slot) : void;
public function onContentChange(Inventory $inventory) : void;
}

View File

@ -0,0 +1,34 @@
<?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;
interface InventoryHolder{
/**
* Get the object related inventory
*
* @return Inventory
*/
public function getInventory();
}

View File

@ -0,0 +1,44 @@
<?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\player\Player;
class PlayerCursorInventory extends BaseInventory{
/** @var Player */
protected $holder;
public function __construct(Player $holder){
$this->holder = $holder;
parent::__construct(1);
}
/**
* This override is here for documentation and code completion purposes only.
* @return Player
*/
public function getHolder(){
return $this->holder;
}
}

View File

@ -0,0 +1,141 @@
<?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\entity\Human;
use pocketmine\item\Item;
use pocketmine\player\Player;
class PlayerInventory extends BaseInventory{
/** @var Human */
protected $holder;
/** @var int */
protected $itemInHandIndex = 0;
/**
* @param Human $player
*/
public function __construct(Human $player){
$this->holder = $player;
parent::__construct(36);
}
public function isHotbarSlot(int $slot) : bool{
return $slot >= 0 and $slot <= $this->getHotbarSize();
}
/**
* @param int $slot
*
* @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.
*
* @param int $hotbarSlot
*
* @return Item
*
* @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.
* @return int
*/
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
* @param bool $send Whether to send updates back to the inventory holder. This should usually be true for plugin calls.
* It should only be false to prevent feedback loops of equipment packets between client and server.
*
* @throws \InvalidArgumentException if the hotbar slot is out of range
*/
public function setHeldItemIndex(int $hotbarSlot, bool $send = true) : void{
$this->throwIfNotHotbarSlot($hotbarSlot);
$this->itemInHandIndex = $hotbarSlot;
if($this->holder instanceof Player and $send){
$this->holder->getNetworkSession()->getInvManager()->syncSelectedHotbarSlot();
}
foreach($this->holder->getViewers() as $viewer){
$viewer->getNetworkSession()->onMobEquipmentChange($this->holder);
}
}
/**
* Returns the currently-held item.
*
* @return Item
*/
public function getItemInHand() : Item{
return $this->getHotbarSlotItem($this->itemInHandIndex);
}
/**
* Sets the item in the currently-held slot to the specified item.
*
* @param Item $item
*/
public function setItemInHand(Item $item) : void{
$this->setItem($this->getHeldItemIndex(), $item);
foreach($this->holder->getViewers() as $viewer){
$viewer->getNetworkSession()->onMobEquipmentChange($this->holder);
}
}
/**
* Returns the number of slots in the hotbar.
* @return int
*/
public function getHotbarSize() : int{
return 9;
}
/**
* This override is here for documentation and code completion purposes only.
* @return Human|Player
*/
public function getHolder(){
return $this->holder;
}
}

View File

@ -0,0 +1,172 @@
<?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\transaction;
use pocketmine\crafting\CraftingRecipe;
use pocketmine\event\inventory\CraftItemEvent;
use pocketmine\item\Item;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use function array_pop;
use function count;
use function intdiv;
/**
* This transaction type is specialized for crafting validation. It shares most of the same semantics of the base
* inventory transaction type, but the requirement for validity is slightly different.
*
* It is expected that the actions in this transaction type will produce an **unbalanced result**, i.e. some inputs won't
* have corresponding outputs, and vice versa. The reason why is because the unmatched inputs are recipe inputs, and
* the unmatched outputs are recipe results.
*
* Therefore, the validity requirement is that the imbalance of the transaction should match the expected inputs and
* outputs of a registered crafting recipe.
*
* This transaction allows multiple repetitions of the same recipe to be crafted in a single batch. In the case of batch
* crafting, the number of unmatched inputs and outputs must be exactly divisible by the expected recipe ingredients and
* results, with no remainder. Any leftovers are expected to be emitted back to the crafting grid.
*/
class CraftingTransaction extends InventoryTransaction{
/** @var CraftingRecipe|null */
protected $recipe;
/** @var int|null */
protected $repetitions;
/** @var Item[] */
protected $inputs = [];
/** @var Item[] */
protected $outputs = [];
/**
* @param Item[] $txItems
* @param Item[] $recipeItems
* @param bool $wildcards
* @param int $iterations
*
* @return int
* @throws TransactionValidationException
*/
protected function matchRecipeItems(array $txItems, array $recipeItems, bool $wildcards, int $iterations = 0) : int{
if(empty($recipeItems)){
throw new TransactionValidationException("No recipe items given");
}
if(empty($txItems)){
throw new TransactionValidationException("No transaction items given");
}
while(!empty($recipeItems)){
/** @var Item $recipeItem */
$recipeItem = array_pop($recipeItems);
$needCount = $recipeItem->getCount();
foreach($recipeItems as $i => $otherRecipeItem){
if($otherRecipeItem->equals($recipeItem)){ //make sure they have the same wildcards set
$needCount += $otherRecipeItem->getCount();
unset($recipeItems[$i]);
}
}
$haveCount = 0;
foreach($txItems as $j => $txItem){
if($txItem->equals($recipeItem, !$wildcards or !$recipeItem->hasAnyDamageValue(), !$wildcards or $recipeItem->hasNamedTag())){
$haveCount += $txItem->getCount();
unset($txItems[$j]);
}
}
if($haveCount % $needCount !== 0){
//wrong count for this output, should divide exactly
throw new TransactionValidationException("Expected an exact multiple of required $recipeItem (given: $haveCount, needed: $needCount)");
}
$multiplier = intdiv($haveCount, $needCount);
if($multiplier < 1){
throw new TransactionValidationException("Expected more than zero items matching $recipeItem (given: $haveCount, needed: $needCount)");
}
if($iterations === 0){
$iterations = $multiplier;
}elseif($multiplier !== $iterations){
//wrong count for this output, should match previous outputs
throw new TransactionValidationException("Expected $recipeItem x$iterations, but found x$multiplier");
}
}
if($iterations < 1){
throw new TransactionValidationException("Tried to craft zero times");
}
if(!empty($txItems)){
//all items should be destroyed in this process
throw new TransactionValidationException("Expected 0 ingredients left over, have " . count($txItems));
}
return $iterations;
}
public function validate() : void{
$this->squashDuplicateSlotChanges();
if(count($this->actions) < 1){
throw new TransactionValidationException("Transaction must have at least one action to be executable");
}
$this->matchItems($this->outputs, $this->inputs);
$failed = 0;
foreach($this->source->getServer()->getCraftingManager()->matchRecipeByOutputs($this->outputs) as $recipe){
try{
//compute number of times recipe was crafted
$this->repetitions = $this->matchRecipeItems($this->outputs, $recipe->getResultsFor($this->source->getCraftingGrid()), false);
//assert that $repetitions x recipe ingredients should be consumed
$this->matchRecipeItems($this->inputs, $recipe->getIngredientList(), true, $this->repetitions);
//Success!
$this->recipe = $recipe;
break;
}catch(TransactionValidationException $e){
//failed
++$failed;
}
}
if($this->recipe === null){
throw new TransactionValidationException("Unable to match a recipe to transaction (tried to match against $failed recipes)");
}
}
protected function callExecuteEvent() : bool{
$ev = new CraftItemEvent($this, $this->recipe, $this->repetitions, $this->inputs, $this->outputs);
$ev->call();
return !$ev->isCancelled();
}
protected function sendInventories() : void{
parent::sendInventories();
/*
* TODO: HACK!
* we can't resend the contents of the crafting window, so we force the client to close it instead.
* So people don't whine about messy desync issues when someone cancels CraftItemEvent, or when a crafting
* transaction goes wrong.
*/
$this->source->sendDataPacket(ContainerClosePacket::create(ContainerIds::NONE));
}
}

View File

@ -0,0 +1,338 @@
<?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\transaction;
use pocketmine\event\inventory\InventoryTransactionEvent;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item;
use pocketmine\player\Player;
use function array_keys;
use function assert;
use function count;
use function get_class;
use function min;
use function shuffle;
use function spl_object_hash;
use function spl_object_id;
/**
* This is the basic type for an inventory transaction. This is used for moving items between inventories, dropping
* items and more. It allows transactions with multiple inputs and outputs.
*
* Validation **does not** depend on ordering. This means that the actions can appear in any order and still be valid.
* The only validity requirement for this transaction type is that the balance of items must add up to zero. This means:
* - No new outputs without matching input amounts
* - No inputs without matching output amounts
* - No userdata changes (item state, NBT, etc)
*
* A transaction is composed of "actions", which are pairs of inputs and outputs which target a specific itemstack in
* a specific location. There are multiple types of inventory actions which might be involved in a transaction.
*
* @see InventoryAction
*/
class InventoryTransaction{
protected $hasExecuted = false;
/** @var Player */
protected $source;
/** @var Inventory[] */
protected $inventories = [];
/** @var InventoryAction[] */
protected $actions = [];
/**
* @param Player $source
* @param InventoryAction[] $actions
*/
public function __construct(Player $source, array $actions = []){
$this->source = $source;
foreach($actions as $action){
$this->addAction($action);
}
}
/**
* @return Player
*/
public function getSource() : Player{
return $this->source;
}
/**
* @return Inventory[]
*/
public function getInventories() : array{
return $this->inventories;
}
/**
* Returns an **unordered** set of actions involved in this transaction.
*
* WARNING: This system is **explicitly designed NOT to care about ordering**. Any order seen in this set has NO
* significance and should not be relied on.
*
* @return InventoryAction[]
*/
public function getActions() : array{
return $this->actions;
}
/**
* @param InventoryAction $action
*/
public function addAction(InventoryAction $action) : void{
if(!isset($this->actions[$hash = spl_object_id($action)])){
$this->actions[$hash] = $action;
$action->onAddToTransaction($this);
}else{
throw new \InvalidArgumentException("Tried to add the same action to a transaction twice");
}
}
/**
* Shuffles actions in the transaction to prevent external things relying on any implicit ordering.
*/
private function shuffleActions() : void{
$keys = array_keys($this->actions);
shuffle($keys);
$actions = [];
foreach($keys as $key){
$actions[$key] = $this->actions[$key];
}
$this->actions = $actions;
}
/**
* @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions
* involving inventories.
*
* @param Inventory $inventory
*/
public function addInventory(Inventory $inventory) : void{
if(!isset($this->inventories[$hash = spl_object_id($inventory)])){
$this->inventories[$hash] = $inventory;
}
}
/**
* @param Item[] $needItems
* @param Item[] $haveItems
*
* @throws TransactionValidationException
*/
protected function matchItems(array &$needItems, array &$haveItems) : void{
foreach($this->actions as $key => $action){
if(!$action->getTargetItem()->isNull()){
$needItems[] = $action->getTargetItem();
}
if(!$action->isValid($this->source)){
throw new TransactionValidationException("Action " . get_class($action) . " is not valid in the current transaction");
}
if(!$action->getSourceItem()->isNull()){
$haveItems[] = $action->getSourceItem();
}
}
foreach($needItems as $i => $needItem){
foreach($haveItems as $j => $haveItem){
if($needItem->equals($haveItem)){
$amount = min($needItem->getCount(), $haveItem->getCount());
$needItem->setCount($needItem->getCount() - $amount);
$haveItem->setCount($haveItem->getCount() - $amount);
if($haveItem->getCount() === 0){
unset($haveItems[$j]);
}
if($needItem->getCount() === 0){
unset($needItems[$i]);
break;
}
}
}
}
}
/**
* Iterates over SlotChangeActions in this transaction and compacts any which refer to the same slot in the same
* inventory so they can be correctly handled.
*
* Under normal circumstances, the same slot would never be changed more than once in a single transaction. However,
* due to the way things like the crafting grid are "implemented" in MCPE 1.2 (a.k.a. hacked-in), we may get
* multiple slot changes referring to the same slot in a single transaction. These multiples are not even guaranteed
* to be in the correct order (slot splitting in the crafting grid for example, causes the actions to be sent in the
* wrong order), so this method also tries to chain them into order.
*/
protected function squashDuplicateSlotChanges() : void{
/** @var SlotChangeAction[][] $slotChanges */
$slotChanges = [];
/** @var Inventory[] $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();
$slots[$h] = $action->getSlot();
}
}
foreach($slotChanges as $hash => $list){
if(count($list) === 1){ //No need to compact slot changes if there is only one on this slot
continue;
}
$inventory = $inventories[$hash];
$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));
}
$sourceItem = $inventory->getItem($slot);
$targetItem = $this->findResultItem($sourceItem, $list);
if($targetItem === null){
throw new TransactionValidationException("Failed to compact " . count($list) . " duplicate actions");
}
foreach($list as $action){
unset($this->actions[spl_object_id($action)]);
}
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));
}
}
}
/**
* @param Item $needOrigin
* @param SlotChangeAction[] $possibleActions
*
* @return null|Item
*/
protected function findResultItem(Item $needOrigin, array $possibleActions) : ?Item{
assert(!empty($possibleActions));
foreach($possibleActions as $i => $action){
if($action->getSourceItem()->equalsExact($needOrigin)){
$newList = $possibleActions;
unset($newList[$i]);
if(empty($newList)){
return $action->getTargetItem();
}
$result = $this->findResultItem($action->getTargetItem(), $newList);
if($result !== null){
return $result;
}
}
}
return null;
}
/**
* Verifies that the transaction can execute.
*
* @throws TransactionValidationException
*/
public function validate() : void{
$this->squashDuplicateSlotChanges();
$haveItems = [];
$needItems = [];
$this->matchItems($needItems, $haveItems);
if(count($this->actions) === 0){
throw new TransactionValidationException("Inventory transaction must have at least one action to be executable");
}
if(count($haveItems) > 0){
throw new TransactionValidationException("Transaction does not balance (tried to destroy some items)");
}
if(count($needItems) > 0){
throw new TransactionValidationException("Transaction does not balance (tried to create some items)");
}
}
protected function sendInventories() : void{
foreach($this->inventories as $inventory){
$this->source->getNetworkSession()->getInvManager()->syncContents($inventory);
}
}
protected function callExecuteEvent() : bool{
$ev = new InventoryTransactionEvent($this);
$ev->call();
return !$ev->isCancelled();
}
/**
* Executes the group of actions, returning whether the transaction executed successfully or not.
* @return bool
*
* @throws TransactionValidationException
*/
public function execute() : bool{
if($this->hasExecuted()){
$this->sendInventories();
return false;
}
$this->shuffleActions();
$this->validate();
if(!$this->callExecuteEvent()){
$this->sendInventories();
return false;
}
foreach($this->actions as $action){
if(!$action->onPreExecute($this->source)){
$this->sendInventories();
return false;
}
}
foreach($this->actions as $action){
$action->execute($this->source);
}
$this->hasExecuted = true;
return true;
}
/**
* @return bool
*/
public function hasExecuted() : bool{
return $this->hasExecuted;
}
}

View File

@ -0,0 +1,28 @@
<?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\transaction;
class TransactionValidationException extends \RuntimeException{
}

View File

@ -0,0 +1,48 @@
<?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\transaction\action;
use pocketmine\inventory\CreativeInventory;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\player\Player;
/**
* This action is used by creative players to balance transactions involving the creative inventory menu.
* The source item is the item being created ("taken" from the creative menu).
*/
class CreateItemAction extends InventoryAction{
public function __construct(Item $sourceItem){
parent::__construct($sourceItem, ItemFactory::air());
}
public function isValid(Player $source) : bool{
return !$source->hasFiniteResources() and CreativeInventory::contains($this->sourceItem);
}
public function execute(Player $source) : void{
//NOOP
}
}

View File

@ -0,0 +1,47 @@
<?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\transaction\action;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\player\Player;
/**
* This action type shows up when a creative player puts an item into the creative inventory menu to destroy it.
* The output is the item destroyed. You can think of this action type like setting an item into /dev/null
*/
class DestroyItemAction extends InventoryAction{
public function __construct(Item $targetItem){
parent::__construct(ItemFactory::air(), $targetItem);
}
public function isValid(Player $source) : bool{
return !$source->hasFiniteResources();
}
public function execute(Player $source) : void{
//NOOP
}
}

View File

@ -0,0 +1,62 @@
<?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\transaction\action;
use pocketmine\event\player\PlayerDropItemEvent;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\player\Player;
/**
* Represents an action involving dropping an item into the world.
*/
class DropItemAction extends InventoryAction{
public function __construct(Item $targetItem){
parent::__construct(ItemFactory::air(), $targetItem);
}
public function isValid(Player $source) : bool{
return !$this->targetItem->isNull();
}
public function onPreExecute(Player $source) : bool{
$ev = new PlayerDropItemEvent($source, $this->targetItem);
$ev->call();
if($ev->isCancelled()){
return false;
}
return true;
}
/**
* Drops the target item in front of the player.
*
* @param Player $source
*/
public function execute(Player $source) : void{
$source->dropItem($this->targetItem);
}
}

View File

@ -0,0 +1,97 @@
<?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\transaction\action;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\item\Item;
use pocketmine\player\Player;
/**
* Represents an action involving a change that applies in some way to an inventory or other item-source.
*/
abstract class InventoryAction{
/** @var Item */
protected $sourceItem;
/** @var Item */
protected $targetItem;
public function __construct(Item $sourceItem, Item $targetItem){
$this->sourceItem = $sourceItem;
$this->targetItem = $targetItem;
}
/**
* Returns the item that was present before the action took place.
* @return Item
*/
public function getSourceItem() : Item{
return clone $this->sourceItem;
}
/**
* Returns the item that the action attempted to replace the source item with.
* @return Item
*/
public function getTargetItem() : Item{
return clone $this->targetItem;
}
/**
* Returns whether this action is currently valid. This should perform any necessary sanity checks.
*
* @param Player $source
*
* @return bool
*/
abstract public function isValid(Player $source) : bool;
/**
* Called when the action is added to the specified InventoryTransaction.
*
* @param InventoryTransaction $transaction
*/
public function onAddToTransaction(InventoryTransaction $transaction) : void{
}
/**
* Called by inventory transactions before any actions are processed. If this returns false, the transaction will
* be cancelled.
*
* @param Player $source
*
* @return bool
*/
public function onPreExecute(Player $source) : bool{
return true;
}
/**
* Performs actions needed to complete the inventory-action server-side. This will only be called if the transaction
* which it is part of is considered valid.
*
* @param Player $source
*/
abstract public function execute(Player $source) : void;
}

View File

@ -0,0 +1,107 @@
<?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\transaction\action;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\item\Item;
use pocketmine\player\Player;
/**
* Represents an action causing a change in an inventory slot.
*/
class SlotChangeAction extends InventoryAction{
/** @var Inventory */
protected $inventory;
/** @var int */
private $inventorySlot;
/**
* @param Inventory $inventory
* @param int $inventorySlot
* @param Item $sourceItem
* @param Item $targetItem
*/
public function __construct(Inventory $inventory, int $inventorySlot, Item $sourceItem, Item $targetItem){
parent::__construct($sourceItem, $targetItem);
$this->inventory = $inventory;
$this->inventorySlot = $inventorySlot;
}
/**
* Returns the inventory involved in this action.
*
* @return Inventory
*/
public function getInventory() : Inventory{
return $this->inventory;
}
/**
* Returns the slot in the inventory which this action modified.
* @return int
*/
public function getSlot() : int{
return $this->inventorySlot;
}
/**
* Checks if the item in the inventory at the specified slot is the same as this action's source item.
*
* @param Player $source
*
* @return bool
*/
public function isValid(Player $source) : bool{
return (
$this->inventory->slotExists($this->inventorySlot) and
$this->inventory->getItem($this->inventorySlot)->equalsExact($this->sourceItem)
);
}
/**
* Adds this action's target inventory to the transaction's inventory list.
*
* @param InventoryTransaction $transaction
*
*/
public function onAddToTransaction(InventoryTransaction $transaction) : void{
$transaction->addInventory($this->inventory);
}
/**
* Sets the item into the target inventory.
*
* @param Player $source
*/
public function execute(Player $source) : void{
$this->inventory->setItem($this->inventorySlot, $this->targetItem, false);
foreach($this->inventory->getViewers() as $viewer){
if($viewer !== $source){
$viewer->getNetworkSession()->getInvManager()->syncSlot($this->inventory, $this->inventorySlot);
}
}
}
}