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