Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 63 additions & 19 deletions src/inventory/transaction/InventoryTransaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@

use pocketmine\event\inventory\InventoryTransactionEvent;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\PlayerCursorInventory;
use pocketmine\inventory\PlayerInventory;
use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item;
use pocketmine\item\ItemLockMode;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use function array_keys;
use function array_push;
use function array_values;
use function assert;
use function count;
Expand Down Expand Up @@ -135,30 +139,14 @@ private function shuffleActions() : void{
/**
* @param Item[] $needItems
* @param Item[] $haveItems
* @phpstan-param list<Item> $needItems
* @phpstan-param list<Item> $haveItems
* @phpstan-param-out list<Item> $needItems
* @phpstan-param-out list<Item> $haveItems
*
* @throws TransactionValidationException
*/
protected function matchItems(array &$needItems, array &$haveItems) : void{
$needItems = [];
$haveItems = [];
foreach($this->actions as $key => $action){
if(!$action->getTargetItem()->isNull()){
$needItems[] = $action->getTargetItem();
}

try{
$action->validate($this->source);
}catch(TransactionValidationException $e){
throw new TransactionValidationException(get_class($action) . "#" . spl_object_id($action) . ": " . $e->getMessage(), 0, $e);
}

if(!$action->getSourceItem()->isNull()){
$haveItems[] = $action->getSourceItem();
}
}

public function computeDiff(array &$needItems, array &$haveItems) : void{
foreach($needItems as $i => $needItem){
foreach($haveItems as $j => $haveItem){
if($needItem->canStackWith($haveItem)){
Expand All @@ -179,6 +167,62 @@ protected function matchItems(array &$needItems, array &$haveItems) : void{
$haveItems = array_values($haveItems);
}

/**
* @param Item[] $needItems
* @param Item[] $haveItems
* @phpstan-param-out list<Item> $needItems
* @phpstan-param-out list<Item> $haveItems
*
* @throws TransactionValidationException
*/
protected function matchItems(array &$needItems, array &$haveItems) : void{
$needItems = [];
$haveItems = [];

$boundLockedNeedItems = [];
$boundLockedHaveItems = [];
foreach($this->actions as $key => $action){
$checkBoundLockedItem = $action instanceof SlotChangeAction &&
(($inventory = $action->getInventory()) instanceof PlayerInventory || $inventory instanceof PlayerCursorInventory);
$targetItem = $action->getTargetItem();
if(!$targetItem->isNull()){
if($checkBoundLockedItem && $targetItem->getLockMode() === ItemLockMode::INVENTORY){
$boundLockedNeedItems[] = $targetItem;
}else{
$needItems[] = $targetItem;
}
}

try{
$action->validate($this->source);
}catch(TransactionValidationException $e){
throw new TransactionValidationException(get_class($action) . "#" . spl_object_id($action) . ": " . $e->getMessage(), 0, $e);
}

$sourceItem = $action->getSourceItem();
if(!$sourceItem->isNull()){
if($checkBoundLockedItem && $sourceItem->getLockMode() === ItemLockMode::INVENTORY){
$boundLockedHaveItems[] = $sourceItem;
}else{
$haveItems[] = $sourceItem;
}
}
}

$this->computeDiff($needItems, $haveItems);

if(count($boundLockedNeedItems) > 0 || count($boundLockedHaveItems) > 0){
//first, try to balance bound locked items with items already bound to the player's inventory
$this->computeDiff($boundLockedNeedItems, $boundLockedHaveItems);
//then, check if there are unbound locked items the player moved into its inventory
//this allows moving locked items from e.g. a chest -> player's inventory, but not the other way round
$this->computeDiff($boundLockedNeedItems, $haveItems);

array_push($needItems, ...$boundLockedNeedItems);
array_push($haveItems, ...$boundLockedHaveItems);
}
}

/**
* 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.
Expand Down
3 changes: 3 additions & 0 deletions src/inventory/transaction/action/DropItemAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public function validate(Player $source) : void{
if($this->targetItem->getCount() > $this->targetItem->getMaxStackSize()){
throw new TransactionValidationException("Target item exceeds item type max stack size");
}
if($this->targetItem->getLockMode() !== null){
throw new TransactionValidationException("Target item is locked in inventory or slot");
}
}

public function onPreExecute(Player $source) : bool{
Expand Down
7 changes: 7 additions & 0 deletions src/inventory/transaction/action/SlotChangeAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use pocketmine\inventory\SlotValidatedInventory;
use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\item\Item;
use pocketmine\item\ItemLockMode;
use pocketmine\player\Player;

/**
Expand Down Expand Up @@ -74,6 +75,12 @@ 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->targetItem->getLockMode() === ItemLockMode::SLOT){
throw new TransactionValidationException("Target item is locked in slot");
}
if($this->sourceItem->getLockMode() === ItemLockMode::SLOT){
throw new TransactionValidationException("Source item is locked in slot");
}
if($this->inventory instanceof SlotValidatedInventory && !$this->targetItem->isNull()){
foreach($this->inventory->getSlotValidators() as $validator){
$ret = $validator->validate($this->inventory, $this->targetItem, $this->inventorySlot);
Expand Down
29 changes: 29 additions & 0 deletions src/item/Item.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,16 @@ class Item implements \JsonSerializable{

public const TAG_DISPLAY = "display";
public const TAG_BLOCK_ENTITY_TAG = "BlockEntityTag";
public const TAG_ITEM_LOCK = "minecraft:item_lock";

public const TAG_DISPLAY_NAME = "Name";
public const TAG_DISPLAY_LORE = "Lore";

public const TAG_KEEP_ON_DEATH = "minecraft:keep_on_death";

public const VALUE_ITEM_LOCK_IN_SLOT = 1;
public const VALUE_ITEM_LOCK_IN_INVENTORY = 2;

private const TAG_CAN_PLACE_ON = "CanPlaceOn"; //TAG_List<TAG_String>
private const TAG_CAN_DESTROY = "CanDestroy"; //TAG_List<TAG_String>

Expand Down Expand Up @@ -100,6 +104,8 @@ class Item implements \JsonSerializable{

protected bool $keepOnDeath = false;

protected ?ItemLockMode $lockMode = null;

/**
* Constructs a new Item type. This constructor should ONLY be used when constructing a new item TYPE to register
* into the index.
Expand Down Expand Up @@ -227,6 +233,15 @@ public function setCanDestroy(array $canDestroy) : void{
}
}

public function getLockMode() : ?ItemLockMode{
return $this->lockMode;
}

public function setLockMode(?ItemLockMode $lockMode) : self{
$this->lockMode = $lockMode;
return $this;
}

/**
* Returns whether players will retain this item on death. If a non-player dies it will be excluded from the drops.
*/
Expand Down Expand Up @@ -338,6 +353,12 @@ protected function deserializeCompoundTag(CompoundTag $tag) : void{
}

$this->keepOnDeath = $tag->getByte(self::TAG_KEEP_ON_DEATH, 0) !== 0;

$this->lockMode = match($tag->getByte(self::TAG_ITEM_LOCK, 0)){
self::VALUE_ITEM_LOCK_IN_SLOT => ItemLockMode::SLOT,
self::VALUE_ITEM_LOCK_IN_INVENTORY => ItemLockMode::INVENTORY,
default => null
};
}

protected function serializeCompoundTag(CompoundTag $tag) : void{
Expand Down Expand Up @@ -406,6 +427,14 @@ protected function serializeCompoundTag(CompoundTag $tag) : void{
}else{
$tag->removeTag(self::TAG_KEEP_ON_DEATH);
}
if($this->lockMode !== null){
$tag->setByte(self::TAG_ITEM_LOCK, match($this->lockMode){
ItemLockMode::SLOT => self::VALUE_ITEM_LOCK_IN_SLOT,
ItemLockMode::INVENTORY => self::VALUE_ITEM_LOCK_IN_INVENTORY,
});
}else{
$tag->removeTag("minecraft:item_lock");
}
}

public function getCount() : int{
Expand Down
29 changes: 29 additions & 0 deletions src/item/ItemLockMode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?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\item;

enum ItemLockMode{
case SLOT;
case INVENTORY;
}