PocketMine-MP 5.33.2 git-1133d49c924b4358c79d44eeb97dcbf56cb4d1eb
Loading...
Searching...
No Matches
CombinedInventoryProxy.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\inventory;
25
29use function array_fill_keys;
30use function array_keys;
31use function array_map;
32use function array_merge;
33use function count;
34use function spl_object_id;
35
41
42 private readonly int $size;
43
48 private array $backingInventories = [];
53 private array $slotToInventoryMap = [];
58 private array $inventoryToOffsetMap = [];
59
60 private InventoryListener $backingInventoryListener;
61 private bool $modifyingBackingInventory = false;
62
66 public function __construct(
67 array $backingInventories
68 ){
69 parent::__construct();
70 foreach($backingInventories as $backingInventory){
71 $this->backingInventories[spl_object_id($backingInventory)] = $backingInventory;
72 }
73 $combinedSize = 0;
74 foreach($this->backingInventories as $inventory){
75 $size = $inventory->getSize();
76
77 $this->inventoryToOffsetMap[spl_object_id($inventory)] = $combinedSize;
78 for($slot = 0; $slot < $size; $slot++){
79 $this->slotToInventoryMap[$combinedSize + $slot] = $inventory;
80 }
81
82 $combinedSize += $size;
83 }
84 $this->size = $combinedSize;
85
86 $weakThis = \WeakReference::create($this);
87 $this->backingInventoryListener = new CallbackInventoryListener(
88 static fn(Inventory $inventory, int $slot, Item $oldItem) => $weakThis->get()?->onBackingSlotChange($inventory, $slot, $oldItem),
89 static fn(Inventory $inventory, array $oldContents) => $weakThis->get()?->onBackingContentChange($inventory, $oldContents)
90 );
91 foreach($this->backingInventories as $inventory){
92 $inventory->getListeners()->add($this->backingInventoryListener);
93 }
94 }
95
96 private function onBackingSlotChange(Inventory $inventory, int $slot, Item $oldItem) : void{
97 if($this->modifyingBackingInventory){
98 return;
99 }
100
101 $offset = $this->inventoryToOffsetMap[spl_object_id($inventory)];
102 $this->onSlotChange($offset + $slot, $oldItem);
103 }
104
108 private function onBackingContentChange(Inventory $inventory, array $oldContents) : void{
109 if($this->modifyingBackingInventory){
110 return;
111 }
112
113 if(count($this->backingInventories) === 1){
114 $this->onContentChange($oldContents);
115 }else{
116 $offset = $this->inventoryToOffsetMap[spl_object_id($inventory)];
117 for($slot = 0, $limit = $inventory->getSize(); $slot < $limit; $slot++){
118 $this->onSlotChange($offset + $slot, $oldContents[$slot] ?? VanillaItems::AIR());
119 }
120 }
121 }
122
123 public function __destruct(){
124 foreach($this->backingInventories as $inventory){
125 $inventory->getListeners()->remove($this->backingInventoryListener);
126 }
127 }
128
132 private function getInventory(int $slot) : array{
133 $inventory = $this->slotToInventoryMap[$slot] ?? throw new \InvalidArgumentException("Invalid combined inventory slot $slot");
134 $actualSlot = $slot - $this->inventoryToOffsetMap[spl_object_id($inventory)];
135 return [$inventory, $actualSlot];
136 }
137
138 protected function internalSetItem(int $index, Item $item) : void{
139 [$inventory, $actualSlot] = $this->getInventory($index);
140
141 //Make sure our backing listener doesn't dispatch double updates to our own listeners
142 $this->modifyingBackingInventory = true;
143 try{
144 $inventory->setItem($actualSlot, $item);
145 }finally{
146 $this->modifyingBackingInventory = false;
147 }
148 }
149
150 protected function internalSetContents(array $items) : void{
151 $contentsByInventory = array_fill_keys(array_keys($this->backingInventories), []);
152 foreach($items as $i => $item){
153 [$inventory, $actualSlot] = $this->getInventory($i);
154 $contentsByInventory[spl_object_id($inventory)][$actualSlot] = $item;
155 }
156 foreach($contentsByInventory as $splObjectId => $backingInventoryContents){
157 $backingInventory = $this->backingInventories[$splObjectId];
158
159 //Make sure our backing listener doesn't dispatch double updates to our own listeners
160 $this->modifyingBackingInventory = true;
161 try{
162 $backingInventory->setContents($backingInventoryContents);
163 }finally{
164 $this->modifyingBackingInventory = false;
165 }
166 }
167 }
168
169 public function getSize() : int{
170 return $this->size;
171 }
172
173 public function getItem(int $index) : Item{
174 [$inventory, $actualSlot] = $this->getInventory($index);
175 return $inventory->getItem($actualSlot);
176 }
177
178 public function getContents(bool $includeEmpty = false) : array{
179 $result = [];
180 foreach($this->backingInventories as $inventory){
181 $offset = $this->inventoryToOffsetMap[spl_object_id($inventory)];
182 foreach($inventory->getContents($includeEmpty) as $i => $item){
183 $result[$offset + $i] = $item;
184 }
185 }
186
187 return $result;
188 }
189
190 public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{
191 [$inventory, $actualSlot] = $this->getInventory($slot);
192 return $inventory->getMatchingItemCount($actualSlot, $test, $checkTags);
193 }
194
195 public function isSlotEmpty(int $index) : bool{
196 [$inventory, $actualSlot] = $this->getInventory($index);
197 return $inventory->isSlotEmpty($actualSlot);
198 }
199
200 public function onOpen(InventoryWindow $window) : void{
201 foreach($this->backingInventories as $inventory){
202 $inventory->onOpen($window);
203 }
204 }
205
206 public function onClose(InventoryWindow $window) : void{
207 foreach($this->backingInventories as $inventory){
208 $inventory->onClose($window);
209 }
210 }
211
212 public function removeAllWindows() : void{
213 foreach($this->backingInventories as $inventory){
214 $inventory->removeAllWindows();
215 }
216 }
217
218 public function getViewers() : array{
219 return array_merge(...array_map(fn(Inventory $inventory) => $inventory->getViewers(), $this->backingInventories));
220 }
221}
getMatchingItemCount(int $slot, Item $test, bool $checkTags)