Рекурсия в Container#transferStackInSlot/Container#slotClick

Версия Minecraft
1.7.10
API
Forge
192
2
9
Всем привет. Не могу найти описание и инфу по методам, приведенным ниже. Если кто шарит, не могли бы вы подсказать, за что они отвечают, какие и чьи аргументы принимают, что за слоты и что конкретно должно возвращаться в обоих методах? А так же может ли (правильно ли) быть предмет в 1.7.10 объявлен, как null вместо EMPTY или AIR, как в других версиях?

Я пытаюсь пофиксить краш, который вызывается рекурсией, но почему эта рекурсия появляется, понять не могу, так как не понимаю, как должна работать логика этих методов. Вот кусочек лога:
Log:
java.lang.StackOverflowError: Updating screen events
    at pack.container.MYContainer.func_75144_a(MYContainer.java:46)
    at pack.container.MYContainer.func_75133_b(MYContainer.java:325)
    at pack.container.MYContainer.func_75144_a(MYContainer.java:152)
    at pack.container.MYContainer.func_75133_b(MYContainer.java:325)
    at pack.container.MYContainer.func_75144_a(MYContainer.java:152)
Container:
package pack.container;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.init.Items;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;

public abstract class MYContainer extends Container {

    public InventoryPlayer playerInv;
    int xs, ys;
    private int field_94535_f = -1;
    private int field_94536_g;
    private final Set field_94537_h = new HashSet();

    public MYContainer(InventoryPlayer playerInv) {
        this.playerInv = playerInv;
        this.xs = 8;
        this.ys = 84;
    }

    public void loadPlayerInvSlots() {
        int i;
        for (i = 0; i < 3; i++) {
            for (int j = 0; j < 9; j++)
                addSlotToContainer(new Slot(this.playerInv, j + i * 9 + 9, this.xs + j * 18, this.ys + i * 18));
        }
        for (i = 0; i < 9; i++)
            addSlotToContainer(new Slot(this.playerInv, i, this.xs + i * 18, this.ys + 58));
    }
 
    public ItemStack transferStackInSlot(EntityPlayer p, int arg) {
        Slot slot = (Slot)this.inventorySlots.get(arg);
        return slot != null ? slot.getStack() : null;
    }
 
