PocketMine-MP 5.17.1 git-df4ada81e5d74a14046f27cf44a37dcee69d657e
BaseInventory.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
31use function array_slice;
32use function count;
33use function max;
34use function min;
35use function spl_object_id;
36
40abstract class BaseInventory implements Inventory{
41 protected int $maxStackSize = Inventory::MAX_STACK;
43 protected array $viewers = [];
48 protected ObjectSet $listeners;
49
50 public function __construct(){
51 $this->listeners = new ObjectSet();
52 }
53
54 public function getMaxStackSize() : int{
55 return $this->maxStackSize;
56 }
57
58 public function setMaxStackSize(int $size) : void{
59 $this->maxStackSize = $size;
60 }
61
62 abstract protected function internalSetItem(int $index, Item $item) : void;
63
64 public function setItem(int $index, Item $item) : void{
65 if($item->isNull()){
66 $item = VanillaItems::AIR();
67 }else{
68 $item = clone $item;
69 }
70
71 $oldItem = $this->getItem($index);
72
73 $this->internalSetItem($index, $item);
74 $this->onSlotChange($index, $oldItem);
75 }
76
81 abstract protected function internalSetContents(array $items) : void;
82
87 public function setContents(array $items) : void{
88 Utils::validateArrayValueType($items, function(Item $item) : void{});
89 if(count($items) > $this->getSize()){
90 $items = array_slice($items, 0, $this->getSize(), true);
91 }
92
93 $oldContents = $this->getContents(true);
94
95 $listeners = $this->listeners->toArray();
96 $this->listeners->clear();
97 $viewers = $this->viewers;
98 $this->viewers = [];
99
100 $this->internalSetContents($items);
101
102 $this->listeners->add(...$listeners); //don't directly write, in case listeners were added while operation was in progress
103 foreach($viewers as $id => $viewer){
104 $this->viewers[$id] = $viewer;
105 }
106
107 $this->onContentChange($oldContents);
108 }
109
114 protected function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{
115 $item = $this->getItem($slot);
116 return $item->equals($test, true, $checkTags) ? $item->getCount() : 0;
117 }
118
119 public function contains(Item $item) : bool{
120 $count = max(1, $item->getCount());
121 $checkTags = $item->hasNamedTag();
122 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
123 $slotCount = $this->getMatchingItemCount($i, $item, $checkTags);
124 if($slotCount > 0){
125 $count -= $slotCount;
126 if($count <= 0){
127 return true;
128 }
129 }
130 }
131
132 return false;
133 }
134
135 public function all(Item $item) : array{
136 $slots = [];
137 $checkTags = $item->hasNamedTag();
138 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
139 if($this->getMatchingItemCount($i, $item, $checkTags) > 0){
140 $slots[$i] = $this->getItem($i);
141 }
142 }
143
144 return $slots;
145 }
146
147 public function first(Item $item, bool $exact = false) : int{
148 $count = $exact ? $item->getCount() : max(1, $item->getCount());
149 $checkTags = $exact || $item->hasNamedTag();
150
151 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
152 $slotCount = $this->getMatchingItemCount($i, $item, $checkTags);
153 if($slotCount > 0 && ($slotCount === $count || (!$exact && $slotCount > $count))){
154 return $i;
155 }
156 }
157
158 return -1;
159 }
160
161 public function firstEmpty() : int{
162 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
163 if($this->isSlotEmpty($i)){
164 return $i;
165 }
166 }
167
168 return -1;
169 }
170
175 public function isSlotEmpty(int $index) : bool{
176 return $this->getItem($index)->isNull();
177 }
178
179 public function canAddItem(Item $item) : bool{
180 return $this->getAddableItemQuantity($item) === $item->getCount();
181 }
182
183 public function getAddableItemQuantity(Item $item) : int{
184 $count = $item->getCount();
185 $maxStackSize = min($this->getMaxStackSize(), $item->getMaxStackSize());
186
187 for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
188 if($this->isSlotEmpty($i)){
189 $count -= $maxStackSize;
190 }else{
191 $slotCount = $this->getMatchingItemCount($i, $item, true);
192 if($slotCount > 0 && ($diff = $maxStackSize - $slotCount) > 0){
193 $count -= $diff;
194 }
195 }
196
197 if($count <= 0){
198 return $item->getCount();
199 }
200 }
201
202 return $item->getCount() - $count;
203 }
204
205 public function addItem(Item ...$slots) : array{
208 $itemSlots = [];
209 foreach($slots as $slot){
210 if(!$slot->isNull()){
211 $itemSlots[] = clone $slot;
212 }
213 }
214
216 $returnSlots = [];
217
218 foreach($itemSlots as $item){
219 $leftover = $this->internalAddItem($item);
220 if(!$leftover->isNull()){
221 $returnSlots[] = $leftover;
222 }
223 }
224
225 return $returnSlots;
226 }
227
228 private function internalAddItem(Item $newItem) : Item{
229 $emptySlots = [];
230
231 $maxStackSize = min($this->getMaxStackSize(), $newItem->getMaxStackSize());
232
233 for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
234 if($this->isSlotEmpty($i)){
235 $emptySlots[] = $i;
236 continue;
237 }
238 $slotCount = $this->getMatchingItemCount($i, $newItem, true);
239 if($slotCount === 0){
240 continue;
241 }
242
243 if($slotCount < $maxStackSize){
244 $amount = min($maxStackSize - $slotCount, $newItem->getCount());
245 if($amount > 0){
246 $newItem->setCount($newItem->getCount() - $amount);
247 $slotItem = $this->getItem($i);
248 $slotItem->setCount($slotItem->getCount() + $amount);
249 $this->setItem($i, $slotItem);
250 if($newItem->getCount() <= 0){
251 break;
252 }
253 }
254 }
255 }
256
257 if(count($emptySlots) > 0){
258 foreach($emptySlots as $slotIndex){
259 $amount = min($maxStackSize, $newItem->getCount());
260 $newItem->setCount($newItem->getCount() - $amount);
261 $slotItem = clone $newItem;
262 $slotItem->setCount($amount);
263 $this->setItem($slotIndex, $slotItem);
264 if($newItem->getCount() <= 0){
265 break;
266 }
267 }
268 }
269
270 return $newItem;
271 }
272
273 public function remove(Item $item) : void{
274 $checkTags = $item->hasNamedTag();
275
276 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
277 if($this->getMatchingItemCount($i, $item, $checkTags) > 0){
278 $this->clear($i);
279 }
280 }
281 }
282
283 public function removeItem(Item ...$slots) : array{
286 $searchItems = [];
287 foreach($slots as $slot){
288 if(!$slot->isNull()){
289 $searchItems[] = clone $slot;
290 }
291 }
292
293 for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
294 if($this->isSlotEmpty($i)){
295 continue;
296 }
297
298 foreach($searchItems as $index => $search){
299 $slotCount = $this->getMatchingItemCount($i, $search, $search->hasNamedTag());
300 if($slotCount > 0){
301 $amount = min($slotCount, $search->getCount());
302 $search->setCount($search->getCount() - $amount);
303
304 $slotItem = $this->getItem($i);
305 $slotItem->setCount($slotItem->getCount() - $amount);
306 $this->setItem($i, $slotItem);
307 if($search->getCount() <= 0){
308 unset($searchItems[$index]);
309 }
310 }
311 }
312
313 if(count($searchItems) === 0){
314 break;
315 }
316 }
317
318 return $searchItems;
319 }
320
321 public function clear(int $index) : void{
322 $this->setItem($index, VanillaItems::AIR());
323 }
324
325 public function clearAll() : void{
326 $this->setContents([]);
327 }
328
329 public function swap(int $slot1, int $slot2) : void{
330 $i1 = $this->getItem($slot1);
331 $i2 = $this->getItem($slot2);
332 $this->setItem($slot1, $i2);
333 $this->setItem($slot2, $i1);
334 }
335
339 public function getViewers() : array{
340 return $this->viewers;
341 }
342
346 public function removeAllViewers() : void{
347 foreach($this->viewers as $hash => $viewer){
348 if($viewer->getCurrentWindow() === $this){ //this might not be the case for the player's own inventory
349 $viewer->removeCurrentWindow();
350 }
351 unset($this->viewers[$hash]);
352 }
353 }
354
355 public function onOpen(Player $who) : void{
356 $this->viewers[spl_object_id($who)] = $who;
357 }
358
359 public function onClose(Player $who) : void{
360 unset($this->viewers[spl_object_id($who)]);
361 }
362
363 protected function onSlotChange(int $index, Item $before) : void{
364 foreach($this->listeners as $listener){
365 $listener->onSlotChange($this, $index, $before);
366 }
367 foreach($this->viewers as $viewer){
368 $invManager = $viewer->getNetworkSession()->getInvManager();
369 if($invManager === null){
370 continue;
371 }
372 $invManager->onSlotChange($this, $index);
373 }
374 }
375
380 protected function onContentChange(array $itemsBefore) : void{
381 foreach($this->listeners as $listener){
382 $listener->onContentChange($this, $itemsBefore);
383 }
384
385 foreach($this->getViewers() as $viewer){
386 $invManager = $viewer->getNetworkSession()->getInvManager();
387 if($invManager === null){
388 continue;
389 }
390 $invManager->syncContents($this);
391 }
392 }
393
394 public function slotExists(int $slot) : bool{
395 return $slot >= 0 && $slot < $this->getSize();
396 }
397
398 public function getListeners() : ObjectSet{
399 return $this->listeners;
400 }
401}
first(Item $item, bool $exact=false)
getMatchingItemCount(int $slot, Item $test, bool $checkTags)
setItem(int $index, Item $item)
swap(int $slot1, int $slot2)