43 protected int $maxStackSize = Inventory::MAX_STACK;
45 protected array $viewers = [];
54 public function __construct(){
60 return $this->maxStackSize;
64 $this->maxStackSize = $size;
67 abstract protected function internalSetItem(
int $index,
Item $item) : void;
71 $item = VanillaItems::AIR();
76 $oldItem = $this->getItem($index);
78 $this->internalSetItem($index, $item);
79 $this->onSlotChange($index, $oldItem);
93 Utils::validateArrayValueType($items, function(
Item $item) : void{});
94 if(count($items) > $this->getSize()){
95 $items = array_slice($items, 0, $this->getSize(),
true);
98 $oldContents = $this->getContents(
true);
100 $listeners = $this->listeners->
toArray();
101 $this->listeners->clear();
102 $viewers = $this->viewers;
105 $this->internalSetContents($items);
107 $this->listeners->add(...$listeners);
108 foreach($viewers as $id => $viewer){
109 $this->viewers[$id] = $viewer;
112 $this->onContentChange($oldContents);
120 $item = $this->getItem($slot);
121 return $item->equals($test,
true, $checkTags) ? $item->getCount() : 0;
125 $count = max(1, $item->getCount());
127 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
128 $slotCount = $this->getMatchingItemCount($i, $item, $checkTags);
130 $count -= $slotCount;
143 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
144 if($this->getMatchingItemCount($i, $item, $checkTags) > 0){
145 $slots[$i] = $this->getItem($i);
152 public function first(
Item $item,
bool $exact =
false) : int{
153 $count = $exact ? $item->getCount() : max(1, $item->getCount());
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))){
167 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
168 if($this->isSlotEmpty($i)){
181 return $this->getItem($index)->isNull();
185 return $this->getAddableItemQuantity($item) === $item->getCount();
189 $count = $item->getCount();
190 $maxStackSize = min($this->getMaxStackSize(), $item->
getMaxStackSize());
192 for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
193 if($this->isSlotEmpty($i)){
194 $count -= $maxStackSize;
196 $slotCount = $this->getMatchingItemCount($i, $item,
true);
197 if($slotCount > 0 && ($diff = $maxStackSize - $slotCount) > 0){
203 return $item->getCount();
207 return $item->getCount() - $count;
214 foreach($slots as $slot){
215 if(!$slot->isNull()){
216 $itemSlots[] = clone $slot;
223 foreach($itemSlots as $item){
224 $leftover = $this->internalAddItem($item);
225 if(!$leftover->isNull()){
226 $returnSlots[] = $leftover;
233 private function internalAddItem(Item $newItem) : Item{
236 $maxStackSize = min($this->getMaxStackSize(), $newItem->getMaxStackSize());
238 for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
239 if($this->isSlotEmpty($i)){
243 $slotCount = $this->getMatchingItemCount($i, $newItem,
true);
244 if($slotCount === 0){
248 if($slotCount < $maxStackSize){
249 $amount = min($maxStackSize - $slotCount, $newItem->getCount());
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){
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){
278 public function remove(
Item $item) : void{
279 $checkTags = $item->hasNamedTag();
281 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
282 if($this->getMatchingItemCount($i, $item, $checkTags) > 0){
292 foreach($slots as $slot){
293 if(!$slot->isNull()){
294 $searchItems[] = clone $slot;
298 for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
299 if($this->isSlotEmpty($i)){
303 foreach($searchItems as $index => $search){
304 $slotCount = $this->getMatchingItemCount($i, $search, $search->hasNamedTag());
306 $amount = min($slotCount, $search->getCount());
307 $search->setCount($search->getCount() - $amount);
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]);
318 if(count($searchItems) === 0){
326 public function clear(
int $index) : void{
331 $this->setContents([]);
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);
345 return $this->viewers;
352 foreach($this->viewers as $hash => $viewer){
353 if($viewer->getCurrentWindow() === $this){
354 $viewer->removeCurrentWindow();
356 unset($this->viewers[$hash]);
361 $this->viewers[spl_object_id($who)] = $who;
364 public function onClose(
Player $who) : void{
365 unset($this->viewers[spl_object_id($who)]);
368 protected function onSlotChange(
int $index, Item $before) : void{
369 foreach($this->listeners as $listener){
370 $listener->onSlotChange($this, $index, $before);
372 foreach($this->viewers as $viewer){
373 $invManager = $viewer->getNetworkSession()->getInvManager();
374 if($invManager ===
null){
377 $invManager->onSlotChange($this, $index);
386 foreach($this->listeners as $listener){
387 $listener->onContentChange($this, $itemsBefore);
390 foreach($this->getViewers() as $viewer){
391 $invManager = $viewer->getNetworkSession()->getInvManager();
392 if($invManager ===
null){
395 $invManager->syncContents($this);
400 return $slot >= 0 && $slot < $this->getSize();
404 return $this->listeners;
408 return $this->validators;