    public ItemStack slotClick(int arg1, int arg2, int arg3, EntityPlayer p) {
        ItemStack itemstack = null;
        InventoryPlayer inventoryplayer = p.inventory;
        int i1;
        ItemStack itemstack3;

        if (arg3 == 5) {
            int l = this.field_94536_g;
            this.field_94536_g = func_94532_c(arg2);

            if ((l != 1 || this.field_94536_g != 2) && l != this.field_94536_g) {
                this.func_94533_d();
            } else if (inventoryplayer.getItemStack() == null) {
                this.func_94533_d();
            } else if (this.field_94536_g == 0) {
                this.field_94535_f = func_94529_b(arg2);

                if (func_94528_d(this.field_94535_f)) {
                    this.field_94536_g = 1;
                    this.field_94537_h.clear();
                } else {
                    this.func_94533_d();
                }
            } else if (this.field_94536_g == 1) {
                Slot slot = (Slot)this.inventorySlots.get(arg1);

                if (slot != null && func_94527_a(slot, inventoryplayer.getItemStack(), true) && slot.isItemValid(inventoryplayer.getItemStack()) && inventoryplayer.getItemStack().stackSize > this.field_94537_h.size() && this.canDragIntoSlot(slot)) {
                    this.field_94537_h.add(slot);
                }
            } else if (this.field_94536_g == 2) {
                if (!this.field_94537_h.isEmpty()) {
                    itemstack3 = inventoryplayer.getItemStack().copy();
                    i1 = inventoryplayer.getItemStack().stackSize;
                    Iterator iterator = this.field_94537_h.iterator();

                    while (iterator.hasNext()) {
                        Slot slot1 = (Slot)iterator.next();

                        if (slot1 != null && func_94527_a(slot1, inventoryplayer.getItemStack(), true) && slot1.isItemValid(inventoryplayer.getItemStack()) && inventoryplayer.getItemStack().stackSize >= this.field_94537_h.size() && this.canDragIntoSlot(slot1)) {
                            ItemStack itemstack1 = itemstack3.copy();
                            int j1 = slot1.getHasStack() ? slot1.getStack().stackSize : 0;
                            func_94525_a(this.field_94537_h, this.field_94535_f, itemstack1, j1);

                            if (itemstack1.stackSize > itemstack1.getMaxStackSize()) {
                                itemstack1.stackSize = itemstack1.getMaxStackSize();
                            }

                            if (itemstack1.stackSize > slot1.getSlotStackLimit()) {
                                itemstack1.stackSize = slot1.getSlotStackLimit();
                            }

                            i1 -= itemstack1.stackSize - j1;
                            slot1.putStack(itemstack1);
                        }
                    }

                    itemstack3.stackSize = i1;

                    if (itemstack3.stackSize <= 0) {
                        itemstack3 = null;
                    }

                    inventoryplayer.setItemStack(itemstack3);
                }

                this.func_94533_d();
            } else {
                this.func_94533_d();
            }
        } else if (this.field_94536_g != 0) {
            this.func_94533_d();
        } else {
            Slot slot2;
            int l1;
            ItemStack itemstack5;

            if ((arg3 == 0 || arg3 == 1) && (arg2 == 0 || arg2 == 1)) {
                if (arg1 == -999) {
                    if (inventoryplayer.getItemStack() != null && arg1 == -999) {
                        if (arg2 == 0) {
                            p.dropPlayerItemWithRandomChoice(inventoryplayer.getItemStack(), true);
                            inventoryplayer.setItemStack((ItemStack)null);
                        }

                        if (arg2 == 1) {
                            p.dropPlayerItemWithRandomChoice(inventoryplayer.getItemStack().splitStack(1), true);

                            if (inventoryplayer.getItemStack().stackSize == 0) {
                                inventoryplayer.setItemStack((ItemStack)null);
                            }
                        }
                    }
                } else if (arg3 == 1) {
                    if (arg1 < 0) {
                        return null;
                    }

                    slot2 = (Slot)this.inventorySlots.get(arg1);

                    if (slot2 != null && slot2.canTakeStack(p)) {
                        itemstack3 = this.transferStackInSlot(p, arg1);

                        if (itemstack3 != null) {
                            Item item = itemstack3.getItem();
                            itemstack = itemstack3.copy();

                            if (slot2.getStack() != null && slot2.getStack().getItem() == item) {
                                this.retrySlotClick(arg1, arg2, true, p);
                            }
                        }
                    }
                } else {
                    if (arg1 < 0) {
                        return null;
                    }

                    slot2 = (Slot)this.inventorySlots.get(arg1);

                    if (slot2 != null) {
                        itemstack3 = slot2.getStack();
                        ItemStack itemstack4 = inventoryplayer.getItemStack();

                        if (itemstack3 != null) {
                            itemstack = itemstack3.copy();
                        }

                        if (itemstack3 == null) {
                            if (itemstack4 != null && slot2.isItemValid(itemstack4)) {
                                l1 = arg2 == 0 ? itemstack4.stackSize : 1;

                                if (l1 > slot2.getSlotStackLimit()) {
                                    l1 = slot2.getSlotStackLimit();
                                }

                                if (itemstack4.stackSize >= l1) {
                                    slot2.putStack(itemstack4.splitStack(l1));
                                }

                                if (itemstack4.stackSize == 0) {
                                    inventoryplayer.setItemStack((ItemStack)null);
                                }
                            }
                        } else if (slot2.canTakeStack(p)) {
                            if (itemstack4 == null) {
                                l1 = arg2 == 0 ? itemstack3.stackSize : (itemstack3.stackSize + 1) / 2;
                                itemstack5 = slot2.decrStackSize(l1);
                                inventoryplayer.setItemStack(itemstack5);

                                if (itemstack3.stackSize == 0) {
                                    slot2.putStack((ItemStack)null);
                                }

                                slot2.onPickupFromSlot(p, inventoryplayer.getItemStack());
                            } else if (slot2.isItemValid(itemstack4)) {
                                if (itemstack3.getItem() == itemstack4.getItem() && itemstack3.getItemDamage() == itemstack4.getItemDamage() && ItemStack.areItemStackTagsEqual(itemstack3, itemstack4)) {
                                    l1 = arg2 == 0 ? itemstack4.stackSize : 1;

                                    if (l1 > slot2.getSlotStackLimit() - itemstack3.stackSize) {
                                        l1 = slot2.getSlotStackLimit() - itemstack3.stackSize;
                                    }

                                    if (l1 > itemstack4.getMaxStackSize() - itemstack3.stackSize) {
                                        l1 = itemstack4.getMaxStackSize() - itemstack3.stackSize;
                                    }

                                    itemstack4.splitStack(l1);

                                    if (itemstack4.stackSize == 0) {
                                        inventoryplayer.setItemStack((ItemStack)null);
                                    }

                                    itemstack3.stackSize += l1;
                                } else if (itemstack4.stackSize <= slot2.getSlotStackLimit()) {
                                    slot2.putStack(itemstack4);
                                    inventoryplayer.setItemStack(itemstack3);
                                }
                            } else if (itemstack3.getItem() == itemstack4.getItem() && itemstack4.getMaxStackSize() > 1 && (!itemstack3.getHasSubtypes() || itemstack3.getItemDamage() == itemstack4.getItemDamage()) && ItemStack.areItemStackTagsEqual(itemstack3, itemstack4)) {
                                l1 = itemstack3.stackSize;

                                if (l1 > 0 && l1 + itemstack4.stackSize <= itemstack4.getMaxStackSize()) {
                                    itemstack4.stackSize += l1;
                                    itemstack3 = slot2.decrStackSize(l1);

                                    if (itemstack3.stackSize == 0) {
                                        slot2.putStack((ItemStack)null);
                                    }

                                    slot2.onPickupFromSlot(p, inventoryplayer.getItemStack());
                                }
                            }
                        }

                        slot2.onSlotChanged();
                    }
                }
            } else if (arg3 == 2 && arg2 >= 0 && arg2 < 9) {
                slot2 = (Slot)this.inventorySlots.get(arg1);

                if (slot2.canTakeStack(p)) {
                    itemstack3 = inventoryplayer.getStackInSlot(arg2);
                    boolean flag = itemstack3 == null || slot2.inventory == inventoryplayer && slot2.isItemValid(itemstack3);
                    l1 = -1;

                    if (!flag) {
                        l1 = inventoryplayer.getFirstEmptyStack();
                        flag |= l1 > -1;
                    }

                    if (slot2.getHasStack() && flag) {
                        itemstack5 = slot2.getStack();
                        inventoryplayer.setInventorySlotContents(arg2, itemstack5.copy());

                        if ((slot2.inventory != inventoryplayer || !slot2.isItemValid(itemstack3)) && itemstack3 != null) {
                            if (l1 > -1) {
                                inventoryplayer.addItemStackToInventory(itemstack3);
                                slot2.decrStackSize(itemstack5.stackSize);
                                slot2.putStack((ItemStack)null);
                                slot2.onPickupFromSlot(p, itemstack5);
                            }
                        } else {
                            slot2.decrStackSize(itemstack5.stackSize);
                            slot2.putStack(itemstack3);
                            slot2.onPickupFromSlot(p, itemstack5);
                        }
                    } else if (!slot2.getHasStack() && itemstack3 != null && slot2.isItemValid(itemstack3)) {
                        inventoryplayer.setInventorySlotContents(arg2, (ItemStack)null);
                        slot2.putStack(itemstack3);
                    }
                }
            } else if (arg3 == 3 && p.capabilities.isCreativeMode && inventoryplayer.getItemStack() == null && arg1 >= 0) {
                slot2 = (Slot)this.inventorySlots.get(arg1);

                if (slot2 != null && slot2.getHasStack()) {
                    itemstack3 = slot2.getStack().copy();
                    itemstack3.stackSize = itemstack3.getMaxStackSize();
                    inventoryplayer.setItemStack(itemstack3);
                }
            } else if (arg3 == 4 && inventoryplayer.getItemStack() == null && arg1 >= 0) {
                slot2 = (Slot)this.inventorySlots.get(arg1);

                if (slot2 != null && slot2.getHasStack() && slot2.canTakeStack(p)) {
                    itemstack3 = slot2.decrStackSize(arg2 == 0 ? 1 : slot2.getStack().stackSize);
                    slot2.onPickupFromSlot(p, itemstack3);
                    p.dropPlayerItemWithRandomChoice(itemstack3, true);
                }
            } else if (arg3 == 6 && arg1 >= 0) {
                slot2 = (Slot)this.inventorySlots.get(arg1);
                itemstack3 = inventoryplayer.getItemStack();

                if (itemstack3 != null && (slot2 == null || !slot2.getHasStack() || !slot2.canTakeStack(p))) {
                    i1 = arg2 == 0 ? 0 : this.inventorySlots.size() - 1;
                    l1 = arg2 == 0 ? 1 : -1;

                    for (int i2 = 0; i2 < 2; ++i2) {
                        for (int j2 = i1; j2 >= 0 && j2 < this.inventorySlots.size() && itemstack3.stackSize < itemstack3.getMaxStackSize(); j2 += l1) {
                            Slot slot3 = (Slot)this.inventorySlots.get(j2);

                            if (slot3.getHasStack() && func_94527_a(slot3, itemstack3, true) && slot3.canTakeStack(p) && this.func_94530_a(itemstack3, slot3) && (i2 != 0 || slot3.getStack().stackSize != slot3.getStack().getMaxStackSize())) {
                                int k1 = Math.min(itemstack3.getMaxStackSize() - itemstack3.stackSize, slot3.getStack().stackSize);
                                ItemStack itemstack2 = slot3.decrStackSize(k1);
                                itemstack3.stackSize += k1;

                                if (itemstack2.stackSize <= 0) {
                                    slot3.putStack((ItemStack)null);
                                }

                                slot3.onPickupFromSlot(p, itemstack2);
                            }
                        }
                    }
                }

                this.detectAndSendChanges();
            }
        }

        return itemstack;
    }

