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