22declare(strict_types=1);
24namespace pocketmine\inventory\transaction;
32use
function array_keys;
33use
function array_values;
36use
function get_class;
39use
function spl_object_hash;
40use
function spl_object_id;
58 protected bool $hasExecuted =
false;
61 protected array $inventories = [];
64 protected array $actions = [];
73 foreach($actions as $action){
74 $this->addAction($action);
78 public function getSource() :
Player{
86 return $this->inventories;
98 return $this->actions;
102 if(!isset($this->actions[$hash = spl_object_id($action)])){
103 $this->actions[$hash] = $action;
106 throw new \InvalidArgumentException(
"Tried to add the same action to a transaction twice");
113 private function shuffleActions() : void{
114 $keys = array_keys($this->actions);
117 foreach($keys as $key){
118 $actions[$key] = $this->actions[$key];
120 $this->actions = $actions;
127 public function addInventory(Inventory $inventory) : void{
128 if(!isset($this->inventories[$hash = spl_object_id($inventory)])){
129 $this->inventories[$hash] = $inventory;
141 protected function matchItems(array &$needItems, array &$haveItems) : void{
144 foreach($this->actions as $key => $action){
145 if(!$action->getTargetItem()->isNull()){
146 $needItems[] = $action->getTargetItem();
150 $action->validate($this->source);
155 if(!$action->getSourceItem()->isNull()){
156 $haveItems[] = $action->getSourceItem();
160 foreach($needItems as $i => $needItem){
161 foreach($haveItems as $j => $haveItem){
162 if($needItem->canStackWith($haveItem)){
163 $amount = min($needItem->getCount(), $haveItem->getCount());
164 $needItem->setCount($needItem->getCount() - $amount);
165 $haveItem->setCount($haveItem->getCount() - $amount);
166 if($haveItem->getCount() === 0){
167 unset($haveItems[$j]);
169 if($needItem->getCount() === 0){
170 unset($needItems[$i]);
176 $needItems = array_values($needItems);
177 $haveItems = array_values($haveItems);
198 foreach($this->actions as $key => $action){
200 $slotChanges[$h = (spl_object_hash($action->getInventory()) .
"@" . $action->getSlot())][] = $action;
201 $inventories[$h] = $action->getInventory();
202 $slots[$h] = $action->getSlot();
206 foreach($slotChanges as $hash => $list){
207 if(count($list) === 1){
211 $inventory = $inventories[$hash];
212 $slot = $slots[$hash];
213 if(!$inventory->slotExists($slot)){
214 throw new TransactionValidationException(
"Slot $slot does not exist in inventory " . get_class($inventory));
216 $sourceItem = $inventory->getItem($slot);
218 $targetItem = $this->findResultItem($sourceItem, $list);
219 if($targetItem ===
null){
220 throw new TransactionValidationException(
"Failed to compact " . count($list) .
" duplicate actions");
223 foreach($list as $action){
224 unset($this->actions[spl_object_id($action)]);
227 if(!$targetItem->equalsExact($sourceItem)){
229 $this->addAction(
new SlotChangeAction($inventory, $slot, $sourceItem, $targetItem));
238 assert(count($possibleActions) > 0);
241 $newList = $possibleActions;
242 foreach($possibleActions as $i => $action){
243 if($action->getSourceItem()->equalsExact($needOrigin)){
244 if($candidate !==
null){
255 $candidate = $action;
259 if($candidate ===
null){
264 if(count($newList) === 0){
265 return $candidate->getTargetItem();
267 return $this->findResultItem($candidate->getTargetItem(), $newList);
276 $this->squashDuplicateSlotChanges();
280 $this->matchItems($needItems, $haveItems);
281 if(count($this->actions) === 0){
285 if(count($haveItems) > 0){
288 if(count($needItems) > 0){
289 throw new TransactionValidationException(
"Transaction does not balance (tried to create some items)");
293 protected function callExecuteEvent() : bool{
294 $ev = new InventoryTransactionEvent($this);
296 return !$ev->isCancelled();
305 if($this->hasExecuted()){
309 $this->shuffleActions();
313 if(!$this->callExecuteEvent()){
317 foreach($this->actions as $action){
318 if(!$action->onPreExecute($this->source)){
319 throw new TransactionCancelledException(
"One of the actions in this transaction was cancelled");
323 foreach($this->actions as $action){
324 $action->execute($this->source);
327 $this->hasExecuted =
true;
330 public function hasExecuted() : bool{
331 return $this->hasExecuted;
findResultItem(Item $needOrigin, array $possibleActions)
__construct(protected Player $source, array $actions=[])
matchItems(array &$needItems, array &$haveItems)
squashDuplicateSlotChanges()
onAddToTransaction(InventoryTransaction $transaction)