    protected void retrySlotClick(int arg1, int arg2, boolean arg3, EntityPlayer p) {
        this.slotClick(arg1, arg2, 1, p);
    }
Пришел за конкретикой, чтобы понимать код, а не копипастить)
 
192
2
9
Разумеется. А вопрос в чем состоит, не почитал? Я не понимаю работы метода и прошу прошаренных что-то как-то разъяснить или ссылки на доки какие-нибудь хотя бы приложить что ли, если существуют... Я не нашел
 
1,074
72
372
Много лишнего накопипастил. Нужно только переопределить метод transferStackInSlot() - иначе краш будет. Он отвечает за логику работы shift и цифровых клавиш.

А так же может ли (правильно ли) быть предмет в 1.7.10 объявлен, как null вместо EMPTY или AIR, как в других версиях?
Правильно использовать null, потому что констант-заглушек в древней 1.7.10 ещё нет.
 
192
2
9
Спустя праздники (всех с праздником) я вернулся и решил попытаться разобраться.
Внутри метода transferStackInSlot() могут использоваться и другие методы по типу того же mergeItemStack().
Если я верно понял, то mergeItemStack() перемещает предмет в ближайший свободный слот из указанного интервала слотов.
С остальными методами я так пока и не разобрался. Подскажите, какие проверки и что я пропустил вообще в получившемся коде?
Крашей сейчас нет и код срабатывает на клиенте корректно, на сервере отдельно не чекал.
Class:
private boolean merge(ItemStack is, Slot slot, int arg1, int arg2) {
    if (!this.mergeItemStack(is, arg1, arg2, true)) return false;
    slot.onSlotChanged();
    return true;
}

public ItemStack transferStackInSlot(EntityPlayer p, int arg) {
    ItemStack is = null;
    Slot slot = (Slot) this.inventorySlots.get(arg);
    if (slot != null && slot.getHasStack()) {
        ItemStack is1 = slot.getStack();
        is = is1.copy();
        
        if (arg < 0 || arg+1 > this.inventorySlots.size()) return null;
        
        if (arg < 4) {
            if (merge(is1, slot, 4, 39)) return is;
        } else {
            if (merge(is1, slot, 0, 3)) return is;
        }
        
        if (is1.stackSize == 0)
            slot.putStack((ItemStack)null);
        else
            slot.onSlotChanged();
        
        return null;

    }
    return is;
}

protected void retrySlotClick(int arg1, int arg2, boolean arg3, EntityPlayer p) {
    this.slotClick(arg1, arg2, 1, p);
}
 
192
2
9
А еще я выяснил опытным путем, что в метод transferStackInSlot() передается игрок, который кликнул по слоту и соответственно индекс этого слота.
Число слотов в данном случае будет равно число слотов контейнера + число слотов инвентаря игрока в гуи и лежат все эти слоты в переменной this.inventorySlots. Первыми в порядке идут слоты контейнера, после слоты инвентаря игрока.
 
Сверху