43 protected int $maxStackSize = Inventory::MAX_STACK;
48 protected array $viewers = [];
57 public function __construct(){
63 return $this->maxStackSize;
67 $this->maxStackSize = $size;
70 abstract protected function internalSetItem(
int $index,
Item $item) : void;
74 $item = VanillaItems::AIR();
79 $oldItem = $this->getItem($index);
81 $this->internalSetItem($index, $item);
82 $this->onSlotChange($index, $oldItem);
96 Utils::validateArrayValueType($items, function(
Item $item) : void{});
97 if(count($items) > $this->getSize()){
98 $items = array_slice($items, 0, $this->getSize(),
true);
101 $oldContents = $this->getContents(
true);
103 $listeners = $this->listeners->
toArray();
104 $this->listeners->clear();
105 $viewers = $this->viewers;
108 $this->internalSetContents($items);
110 $this->listeners->add(...$listeners);
111 foreach($viewers as $id => $viewer){
112 $this->viewers[$id] = $viewer;
115 $this->onContentChange($oldContents);
123 $item = $this->getItem($slot);
124 return $item->equals($test,
true, $checkTags) ? $item->getCount() : 0;
128 $count = max(1, $item->getCount());
130 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
131 $slotCount = $this->getMatchingItemCount($i, $item, $checkTags);
133 $count -= $slotCount;
146 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
147 if($this->getMatchingItemCount($i, $item, $checkTags) > 0){
148 $slots[$i] = $this->getItem($i);
155 public function first(
Item $item,
bool $exact =
false) : int{
156 $count = $exact ? $item->getCount() : max(1, $item->getCount());
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))){
170 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
171 if($this->isSlotEmpty($i)){
184 return $this->getItem($index)->isNull();
188 return $this->getAddableItemQuantity($item) === $item->getCount();
192 $count = $item->getCount();
193 $maxStackSize = min($this->getMaxStackSize(), $item->
getMaxStackSize());
195 for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
196 if($this->isSlotEmpty($i)){
197 $count -= $maxStackSize;
199 $slotCount = $this->getMatchingItemCount($i, $item,
true);
200 if($slotCount > 0 && ($diff = $maxStackSize - $slotCount) > 0){
206 return $item->getCount();
210 return $item->getCount() - $count;
217 foreach($slots as $slot){
218 if(!$slot->isNull()){
219 $itemSlots[] = clone $slot;
226 foreach($itemSlots as $item){
227 $leftover = $this->internalAddItem($item);
228 if(!$leftover->isNull()){
229 $returnSlots[] = $leftover;
236 private function internalAddItem(Item $newItem) : Item{
239 $maxStackSize = min($this->getMaxStackSize(), $newItem->getMaxStackSize());
241 for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
242 if($this->isSlotEmpty($i)){
246 $slotCount = $this->getMatchingItemCount($i, $newItem,
true);
247 if($slotCount === 0){
251 if($slotCount < $maxStackSize){
252 $amount = min($maxStackSize - $slotCount, $newItem->getCount());
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){
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){
281 public function remove(
Item $item) : void{
282 $checkTags = $item->hasNamedTag();
284 for($i = 0, $size = $this->getSize(); $i < $size; $i++){
285 if($this->getMatchingItemCount($i, $item, $checkTags) > 0){
293 foreach($slots as $slot){
294 if(!$slot->isNull()){
295 $searchItems[] = clone $slot;
299 for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
300 if($this->isSlotEmpty($i)){
304 foreach($searchItems as $index => $search){
305 $slotCount = $this->getMatchingItemCount($i, $search, $search->hasNamedTag());
307 $amount = min($slotCount, $search->getCount());
308 $search->setCount($search->getCount() - $amount);
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]);
319 if(count($searchItems) === 0){
327 public function clear(
int $index) : void{
332 $this->setContents([]);
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);
346 return $this->viewers;
353 foreach($this->viewers as $hash => $viewer){
354 if($viewer->getCurrentWindow() === $this){
355 $viewer->removeCurrentWindow();
357 unset($this->viewers[$hash]);
362 $this->viewers[spl_object_id($who)] = $who;
365 public function onClose(
Player $who) : void{
366 unset($this->viewers[spl_object_id($who)]);
369 protected function onSlotChange(
int $index, Item $before) : void{
370 foreach($this->listeners as $listener){
371 $listener->onSlotChange($this, $index, $before);
373 foreach($this->viewers as $viewer){
374 $invManager = $viewer->getNetworkSession()->getInvManager();
375 if($invManager ===
null){
378 $invManager->onSlotChange($this, $index);
387 foreach($this->listeners as $listener){
388 $listener->onContentChange($this, $itemsBefore);
391 foreach($this->getViewers() as $viewer){
392 $invManager = $viewer->getNetworkSession()->getInvManager();
393 if($invManager ===
null){
396 $invManager->syncContents($this);
401 return $slot >= 0 && $slot < $this->getSize();
405 return $this->listeners;
409 return $this->validators;