Ванильный код перестал создавать EntityPlayeMP

Версия Minecraft
1.7.10
1,159
38
544
Добрый день, товарищи. У меня есть IEEP с кастомными инвентарями и я хочу сделать постоянную синхронизацию моих инвентарей с клиентом на уровне контейнера. Т.е. я хочу создавать один объект класса Container на клиенте и сервере и в момент открытия GUI просто использовать их. Ванильный код EntityPlayer так и делает со своим inventoryContainer - он никогда не пересоздает его и держит синхронизацию вызывая в onUpdate Container#detectAndSendChanges().

В своем IEEP я создал ссылку на Container, которую буду использовать при открытии GUI

Код:
package rsstats.inventory.container;

import cpw.mods.fml.common.registry.GameRegistry;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemArmor;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IIcon;
import rsstats.common.RSStats;
import rsstats.data.ExtendedPlayer;
import rsstats.inventory.SkillsInventory;
import rsstats.inventory.StatsInventory;
import rsstats.inventory.WearableInventory;
import rsstats.inventory.slots.SkillSlot;
import rsstats.inventory.slots.StatSlot;
import rsstats.items.MiscItems;
import rsstats.items.OtherItems;
import rsstats.items.SkillItem;
import rsstats.items.StatItem;
import rsstats.items.perk.PerkItem;
import rsstats.utils.Utils;
import ru.rarescrap.tabinventory.TabContainer;
import ru.rarescrap.tabinventory.TabHostInventory;
import ru.rarescrap.tabinventory.TabInventory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * @author rares
 */
public class MainContainer extends TabContainer {
    private final ExtendedPlayer player;
//    private final InventoryPlayer inventoryPlayer;
//    private final StatsInventory statsInventory;
//    private final WearableInventory wearableInventory;
//    private final SkillsInventory skillsInventory;
//
//    private final TabHostInventory otherTabsHost;
//    private final TabInventory otherTabsInventory;

    /** True, если игрок начал прокачивать статы, перейдя тем самым в режим редактирования */
    public boolean isEditMode = false;

    /** Хранит в себе прокачку игрока, которая была до того как он начал раскидывать очки прокачки.
     * Используется для отката изменений. */
    /* Может показаться, что ключ уже хранит свой itemDamage, который является уровнем статы, и можно просто
     * реализовать хранение через ArrayList, но не нужно забывать, что itemDamage в ключах - может быть измен
     * игроком, что делает хранилище ArrayList, т.к. сохраненные уровни стат будут утеряны */
    private Map<ItemStack, Integer> savedBild = new HashMap<ItemStack, Integer>(); // TODO: Заменить пару на String-Integer
    /** Хранит историю трат при прокачке в формате стак->массив. Каждая ячейка массива соответствует цене,
     * которую заплатил игрок для поднятия уровня стат на 1. */
    protected Map<ItemStack, ArrayList<Integer>> upgradeHistory;

    /** Количество очков прокачки, которые следует вернуть игроку, если тот решил отменить прокачку */
    private int wastedPoints;

    public MainContainer(ExtendedPlayer player/*, InventoryPlayer inventoryPlayer, StatsInventory statsInventory, SkillsInventory skillsInventory, WearableInventory wearableInventory, TabHostInventory otherTabsHost, TabInventory otherTabsInventory*/) {
        this.player = player;
//        this.inventoryPlayer = inventoryPlayer;
//        this.statsInventory = statsInventory;
//        this.skillsInventory = skillsInventory;
//        this.wearableInventory = wearableInventory;
//
//        this.otherTabsHost = otherTabsHost;
//        this.otherTabsInventory = otherTabsInventory;

        // Добавляем вкладочный инвентарь к контейнеру
        tabInventories.put(player.skillsInventory.getInventoryName(), player.skillsInventory);
        tabInventories.put(player.otherTabsInventory.getInventoryName(), player.otherTabsInventory);
        // Добавляем его к движку синхронизации (СЕРВЕР->КЛИЕНТ)
        if (player.isServerSide()/*!player.worldObj.isRemote*/) {
            getSync().addSync(player.skillsInventory);
            getSync().addSync(player.otherTabsInventory);
        }

        addSlots();
    }
    
    private void addSlots() {
        /*if (inventoryPlayer != null)
            for (int y = 0; y < 3; ++y) {
                for (int x = 0; x < 9; ++x) {
                    this.addSlotToContainer(new Slot(inventoryPlayer, x + y * 9 + 9, 8 + x * 18, 84 + y * 18));
                }
            }*/

        // Расставляем слоты на панели руки
        for (int i = 0; i < 9; i++) {
            this.addSlotToContainer(new Slot(player.getEntityPlayer().inventory, i, (i*18 -3) +8, 188));
        }

        // Расставляем слоты на панели статов
        for (int i = 0, slotIndex = 0; i < player.statsInventory.getSizeInventory(); ++i, slotIndex++) {
            this.addSlotToContainer(new StatSlot(player.statsInventory, i, (i*18 +167) +8, /*-24*/8));
            //this.addSlotToContainer(new StatSlot(statsInventory, slotIndex, i*9, 0));
        }

        // Расставляем слоты на панели скиллов
        for (int y = 0; y < 3; ++y) {
            for (int x = 0; x < 9; ++x) {
                this.addSlotToContainer(new SkillSlot(player.skillsInventory, x + y * 9 /*+ 9*/, (x*18 +167) +8, (y * 18) + 26));
            }
        }

        // Расставляем слоты для брони
        for (int i = 0; i < 4; ++i) {
            final int k = i;
            this.addSlotToContainer(new Slot(player.getEntityPlayer().inventory, player.getEntityPlayer().inventory.getSizeInventory() - 1 - i, (i * 18 + 51) + 8, 8) {

                @Override
                public int getSlotStackLimit() {
                    return 1;
                }

                @Override
                public boolean isItemValid(ItemStack par1ItemStack) {
                    if (par1ItemStack == null) return false;
                    return par1ItemStack.getItem().isValidArmor(par1ItemStack, k, player.getEntityPlayer());
                }

                @SideOnly(Side.CLIENT)
                public IIcon getBackgroundIconIndex() {
                    return ItemArmor.func_94602_b(k);
                }


                @Override
                public void onPickupFromSlot(EntityPlayer p_82870_1_, ItemStack itemStack) {
                    super.onPickupFromSlot(p_82870_1_, itemStack);
                    player.modifierManager.removeModifiersFrom(itemStack); // Удаляем модификаторы от прошлой брони
                }

                /**
                 * Helper method to put a stack in the slot.
                 *
                 * @param itemStack
                 */
                @Override
                public void putStack(ItemStack itemStack) {
                    // TODO: Баг с рассинхронизацией модификаторов на клиенте и серве был впервые замечен тут
                    // if (!player.worldObj.isRemote) - не работает на клиенте
                    if (!player.isServerSide()/*!*//*player.worldObj.isRemote*/) {
                        // Если в слоте уже был предмет - удаляем его модификаторы
                        if (this.getStack() != null) {
                            player.modifierManager.removeModifiersFrom(this.getStack());
                        }
                        // Извлекаем и сохраняем модификаторы из стака, который кладется в слот
                        player.modifierManager.addModifiersFrom(itemStack);
                    }
                    super.putStack(itemStack);

                    // Дебажная инфа
                    /*if (player.worldObj.isRemote) {
                        System.out.println("Клиентские модификаторы:");
                    } else {
                        System.out.println("Серверные модификаторы:");
                    }
                    for (ArrayList<RollModifier> modifiers : ExtendedPlayer.get(player).getModifierMap().values()) {
                        for (RollModifier modifier : modifiers) {
                            System.out.println(modifier);
                        }

                    }*/
                }
            });
        }

        // Расставляем слоты на панели носимых вещей
        for (int y = 0; y < 4; ++y) {
            for (int x = 0; x < 4; ++x) {
                this.addSlotToContainer(new Slot(player.wearableInventory, x + y * 4 /*+ 9*/, (x*18 + 51) +8, (y * 18) + 26) {
                    // Сюда нельзя помещать броню
                    @Override
                    public boolean isItemValid(ItemStack p_75214_1_) {
                        if (p_75214_1_.getItem() instanceof ItemArmor)
                            return false;
                        else
                            return super.isItemValid(p_75214_1_);
                    }
                });
            }
        }

        // Расставляем слоты на панели вкладок
        for (int i = 0, slotIndex = 0; i < player.otherTabsHost.getSizeInventory(); ++i, slotIndex++) {
            this.addSlotToContainer(new Slot(player.otherTabsHost, i, (i*18 +167) +8, 116) {
                @Override
                public boolean isItemValid(ItemStack p_75214_1_) {
                    return player.otherTabsHost.isUseableByPlayer(player.getEntityPlayer());
                }
            });

        }

        // Расставляем слоты, которе будут хранить содержимое вкладок
        for (int y = 0; y < 4; ++y) {
            for (int x = 0; x < 9; ++x) {
                this.addSlotToContainer(new Slot(player.otherTabsInventory, x + y * 9 /*+ 9*/, (x*18 +167) +8, (y * 18) + 134) {
                    @Override
                    public boolean isItemValid(ItemStack itemStack) {
                        // Получаем имя вкладки с перками
                        String perkTabName = OtherItems.perksTabItem.getUnlocalizedName();

                        // Проверяем в какую вкладку игрок хочет поместить стак (пока проверяем только очет ли он поместить ее в вкладку перков)
                        if (itemStack != null && itemStack.getItem() instanceof PerkItem && player.otherTabsInventory.getCurrentTab().equals(perkTabName)) {
                            // Проверяем, может ли игрок использовать целевой инвентарь и удовлетворяет ли игрок требованиям перка
                            PerkItem perkItem = (PerkItem) itemStack.getItem();
                            return player.otherTabsHost.isUseableByPlayer(player.getEntityPlayer()) && perkItem.isSuitableFor(player);
                        }

                        return false;

                        /* Небользая заметка: раз этот метод исполняется на клиенте, то результат работы этого метода
                         * должен бть одинаков на обоих сторонах. Возможно, это первый звоночек к тому, что придется
                         * держать постоянную (т.е. не в рамках срока жизни одного контейнера) ВСЕЙ инфы ExtendedPlayer'а
                         * с клиентом. Сделать это можно так же как ванильный код поддерживает синхронизацию
                         * EntityPlayer#inventoryContainer. */
                    }
                });
            }
        }
    }

    /**
     * This should always return true, since custom inventory can be accessed from anywhere
     * @param player TODO
     * @return TODO
     */
    @Override
    public boolean canInteractWith(EntityPlayer player) {
        return true;
    }
    
    /**
     * Called when a entityPlayer shift-clicks on a slot. You must override this or you will crash when someone does that.
     * Basically the same as every other container I make, since I define the same constant indices for all of them
     * @param player TODO
     * @param par2 TODO
     * @return TODO
     */
    @Override
    public ItemStack transferStackInSlot(EntityPlayer player, int par2) {
        ItemStack itemstack = null;
        Slot slot = (Slot) this.inventorySlots.get(par2);
        return itemstack;
    }

    // TODO: баг при стаках очков прокачки 64 и 1
    // TODO: Стоимость прокачки статы должна быть 2 очка, а не 1
    /**
     * Увеличивает переданную стату на 1, если в {@link EntityPlayer#inventory} в ExtendedPlayer есть хотя бы один {@link rsstats.items.ExpItem}.
     * Так же уменьшает ExpItem на 1.
     * @param statStack Стак со статой, которую необходимо прокачать
     * @throws IllegalAccessException Если в statStack нет {@link StatItem}'а
     */
    public void statUp(ItemStack statStack) {
        if ( !(statStack.getItem() instanceof StatItem) ) {
            throw new IllegalArgumentException("ItemStack argument must contain an StatItem.");
        }

        StatItem statItem = (StatItem) statStack.getItem();

        // Находим стак с очками прокачки // TODO: Удаляем комментарии
//        ItemStack expStack = null;
//        int expStackPos = -1; // Если -1 - значит стак с очками прокачки нельзя создать + его и не было
//        for (int i = 0; i < inventoryPlayer.mainInventory.length; i++) {
//            ItemStack itemStack = inventoryPlayer.mainInventory[i];
//            if (itemStack != null && "item.ExpItem".equals(itemStack.getUnlocalizedName())) {
//                expStack = itemStack;
//                expStackPos = i;
//                break;
//            }
//        }
//        if (expStack == null) {
//            return; // Если очков прокачки нет - выходим
//        }

        // Выявляет количество подтипов (уровней) статы
//        List subitems = new ArrayList();
//        statItem.getSubItems(statItem, CreativeTabs.tabMaterials, subitems);
        int subitems = statItem.getMaxDamage();
        int statItemDamage = statItem.getDamage(statStack);


//        if (statItemDamage != subitems.size() - 1) {
//            int price = 1; // Цена прокачки
//            if (statItem instanceof SkillItem) {
//                ItemStack parentStatStack = statsInventory.getStat(((SkillItem) statItem).parentStat.getUnlocalizedName());
//                int parentStatDamage = ((SkillItem) statItem).parentStat.getDamage(parentStatStack);
//                if (statItemDamage > parentStatDamage)
//                    price = 2;
//            }
//
//            // Отнимает очко прокачки ...
//            if (expStack.stackSize >= price) {
//                wastedPoints += price; // Сохраняем количество очков, что потратил пользователь
//                if (expStack.stackSize == price) {
//                    inventoryPlayer.mainInventory[expStackPos] = null; // Убираем стак
//                } else {
//                    expStack.stackSize -= price; // Уменьшаем стак
//                }
//            } else {
//                return;
//            }

        // и увеличиваем стату ...
        statStack.setItemDamage(statItemDamage < subitems ? statItemDamage + 1 : subitems);
//        } else { // Стата уже прокачана до предела - выходим
//            return;
//        }
    }

    // TODO: Рефакторить. См addItemStackToInventory
    public void statDown(ItemStack statStack) {
        if ( !(statStack.getItem() instanceof StatItem) ) {
            throw new IllegalArgumentException("ItemStack argument must contain an StatItem.");
        }

        StatItem statItem = (StatItem) statStack.getItem();
        int statItemDamage = statItem.getDamage(statStack);
//        boolean isExpStackCreated = false; // TODO: Удаляем комментарии

        /* В случае, если игрок в ходе одной сессии прокачки захотел обнулить
         * навыки, прокачанный в прошлой сесии - останавливаем его */
//        ItemStack s = statStack;
//        for (ItemStack keyStack : savedBild.keySet()) { // TODO: Нужно найти и использовать уже имеющийся поиск. Задолбало его писать каждый раз
//            if (keyStack.getUnlocalizedName().equals(statStack.getUnlocalizedName())) {
//                s = keyStack;
//                break;
//            }
//        }
//        if (statItemDamage <= savedBild.get(s)) {
//            return;
//        }

        // Находим стак с очками прокачки
//        ItemStack expStack = null;
//        for (ItemStack itemStack : inventoryPlayer.mainInventory) {
//            if (itemStack != null && "item.ExpItem".equals(itemStack.getUnlocalizedName())) {
//                expStack = itemStack;
//            }
//        }
//        // Если очков прокачки нет или их стак забит - ищем свободное место в инветаре, куда их можно положить
//        if (expStack == null || expStack.stackSize >= expStack.getMaxStackSize()) {
//            int freeSpaceIndex = findFreeSpaceInInventory(inventoryPlayer);
//            if (freeSpaceIndex != -1 && statItemDamage != 0) {
//                expStack = new ItemStack(GameRegistry.findItem(RSStats.MODID, "ExpItem"));
//                isExpStackCreated = true;
//                inventoryPlayer.setInventorySlotContents(freeSpaceIndex, expStack);
//            } else { // Если свободное место не было найдено - выходим
//                return;
//            }
//        }
//
//        // Выявляет количество подтипов (уровней) статы
//        List subitems = new ArrayList();
//        statItem.getSubItems(statItem, CreativeTabs.tabMaterials, subitems);
//
//        if (statItemDamage > 0) {
//            int reward; // Сколько очков прокачки вернется за отмену прокачки
//            if (statItem instanceof SkillItem) {
//                ItemStack parentStatStack = statsInventory.getStat(((SkillItem) statItem).parentStat.getUnlocalizedName());
//                int parentStatDamage = ((SkillItem) statItem).parentStat.getDamage(parentStatStack);
//                if (statItemDamage > parentStatDamage+1)
//                    reward = 2;
//                else
//                    reward = 1;
//            } else { // instanceof StatItem
//                reward = 1;
//            }
//
//            // возвращаем игроку очки прокачки ...
//            if (expStack.stackSize + reward <= expStack.getMaxStackSize()) {
//                // Убираем очки из "возмещения", если тот сам (т.е. без диалога) отменил прокачку конкретного навыка или статы
//                wastedPoints -= reward;
//
//                if (isExpStackCreated) {
//                    expStack.stackSize = reward;
//                } else {
//                    expStack.stackSize += reward;
//                }
//            } else {
//                reward = (expStack.stackSize + reward) % expStack.getMaxStackSize();
//                expStack.stackSize = expStack.getMaxStackSize();
//
//                int freeSpaceIndex = findFreeSpaceInInventory(inventoryPlayer);
//                if (freeSpaceIndex != -1) {
//                    expStack = new ItemStack(GameRegistry.findItem(RSStats.MODID, "ExpItem"));
//                    expStack.stackSize = reward;
//                    inventoryPlayer.setInventorySlotContents(freeSpaceIndex, expStack);
//                } else {
//                    return;
//                }
//            }

        // и уменьшаем стату ...
        statStack.setItemDamage(statItemDamage > 0 ? statItemDamage-1 : 0);
//        } else { // Стата уже спущена до минимального предела - выходим
//            return;
//        }
    }

    @Override
    public void detectAndSendChanges() {
        /* Т.к. по логике данного контейнера slotClick(...) может не работать на клиенте и сервере одинакого,
         * то для поддержания работоспособности синхронизации нам нужно выставить isChangingQuantityOnly = false
         * см. NetHandlerPlayServer#processClickWindow().
         *
         * Кейс: Если этого не сделать, то при прокачке статы на клиент не будет высылаться пакет об уменьшении
         * количества очков прокачки. По логике данного контейнера, проверку на возможность прокачки навыка/статы
         * осуществляет сервер. Именно поэтому slotClick(...) работает по разному на клиенте и сервере.
         */
        // https://rarescrap.blogspot.com/2018/10/minecraft-1_18.html?zx=7ca4a4ed658beb3
        if (player.getEntityPlayer() instanceof EntityPlayerMP)
            ((EntityPlayerMP) player.getEntityPlayer()).isChangingQuantityOnly = false;
        super.detectAndSendChanges(); // Вызов на клиенте ни к чему не приведет, т.к. список crafters будет пустым
    }

    // TODO: Это выполняется и для клиента и для сервера. Разгранич код. Приводит ли такое поведение к рассинхронизации?
    @Override
    public ItemStack slotClick(int slotId, int clickedButton, int mode, EntityPlayer playerIn) {
        // -999 - "переносимый" стак кликается за предеты контейнера (т.е. выбрасывается)
        // -1 - игрок тыкает переносимым стаков в то место в контейнере, в котором нет слота
        if (slotId == -999 || slotId == -1)
            return super.slotClick(slotId, clickedButton, mode, playerIn);

        Slot slot = getSlot(slotId);
        Item itemInSlot;
        if (slot.getStack() != null) {
            itemInSlot = slot.getStack().getItem();
        } else {
            return super.slotClick(slotId, clickedButton, mode, playerIn);
        }

        if (clickedButton == 1 && itemInSlot instanceof StatItem) { // ПКМ
            ItemStack temp = slot.getStack().copy();
            processStatRightClick(slot, mode, playerIn);

            /* Не следует возвращать прокачанный ItemStack, т.к. тогда
             * NetHandlerPlayServer#processClickWindow() обнаружит что стак, по которому кликнул игрок
             * не равен стаку в серверном инвентаре по такой же позиции. Это приведет к тому, что
             * ВСЕ содержимое окна перешлется на клиент, что не очень эффективно. */
            return temp;
        }

        if (clickedButton == 2 && itemInSlot instanceof StatItem) { // СКМ
            ItemStack temp = slot.getStack().copy();
            processStatMiddleClick(slot, mode, playerIn);
            return temp;
        }

        if ((slot.inventory == player.statsInventory || slot.inventory == player.skillsInventory) && (itemInSlot instanceof SkillItem || itemInSlot instanceof StatItem)) {
            ItemStack itemStack = getSlot(slotId).getStack();

            // Защита от дублирующихся сообщений в чате + ролл посылается в клиента, где определен класс GuiScreen
            if (playerIn.worldObj.isRemote) {
                ( (StatItem) itemStack.getItem() ).sendRollPacket(itemStack, playerIn, !GuiScreen.isCtrlKeyDown());
            }
            return slot.getStack();
        }

        // Поведение, если кликнут слот инвентаря otherTabsHost
        if (slot.inventory == player.otherTabsHost) {
            if (player.otherTabsHost.isUseableByPlayer(playerIn)) { // TODO: Не лучше ли использовать isItemValid из переопределенного слота?
                return super.slotClick(slotId, clickedButton, mode, playerIn); // "Захватваем" стак
            } else {
                return null; // Ничего не делаем
            }
        }
        // Поведение, если кликнут слот инвентаря otherTabsInventory
        if (slot.inventory == player.otherTabsInventory) {
            if (player.otherTabsInventory.isUseableByPlayer(playerIn)) {
                return super.slotClick(slotId, clickedButton, mode, playerIn);
            } else {
                return null;
            }
        }

        return super.slotClick(slotId, clickedButton, mode, playerIn);
    }

    // Пересчет при закрытии контейнера нужен, когда в диалоге нажимается "Отменить изменения"
    @Override
    public void onContainerClosed(EntityPlayer entityPlayer) {
        // Пересчитываем параметры и синхронизируем их с клиентом
        if (!entityPlayer.worldObj.isRemote) {
            player.updateParams();
            player.sync();
        }
        super.onContainerClosed(entityPlayer);
    }

    public SkillsInventory getSkillsInventory() {
        return player.skillsInventory;
    }

    /**
     * Сохраняет прокачку персонажа, дабыы иметь возможность ее восстановить
     */
    public void saveBild() {
        savedBild.clear();
        for (ItemStack statStack : player.statsInventory.getStats()) {
            if (statStack != null)
                savedBild.put(statStack, statStack.getItemDamage());
        }
        for (ItemStack skillStack : player.skillsInventory.getSkills()) {
            if (skillStack != null)
                savedBild.put(skillStack, skillStack.getItemDamage());
        }
    }

    /**
     * Восстанавливает прокачку персонажа
     */
    public void restoreBild() {
        for (ItemStack bildStack : savedBild.keySet()) {
            int lvl = savedBild.get(bildStack);
            if (bildStack.getItem() instanceof SkillItem) {
                for (ItemStack currentSkillStack : player.skillsInventory.getSkills()) {
                    if (currentSkillStack != null && currentSkillStack.getItem() == bildStack.getItem()) {
                        currentSkillStack.setItemDamage(lvl);
                    }
                }
            } else {
                for (ItemStack currentStatStack : player.statsInventory.getStats()) {
                    if (currentStatStack != null && currentStatStack.getItem() == bildStack.getItem()) {
                        currentStatStack.setItemDamage(lvl);
                    }
                }
            }
        }

        /* addItemStackToInventory успешно работает с ситуацией, если вернутся больше чем 64 предмета.
         * Нет нужды в своих проверках. */
        ItemStack expStack = new ItemStack(GameRegistry.findItem(RSStats.MODID, "ExpItem"), wastedPoints);
        this.player.getEntityPlayer().inventory.addItemStackToInventory(expStack);
    }

    /**
     * Вычисляет стоимость прокачки статы или навыка на один пункт.
     * @param statStack Стак со статой
     * @return Стоимость прокачки статы или навыка на один пункт. Если достигнут предел, вовзращает -1
     * @throws IllegalAccessException Если в statStack нет {@link StatItem}'а
     */
    public int getUpgradePrice(ItemStack statStack) {  // TODO: Unit-test this
        if ( !(statStack.getItem() instanceof StatItem) ) {
            throw new IllegalArgumentException("ItemStack argument must contain an StatItem.");
        }

        StatItem statItem = (StatItem) statStack.getItem();

//        List subitems = new ArrayList(); // TODO: Удалить закоментированный код
//        statStack.getItem().getSubItems(statItem, CreativeTabs.tabMaterials, subitems); // TODO: БАГ! Крашится на Dedicated сервере. Заменить костыль ниже на приемлимый аналог
        int subtypes = statItem.getMaxDamage();
//        if (statItem instanceof SkillItem) {
//            subtypes = SkillItem.NUMBER_OF_LEVELS - 1;
//        } else { // statItem instanceof StatItem
//            subtypes = StatItem.NUMBER_OF_LEVELS - 1;
//        }

        int price = 1; // Цена прокачки по-умолчанию

        int statItemDamage = statItem.getDamage(statStack);
        if (statItemDamage != subtypes) {
            if (statItem instanceof SkillItem) {
                ItemStack parentStatStack = player.statsInventory.getStat(((SkillItem) statItem).parentStat.getUnlocalizedName());
                int parentStatDamage = parentStatStack.getItemDamage(); //((SkillItem) statItem).parentStat.getDamage(parentStatStack);
                if (statItemDamage > parentStatDamage)
                    price = 2;
            } else { // instanceof StatItem ONLY
                price = 2;
            }

            return price;
        }

        return -1; // TODO: Не самый удачный выбор возвращаемого числа. Может быть сменить на 0?
    }

    /**
     * Вычисляет количество очков, которое получит игрок после даунгрейда статы или навыка на один пункт.
     * @param statStack Стак со статой
     * @return Возврат за даунгрейд статы или навыка на один пункт. Если достигнут предел, вовзращает -1
     * @throws IllegalAccessException Если в statStack нет {@link StatItem}'а
     */
    public int getDowngradeReward(ItemStack statStack) {  // TODO: Unit-test this
        if ( !(statStack.getItem() instanceof StatItem) ) {
            throw new IllegalArgumentException("ItemStack argument must contain an StatItem.");
        }

        StatItem statItem = (StatItem) statStack.getItem();
        int statItemDamage = statStack.getItemDamage();

        // Выявляет количество подтипов (уровней) статы // TODO: Удалить закоментированный код
//        List subitems = new ArrayList();
//        statItem.getSubItems(statItem, CreativeTabs.tabMaterials, subitems); // TODO: Вклалдка не нужна
        int subitem = statItem.getMaxDamage();

//        int reward; // Сколько очков прокачки вернется за отмену прокачки
//        if (statItemDamage > 0) {
//            if (statItem instanceof SkillItem) {
//                ItemStack parentStatStack = statsInventory.getStat(((SkillItem) statItem).parentStat.getUnlocalizedName());
//                int parentStatDamage = ((SkillItem) statItem).parentStat.getDamage(parentStatStack);
//                if (statItemDamage > parentStatDamage + 1)
//                    reward = 2;
//                else
//                    reward = 1;
//            } else { // instanceof StatItem ONLY
//                reward = 2;
//            }
//            return reward;
//        }

        //return -1;

        return getPriceFor(statStack, statStack.getItemDamage()/*-1*/);
    }

    /**
     * Определяет, может ли игрок получить возврат очков прокачки при попытки понизить стату
     * @param statStack Стак со статой
     * @return True, если может, false - нет или если попытается сбросить стату, которую не прокачивал в рамках текущей сессии прокачки.
     * @see #saveBild()
     */
    public boolean canRefund(ItemStack statStack) {  // TODO: Unit-test this
        if ( !(statStack.getItem() instanceof StatItem) ) {
            throw new IllegalArgumentException("ItemStack argument must contain an StatItem.");
        }

        int statItemDamage = statStack.getItemDamage();

        ItemStack s = statStack;
        for (ItemStack keyStack : savedBild.keySet()) { // TODO: Нужно найти и использовать уже имеющийся поиск. Задолбало его писать каждый раз
            if (keyStack.getUnlocalizedName().equals(statStack.getUnlocalizedName())) {
                s = keyStack;
                break;
            }
        }
        return statItemDamage > savedBild.get(s); // TODO: Проверка на null
    }

    /**
     * Возвращает игроку указанное количество очков прокачи и отнимает из их {@link #wastedPoints}
     * @param refund Очки прокачки, которые будут возвращены игроку
     */
    public void doRefund(int refund) { // TODO: Unit-test this
        ItemStack expStack = new ItemStack(MiscItems.expItem, refund);
        this.player.getEntityPlayer().inventory.addItemStackToInventory(expStack);
        wastedPoints -= refund;
    }

    /**
     * Обрабатывает ПКМ по стате/навыку, т.е. намерение пользователя прокачать навык
     * @param slot Слот, в котором лежит стата/навык
     * @param mode Режим клик (см. {@link net.minecraft.client.gui.inventory.GuiContainer#handleMouseClick})
     * @param playerIn Игрок, нажавший ПКМ
     * @return Стак, по которому был сделан клик
     */
    protected ItemStack processStatRightClick(Slot slot, int mode, EntityPlayer playerIn) {
        // Если у игрока есть очки прокачки и он не в режиме редактирования ...
        if (playerIn.inventory.hasItem(MiscItems.expItem) && !isEditMode) {
            // ... тогда инициализируем режим прокачки и сохраняем текущий билд игрока
            isEditMode = true;
            if (!playerIn.worldObj.isRemote) {
                wastedPoints = 0;
                saveBild();
                initUpgradeHistory();
            } else {
                return slot.getStack();
            }
        }

        if (isEditMode && !playerIn.worldObj.isRemote) { // Игрок в режиме прокачки - пытается повысить стату/навык
            int price = getUpgradePrice(slot.getStack());
            if (price != -1 && Utils.removeItemStackFromInventory(player.getEntityPlayer().inventory, "item.ExpItem", price)) {
                wastedPoints += price;

                // Добавляем трату очков в историю
                rememberPriceToNextLevel(slot.getStack(), price);

                // Поднимаем стату
                statUp(slot.getStack());

                // Пересчитваем параметры на сервере и информируем клиент, чтобы он сделал то же самое
                ExtendedPlayer extendedPlayer = ExtendedPlayer.get(playerIn);
                extendedPlayer.updateParams();
                extendedPlayer.sync();
            }
        }

        return slot.getStack();
    }

    /**
     * Обрабатывает СКМ по стате/навыку, т.е. намерение пользователя отменить прокачку навыка
     * @param slot Слот, в котором лежит стата/навык
     * @param mode Режим клика (см. {@link net.minecraft.client.gui.inventory.GuiContainer#handleMouseClick})
     * @param playerIn Игрок, нажавший ЛКМ
     * @return Стак, по которому был сделан клик
     */
    protected ItemStack processStatMiddleClick(Slot slot, int mode, EntityPlayer playerIn) {
        if (playerIn.worldObj.isRemote) // Расчет производится только на сервере
            return slot.getStack();

        int refund = getDowngradeReward(slot.getStack());
        if (isEditMode && canRefund(slot.getStack()) && refund != -1) { // Игрок в режиме прокачки - пытается понизить стату/навык
            statDown(slot.getStack());
            doRefund(refund);

            // Пересчитваем параметры на сервере и информируем клиент, чтобы он сделал то же самое
            ExtendedPlayer extendedPlayer = ExtendedPlayer.get(playerIn);
            extendedPlayer.updateParams();
            extendedPlayer.sync();
        }
        return slot.getStack();
    }

    /**
     * Инициализирует {@link #upgradeHistory} нулевой историей трат
     */
    protected void initUpgradeHistory() { // TODO: Unit-test this
        upgradeHistory = new HashMap<ItemStack, ArrayList<Integer>>();

        // Инициализируем историю трат для статов
        for (ItemStack itemStack : player.statsInventory.getStats()) {
            if (itemStack == null) continue;
            StatItem statItem = (StatItem) itemStack.getItem();

            ArrayList<Integer> history = new ArrayList<Integer>(
                    Collections.nCopies(statItem.getMaxDamage()+1, 0)
            );
            upgradeHistory.put(itemStack, history);
        }

        // Инициализируем историю трат для скиллов
        for (ItemStack itemStack : player.skillsInventory.getSkills()) {
            if (itemStack == null) continue;
            SkillItem skillItem = (SkillItem) itemStack.getItem();

            ArrayList<Integer> history = new ArrayList<Integer>(
                    Collections.nCopies(skillItem.getMaxDamage()+1, 0)
            );
            upgradeHistory.put(itemStack, history);
        }
    }

    /**
     * Сохраняет стомость прокачки, которую заплатит игрок, чтобы увеличить стату/навык на следующий уровень
     * @param itemStack Стак со статой/навыком
     * @param price Цена прокачки на следующий уровень
     */
    protected void rememberPriceToNextLevel(ItemStack itemStack, int price) { // TODO: Unit-test this
        ArrayList<Integer> history = upgradeHistory.get(itemStack);
        history.set(itemStack.getItemDamage()+1, price);
    }

    /**
     * Возвращет цену, которую заплатил игрок, чтобы стата перешла на определенный уровень (lvl) с предыдущего
     * @param itemStack Стак со статой/скиллом
     * @param lvl Уровень, на который поднялась стата с предыдущего уровня
     * @return Цена, которую заплатил игрок, чтобы стата перешла на определеннх уровень с предыдущего
     */
    protected int getPriceFor(ItemStack itemStack, int lvl) { // TODO: Unit-test this
        return upgradeHistory.get(itemStack).get(lvl);
    }

}

Код:
package rsstats.data;

import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.StatCollector;
import net.minecraft.world.World;
import net.minecraftforge.common.IExtendedEntityProperties;
import net.minecraftforge.common.util.Constants;
import rsstats.common.CommonProxy;
import rsstats.common.RSStats;
import rsstats.common.network.PacketSyncPlayer;
import rsstats.i18n.IClientTranslatable;
import rsstats.inventory.SkillsInventory;
import rsstats.inventory.StatsInventory;
import rsstats.inventory.WearableInventory;
import rsstats.inventory.container.MainContainer;
import rsstats.items.OtherItems;
import rsstats.items.SkillItems;
import rsstats.items.StatItem;
import rsstats.items.StatItems;
import rsstats.items.perk.IModifierDependent;
import rsstats.items.perk.PerkItem;
import rsstats.utils.Utils;
import ru.rarescrap.tabinventory.TabHostInventory;
import ru.rarescrap.tabinventory.TabInventory;

/**
 *
 * @author rares
 */
public class ExtendedPlayer implements IExtendedEntityProperties {

    public enum Rank implements IClientTranslatable {
        NOVICE,
        TEMPERED,
        VETERAN,
        HERO,
        LEGEND;

        public int toInt() {
            return this.ordinal();
        }

        public static Rank fromInt(int lvl) {
            return Rank.values()[lvl];
        }

        @Override
        public String getTranslatedString() {
            return StatCollector.translateToLocal("rank." + this.name().toLowerCase());
        }
    }

    /**
     * Ключи статичных параметров игрока
     */
    public enum ParamKeys implements IModifierDependent { // TODO: Упростить get и set методы ExtendedPlayer'а, использая в качестве параметра константы из енума Rank
        STEP,
        PROTECTION,
        PERSISTENCE,
        CHARISMA
    }

    /** Каждый наследник {@link IExtendedEntityProperties} должен иметь индивидуальное имя */
    private static final String EXTENDED_ENTITY_TAG = RSStats.MODID;

    private final EntityPlayer entityPlayer;

    /** Основной параметр игрока - Шаг */
    public int step = 6;
    /** Основной параметр игрока - Защита */
    public int protection;
    /** Основной параметр игрока - Стойкость */
    public int persistence;
    /** Основной параметр игрока - Харизма */
    public int charisma = 0;

    private int exp = 0;
    private Rank rank;
    private int tiredness = 0;
    private int tirednessLimit = 25;
    
    /** Инвентарь для статов */
    public final StatsInventory statsInventory;
    /** Инвентарь для скиллов */
    public final SkillsInventory skillsInventory;
    /** Инвентарь для носимых предметов */
    public final WearableInventory wearableInventory;
    /** Хост вкладок для {@link #otherTabsInventory} */
    public TabHostInventory otherTabsHost;
    /** Инвентарь с вкладками для прочей информации вроде перков, изъянов и т.д. */
    public TabInventory otherTabsInventory;

    /** Хранилище модификаторов броска игрока */
    public ModifierManager modifierManager = new ModifierManager();

    /*
    Тут в виде полей можно хранить дополнительную информацию о Entity: мана,
    золото, хп, переносимый вес, уровень радиации, репутацию и т.д. Т.е. все то,
    что нельзя хранить в виде блоков
    */

    public MainContainer container;

    private ExtendedPlayer(EntityPlayer player) {
        this.entityPlayer = player;
        wearableInventory = new WearableInventory(this);

        statsInventory = new StatsInventory("stats_inv", 9);
        skillsInventory = new SkillsInventory("skills_inv", 36, entityPlayer, statsInventory);

        otherTabsHost = new TabHostInventory("effects_host", 4);
        otherTabsInventory = new TabInventory("effects", 36, entityPlayer, otherTabsHost);

        container = new MainContainer(this);
    }
    
    /**
     * Used to register these extended properties for the entityPlayer during EntityConstructing event
     * This method is for convenience only; it will make your code look nicer
     * @param player
     */
    public static final void register(EntityPlayer player) {
        player.registerExtendedProperties(ExtendedPlayer.EXTENDED_ENTITY_TAG, new ExtendedPlayer(player));
    }
    
    /**
     * Returns ExtendedPlayer properties for entityPlayer
     * This method is for convenience only; it will make your code look nicer
     */
    public static final ExtendedPlayer get(EntityPlayer player) {
       return (ExtendedPlayer) player.getExtendedProperties(EXTENDED_ENTITY_TAG); // TODO: Добавить Exception, если null
    }

    public boolean isServerSide() {
        return this.entityPlayer instanceof EntityPlayerMP;
    }

    @Override
    public void saveNBTData(NBTTagCompound properties) {
        properties.setInteger("exp", exp);
        properties.setInteger("rank", rank.toInt());
        properties.setInteger("tiredness", tiredness);
        properties.setInteger("tirednessLimit", tirednessLimit);

        this.statsInventory.writeToNBT(properties);
        this.skillsInventory.writeToNBT(properties);
        this.wearableInventory.writeToNBT(properties);
        this.otherTabsHost.writeToNBT(properties);
        this.otherTabsInventory.writeToNBT(properties);
    }

    // TODO: Почему-то когда открывается GUI - Отображается категорий скиллов ловкости
    @Override
    public void loadNBTData(NBTTagCompound properties) {
        exp = properties.getInteger("exp");
        rank = Rank.fromInt(properties.getInteger("rank"));
        tiredness = properties.getInteger("tiredness");
        tirednessLimit = properties.getInteger("tirednessLimit");

        /* Нет нужды очищать инвентари перед применением сохранения, т.к.
         * readFromNBT() перезаписывает ВСЕ слоты инвентаря */
        this.statsInventory.readFromNBT(properties);
        this.skillsInventory.readFromNBT(properties);
        this.wearableInventory.readFromNBT(properties);
        this.otherTabsHost.readFromNBT(properties);
        this.otherTabsInventory.readFromNBT(properties);

        /* Т.к. ванильный инвентарь переписывать нежелательно, начальная инициализация модификатором от брони
         * реализована здесь */
        NBTTagList playerInventory = properties.getTagList("Inventory", Constants.NBT.TAG_COMPOUND);
        for (int i = 0; i < playerInventory.tagCount(); i++) {
            NBTTagCompound itemNBT = playerInventory.getCompoundTagAt(i);
            int slotID = itemNBT.getInteger("Slot");
            if (slotID >= 100 && slotID <= 103) {
                modifierManager.addModifiersFrom( ItemStack.loadItemStackFromNBT(itemNBT) );
            }

        }

        // Выгружаем модификаторы перков из NBT-сохранения
        TabInventory.Tab a = otherTabsInventory.items.get(OtherItems.perksTabItem.getUnlocalizedName());
        for (ItemStack stack : a.stacks) {
            if (stack != null) {
                PerkItem perkItem = (PerkItem) stack.getItem();
                modifierManager.addModifiers(perkItem.getModifiers());
            }
        }

        updateParams();
    }

    /**
     * Used to initialize the extended properties with the entity that this is attached to, as well
     * as the world object.
     * Called automatically if you register with the EntityConstructing event.
     * May be called multiple times if the extended properties is moved over to a new entity.
     *  Such as when a player switches dimension {Minecraft re-creates the player entity}
     * @param entity  The entity that this extended properties is attached to
     * @param world  The world in which the entity exists
     */
    @Override
    public void init(Entity entity, World world) {
        /* Крайне интересный хак. Дело в том, что init() используется для инициализации самого ExtendedPlayer'а,
         * а не Compound'а, который передается в loadNBTData(). TODO: Я все еще не разобрался как связана сущность, создаваемая тут и compound в loadNBTData
         * Я проивожу "инициализацию нового игрока" тут, т.к. если игрок зашел в игру первый раз - loadNBTData никогда не
         * вызовется при логине. Подозреваю это из-за того, что на сервере нет NBT записи об этом игроке.
         * Зато когда она заходит во второй раз - loadNBTData() точно вызовется, но т.к. перед ним вызовется и init(), то
         * в loadNBTData() нужно предварительно очистить инициализацию нового игрока, которая выполнилась тут.
         */
        ExtendedPlayer.get((EntityPlayer) entity).statsInventory.initItems();
        ExtendedPlayer.get((EntityPlayer) entity).skillsInventory.initItems();

        ExtendedPlayer.get((EntityPlayer) entity).rank = Rank.NOVICE;

        // Инициализируем основные параметры
        updateParams();
    }

    //@SideOnly(Side.SERVER) // TODO: Удалить это, когда я буду синхронизировать IEEP постоянно
    public int getParamWithModifiers(ParamKeys param) {

        switch (param) {
            case CHARISMA: return charisma+modifierManager.getTotalValue(param);
            case PERSISTENCE: return persistence+modifierManager.getTotalValue(param);
            case PROTECTION: return protection+modifierManager.getTotalValue(param);
            case STEP: return step+modifierManager.getTotalValue(param);
        }

        throw new IllegalStateException("Impossible state. Contact me about it");
    }

//    public int getProtection() {
//        return protection;
//    }
//
//    public int getStep() {
//        return step;
//    }
//
//    public int getPersistence() {
//        return persistence;
//    }
//
//    public int getCharisma() {
//
//        if (entityPlayer.worldObj.isRemote) {
//            return charisma;
//        } else {
//            int value = 0;
//            List<RollModifier> modifiers = modifierManager.getModifiers(ParamKeys.CHARISMA);
//            if (modifiers == null) return 0;
//
//            for (RollModifier modifier : modifiers)
//                value += modifier.getValue();
//
//            return value;
//
//        }
//    }

    public int getExp() {
        return exp;
    }

    public Rank getRank() {
        return rank;
    }

    public int getTiredness() {
        return tiredness;
    }

    public int getTirednessLimit() {
        return tirednessLimit;
    }

//    public void setProtection(int protection) {
//        this.protection = protection;
//    }
//
//    public void setPersistence(int persistence) {
//        this.persistence = persistence;
//    }
//
//    public void setCharisma(int charisma) {
//        this.charisma = charisma;
//    }

    public EntityPlayer getEntityPlayer() {
        return entityPlayer;
    }

    public void setRank(Rank rank) {
        this.rank = rank;
    }

    /**
     * Перерасчитывает параметры игрока (такие, как например, {@link #protection})
     */
    public void updateParams() {
        // Расчитываем параметр "Защита"
        ItemStack itemStack = ru.rarescrap.tabinventory.utils.Utils.findIn(
                skillsInventory,
                Utils.getRegistryName(SkillItems.fightingSkillItem),
                StatItems.agilityStatItem.getUnlocalizedName()); // TODO: UnlocalizedName используется в качестве ключа вкладки!

        if (itemStack != null) {
            if (itemStack.getItem().getDamage(itemStack) == 0) {
                this.protection = 2;
            } else {
                this.protection = 2 + StatItem.getRoll(itemStack).dice / 2;
            }
        }

        // Рассчитываем параметр "Стойкость"
        itemStack = Utils.findIn(statsInventory, Utils.getRegistryName(StatItems.enduranceStatItem));
        if (itemStack != null)
            this.persistence = 2 + StatItem.getRoll(itemStack).dice / 2;

        // Расчитываем параметр "Харизма"
        charisma = 0;
//        List<RollModifier> modifiers = modifierManager.getModifiers(ExtendedPlayer.ParamKeys.CHARISMA);
//        if (modifiers != null) {
//            for (RollModifier rollModifier : modifiers) {
//                charisma += rollModifier.value;
//            }
//        }
    }


    /**
     * Синхронихронизиует серверного и клиентского ExtendedPlayer'а.
     */
    public void sync() {
        if(!entityPlayer.worldObj.isRemote) {
            CommonProxy.INSTANCE.sendTo(new PacketSyncPlayer(this), (EntityPlayerMP)entityPlayer);
        }
    }
}


Код:
package rsstats.common;

import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.network.IGuiHandler;
import cpw.mods.fml.common.network.NetworkRegistry;
import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper;
import cpw.mods.fml.relauncher.Side;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import rsstats.blocks.Blocks;
import rsstats.blocks.UpgradeStationEntity;
import rsstats.common.event.KeyHandler;
import rsstats.common.network.*;
import rsstats.data.ExtendedPlayer;
import rsstats.inventory.container.MainContainer;
import rsstats.inventory.container.StatsContainer;
import rsstats.inventory.container.UpgradeContainer;
import rsstats.items.*;
import ru.rarescrap.tabinventory.network.NetworkUtils;

import static rsstats.common.RSStats.instance;
import static rsstats.common.RSStats.proxy;

/**
 * Проки, содержащий код как для клиента, так и сервера
 * @author RareScrap
 */
public class CommonProxy implements IGuiHandler {
    /** Обработчик нажатия кнопок, используемых для вызова GUI */
    protected KeyHandler keyHandler;
    /** Обертка для работы с сетью */
    public static /*final*/ SimpleNetworkWrapper INSTANCE = // TODO: Филан убран из-за тестов. Восстановить финал
            NetworkRegistry.INSTANCE.newSimpleChannel(RSStats.MODID.toLowerCase());

    public void preInit(FMLPreInitializationEvent event) {
        int discriminator = 0;

        // Когда сообщений станет много, их можно вынести в отдельный класс в метод init()
        INSTANCE.registerMessage(RollPacketToServer.MessageHandler.class, RollPacketToServer.class, discriminator++, Side.SERVER); // Регистрация сообщения о пробросе статы
        INSTANCE.registerMessage(PacketOpenRSStatsInventory.MessageHandler.class, PacketOpenRSStatsInventory.class, discriminator++, Side.SERVER);
        INSTANCE.registerMessage(PacketOpenSSPPage.MessageHandler.class, PacketOpenSSPPage.class, discriminator++, Side.SERVER);
        INSTANCE.registerMessage(PacketOpenWindow.MessageHandler.class, PacketOpenWindow.class, discriminator++, Side.SERVER);
        INSTANCE.registerMessage(PacketSyncGUI.MessageHandler.class, PacketSyncGUI.class, discriminator++, Side.SERVER);
        INSTANCE.registerMessage(PacketDialogAction.MessageHandler.class, PacketDialogAction.class, discriminator++, Side.SERVER);

        INSTANCE.registerMessage(PacketSyncPlayer.MessageHandler.class, PacketSyncPlayer.class, discriminator++, Side.CLIENT);
        INSTANCE.registerMessage(PacketCommandReponse.MessageHandler.class, PacketCommandReponse.class, discriminator++, Side.CLIENT);

        // Регистрируем сообщения для библиотеки MinecraftTabInventory
        NetworkUtils.registerMessages(INSTANCE, discriminator);

        // Регистрация предметов
        StatItems.registerItems();
        SkillItems.registerItems();
        OtherItems.registerItems();
        MiscItems.registerItems();
        PerkItems.registerItems();
        DebugItems.registerDebugItems();

        // Регистрация блоков
        Blocks.registerBlocks();

        // Это не срабатывает. Скорее всего, это решение предназначено для более поздних версий Forge
        /*UpgradeStationBlock block3DWeb = (Block3DWeb)(new Block3DWeb().setUnlocalizedName("mbe05_block_3d_web_unlocalised_name"));
        block3DWeb.setRegistryName("mbe05_block_3d_web_registry_name");
        ForgeRegistries.BLOCKS.register(block3DWeb);

        // We also need to create and register an ItemBlock for this block otherwise it won't appear in the inventory
        ItemBlock itemBlock3DWeb = new ItemBlock(block3DWeb);
        itemBlock3DWeb.setRegistryName(block3DWeb.getRegistryName());
        ForgeRegistries.ITEMS.register(itemBlock3DWeb);*/
    }

    public void init(FMLInitializationEvent event) {
        NetworkRegistry.INSTANCE.registerGuiHandler(instance, proxy);
        registerKeyBindings();
    }

    public void postInit(FMLPostInitializationEvent event) {}

    @Override
    public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
        switch (ID) {
            case RSStats.GUI:
                return ExtendedPlayer.get(player).container/*new MainContainer(player, player.inventory, ExtendedPlayer.get(player).statsInventory, ExtendedPlayer.get(player).skillsInventory, ExtendedPlayer.get(player).wearableInventory, ExtendedPlayer.get(player).otherTabsHost, ExtendedPlayer.get(player).otherTabsInventory)*/;
            case RSStats.SSP_UI_CODE:
                return new StatsContainer(player, player.inventory, ExtendedPlayer.get(player).statsInventory);
            case RSStats.UPGRADE_UI_FROM_BLOCK_CODE: {
                // Получение сущности по координатам блока, по которому кликнул игрок
                TileEntity tileEntity = world.getTileEntity(x, y, z);
                if (tileEntity instanceof UpgradeStationEntity) {
                    UpgradeStationEntity upgradeStationEntity = (UpgradeStationEntity) tileEntity;
                    return new UpgradeContainer(player.inventory, upgradeStationEntity.upgradeStationInventory);
                }
                break;
            }
            case RSStats.UPGRADE_UI_FROM_CMD_CODE:
                return new UpgradeContainer(player.inventory, null);
        }
        return null;
    }

    @Override
    public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
        return null; // Переопределяется в ClientProxy
    }
    
    // Переопределяется в ClientProxy
    public void registerKeyBindings() {}
}


Код:
package rsstats.client;

import cpw.mods.fml.client.registry.ClientRegistry;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.registry.GameRegistry;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.world.World;
import net.minecraftforge.client.MinecraftForgeClient;
import rsstats.blocks.Blocks;
import rsstats.blocks.UpgradeStationBlock;
import rsstats.blocks.UpgradeStationEntity;
import rsstats.blocks.UpgradeStationTESR;
import rsstats.client.gui.MainMenuGUI;
import rsstats.client.gui.SSPPage;
import rsstats.client.gui.UpgradeGUI;
import rsstats.common.CommonProxy;
import rsstats.common.RSStats;
import rsstats.common.event.KeyHandler;
import rsstats.data.ExtendedPlayer;
import rsstats.inventory.container.MainContainer;
import rsstats.inventory.container.UpgradeContainer;

/**
 * Прокси, исполняемый на стороне клиента
 * @author RareScrap
 */
public class ClientProxy extends CommonProxy {
    /**
     * Получает GUI для указанного ID
     * @param ID идентификатор GUI, объект которого необходимо возвратить
     * @param player Сущность игрока, вызывающего GUI
     * @param world Мир
     * @param x Местоположение сущности игрока по оси X
     * @param y Местоположение сущности игрока по оси Y
     * @param z Местоположение сущности игрока по оси Z
     * @return Потомок класса GuiContainer, соответствующий указанному ID
     */
    @Override
    public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
        // Проверяем, точно ли мы на клиете
        // TODO: Не могу однозначно сказать что эта проверка делает
        if (world instanceof WorldClient) {
            // Ищем GUI, соответствующий данному ID
            switch (ID) {
                case RSStats.GUI: {
                    return new MainMenuGUI(
                            ExtendedPlayer.get(player),
                            ExtendedPlayer.get(player).container
                            /*new MainContainer(player,
                                    player.inventory, // TODO: Уменьшить количество аргументов
                                    ExtendedPlayer.get(player).statsInventory,
                                    ExtendedPlayer.get(player).skillsInventory,
                                    ExtendedPlayer.get(player).wearableInventory,
                                    ExtendedPlayer.get(player).otherTabsHost,
                                    ExtendedPlayer.get(player).otherTabsInventory)*/
                    );
                }
                /*
                TODO: ВНИМАНИЕ! По туториалу, мне не нужно делать проверку в строке 25.
                Более того, мне нужно свитч затолкать в CommonProxy. Но так как
                у меня все работает и при таком раскладе, я пока оставлю все как есть
                */
                case RSStats.SSP_UI_CODE: return new SSPPage(player, player.inventory, ExtendedPlayer.get(player).statsInventory);
                case RSStats.UPGRADE_UI_FROM_BLOCK_CODE: return new UpgradeGUI(new UpgradeContainer(player.inventory, ((UpgradeStationEntity)world.getTileEntity(x, y, z)).upgradeStationInventory));
                case RSStats.UPGRADE_UI_FROM_CMD_CODE: return new UpgradeGUI(new UpgradeContainer(player.inventory, null));
                //case RSStats.DIALOG_GUI_CODE: return new MainMenuGUI.Dialog();
                /* Тут не выйдет открывать диалоговое окно, потому что в один момент времени может быть открыт только
                 * один GuiScreen (см код откртия GUI). Выход - вызывать drawScreen() диалогового окна прямо из того
                 * GuiScreen, над которым нужно отобразить диалог. Используйте этот подход, если вам нужно отобразить
                 * один GuiScreen поверх другого. */
            }
        }
        return null;
    }
    
    // Кей-хандлеры должны регистрироваться только на клиенте
    @Override
    public void registerKeyBindings() {
        keyHandler = new KeyHandler();
        FMLCommonHandler.instance().bus().register(keyHandler);
    }

    @Override
    public void preInit(FMLPreInitializationEvent event) {
        super.preInit(event);
        //MinecraftForgeClient.registerItemRenderer(Item.getItemFromBlock(new UpgradeStationBlock()), new UpgradeStationTESR.Renderer(new UpgradeStationTESR(), new UpgradeStationEntity()));
    }

    // TODO: Непонятный пиздец. Хочет найти способ не создавать static поля в CommonProxy, но из-за метода ниже, я не могу этого сделать
    @Override
    public void init(FMLInitializationEvent event) {
        super.init(event);
        // Регистрируем рендереры
        ClientRegistry.bindTileEntitySpecialRenderer(UpgradeStationEntity.class, new UpgradeStationTESR());

        // Пытаюсь как-то получить item для блока

        // Этот способ я использовал чтобы получить item блока, где сам блок не объявлен
        // static переменной при регистрации в CommonProxy. Т.е. я простосоздавал локальную
        // переменную через new UpgradeStationBlock() и регистрировал ее.
        // Результат - любая попытка достать итем блока возвращает null, кроме явного создания нового ItemBlock из блока
        Item d1 = Item.getItemFromBlock(new UpgradeStationBlock()); // null
        ItemBlock d = new ItemBlock(new UpgradeStationBlock());
        Item d12 = GameRegistry.findItem(RSStats.MODID, d.getUnlocalizedName()); // null
        Item d13 = ItemBlock.getItemFromBlock(new UpgradeStationBlock()); // null

        // А тут я делаю то же самое, но теперь использую ту же переменную блока, которая прошла регистрацию в CommonProxy
        // Результат - все вызовы возвращают нормальнйы item
        Item c1 = Item.getItemFromBlock(Blocks.upgradeStation);
        ItemBlock c = new ItemBlock(Blocks.upgradeStation);
        Item c12 = GameRegistry.findItem(RSStats.MODID, Blocks.upgradeStation.getUnlocalizedName());
        Item c13 = ItemBlock.getItemFromBlock(Blocks.upgradeStation);

        // Это не срабатывает, т.к. итем либо null, либо создан явно через консруктор (это, к моему удивлению, не регистрирует ItemRenderer
        //UpgradeStationTESR.Renderer d2 =  new UpgradeStationTESR.Renderer(new UpgradeStationTESR(), new UpgradeStationEntity());
        //MinecraftForgeClient.registerItemRenderer(ItemBlock.getItemFromBlock(new UpgradeStationBlock()), new UpgradeStationTESR.Renderer(new UpgradeStationTESR(), new UpgradeStationEntity()));

        // Только так так ItemRenderer успешно регистрируется и UpgradeStationTESR.Renderer#renderItem() успешно вызывается.
        MinecraftForgeClient.registerItemRenderer(c1, new UpgradeStationTESR.Renderer(new UpgradeStationTESR(), new UpgradeStationEntity()));

        // Как еще можно получить доступ к ItemRenderer'у. Где-то прочитал но зачем это надо - хз
        //Minecraft.getMinecraft().entityRenderer.itemRenderer.
    }
}

Я заранее извиняюсь за говнокод - он в процессе рефакторинга.
При входе в мир выдает краш-лог (приложил ниже). Я попытался пройти отладчиком, но вообще не понимаю что происходит. Вот на этой строе все нормально:
2018-12-27_20-16-24.png
Но как только я делаю step into в конструктор EntityPlayerMP, то получается это
2018-12-27_20-17-18.png

Как так получается? Вообще хз. Собстна, вот 2 вопроса:
  1. Годится ли такой способ синхронизации контейнера с клиентом? Приемлимо ли постоянно держать его открытым? Есть ли подводные камни?
  2. Что за дичь творится с отладчиков? Почему EntityPlayerMP не может создаться?
 
Краш-лог
[20:00:28] [Server thread/INFO]: Preparing start region for level 0
[20:00:29] [Server thread/INFO]: Preparing spawn area: 3%
[20:00:30] [Server thread/INFO]: Preparing spawn area: 5%
[20:00:31] [Server thread/INFO]: Preparing spawn area: 9%
[20:00:32] [Server thread/INFO]: Preparing spawn area: 14%
[20:00:33] [Server thread/INFO]: Preparing spawn area: 20%
[20:00:34] [Server thread/INFO]: Preparing spawn area: 26%
[20:00:35] [Server thread/INFO]: Preparing spawn area: 34%
[20:00:36] [Server thread/INFO]: Preparing spawn area: 43%
[20:00:38] [Server thread/INFO]: Preparing spawn area: 54%
[20:00:39] [Server thread/INFO]: Preparing spawn area: 66%
[20:00:40] [Server thread/INFO]: Preparing spawn area: 79%
[20:00:41] [Server thread/INFO]: Preparing spawn area: 91%
[20:00:41] [Server thread/INFO]: Changing view distance to 12, from 10
[20:00:42] [Server thread/ERROR] [FML]: Exception caught during firing event net.minecraftforge.event.entity.EntityEvent$EntityConstructing@1fb21595:
java.lang.NullPointerException
at rsstats.inventory.container.MainContainer.addSlots(MainContainer.java:118) ~[MainContainer.class:?]
at rsstats.inventory.container.MainContainer.<init>(MainContainer.java:86) ~[MainContainer.class:?]
at rsstats.data.ExtendedPlayer.<init>(ExtendedPlayer.java:119) ~[ExtendedPlayer.class:?]
at rsstats.data.ExtendedPlayer.register(ExtendedPlayer.java:128) ~[ExtendedPlayer.class:?]
at rsstats.common.event.ModEventHandler.onEntityConstructing(ModEventHandler.java:40) ~[ModEventHandler.class:?]
at cpw.mods.fml.common.eventhandler.ASMEventHandler_10_ModEventHandler_onEntityConstructing_EntityConstructing.invoke(.dynamic) ~[?:?]
at cpw.mods.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:54) ~[ASMEventHandler.class:?]
at cpw.mods.fml.common.eventhandler.EventBus.post(EventBus.java:140) [EventBus.class:?]
at net.minecraft.entity.Entity.<init>(Entity.java:224) [Entity.class:?]
at net.minecraft.entity.EntityLivingBase.<init>(EntityLivingBase.java:155) [EntityLivingBase.class:?]
at net.minecraft.entity.player.EntityPlayer.<init>(EntityPlayer.java:165) [EntityPlayer.class:?]
at net.minecraft.entity.player.EntityPlayerMP.<init>(EntityPlayerMP.java:158) [EntityPlayerMP.class:?]
at net.minecraft.server.management.ServerConfigurationManager.createPlayerForUser(ServerConfigurationManager.java:443) [ServerConfigurationManager.class:?]
at net.minecraft.server.network.NetHandlerLoginServer.func_147326_c(NetHandlerLoginServer.java:105) [NetHandlerLoginServer.class:?]
at net.minecraft.server.network.NetHandlerLoginServer.onNetworkTick(NetHandlerLoginServer.java:64) [NetHandlerLoginServer.class:?]
at net.minecraft.network.NetworkManager.processReceivedPackets(NetworkManager.java:244) [NetworkManager.class:?]
at net.minecraft.network.NetworkSystem.networkTick(NetworkSystem.java:182) [NetworkSystem.class:?]
at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:726) [MinecraftServer.class:?]
at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:614) [MinecraftServer.class:?]
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:118) [IntegratedServer.class:?]
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:485) [MinecraftServer.class:?]
at net.minecraft.server.MinecraftServer$2.run(MinecraftServer.java:752) [MinecraftServer$2.class:?]
[20:00:43] [Server thread/ERROR] [FML]: Index: 1 Listeners:
[20:00:43] [Server thread/ERROR] [FML]: 0: NORMAL
[20:00:43] [Server thread/ERROR] [FML]: 1: ASM: rsstats.common.event.ModEventHandler@15b12bc5 onEntityConstructing(Lnet/minecraftforge/event/entity/EntityEvent$EntityConstructing;)V
[20:00:43] [Server thread/ERROR]: Encountered an unexpected exception
net.minecraft.util.ReportedException: Ticking memory connection
at net.minecraft.network.NetworkSystem.networkTick(NetworkSystem.java:198) ~[NetworkSystem.class:?]
at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:726) ~[MinecraftServer.class:?]
at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:614) ~[MinecraftServer.class:?]
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:118) ~[IntegratedServer.class:?]
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:485) [MinecraftServer.class:?]
at net.minecraft.server.MinecraftServer$2.run(MinecraftServer.java:752) [MinecraftServer$2.class:?]
Caused by: java.lang.NullPointerException
at rsstats.inventory.container.MainContainer.addSlots(MainContainer.java:118) ~[MainContainer.class:?]
at rsstats.inventory.container.MainContainer.<init>(MainContainer.java:86) ~[MainContainer.class:?]
at rsstats.data.ExtendedPlayer.<init>(ExtendedPlayer.java:119) ~[ExtendedPlayer.class:?]
at rsstats.data.ExtendedPlayer.register(ExtendedPlayer.java:128) ~[ExtendedPlayer.class:?]
at rsstats.common.event.ModEventHandler.onEntityConstructing(ModEventHandler.java:40) ~[ModEventHandler.class:?]
at cpw.mods.fml.common.eventhandler.ASMEventHandler_10_ModEventHandler_onEntityConstructing_EntityConstructing.invoke(.dynamic) ~[?:?]
at cpw.mods.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:54) ~[ASMEventHandler.class:?]
at cpw.mods.fml.common.eventhandler.EventBus.post(EventBus.java:140) ~[EventBus.class:?]
at net.minecraft.entity.Entity.<init>(Entity.java:224) ~[Entity.class:?]
at net.minecraft.entity.EntityLivingBase.<init>(EntityLivingBase.java:155) ~[EntityLivingBase.class:?]
at net.minecraft.entity.player.EntityPlayer.<init>(EntityPlayer.java:165) ~[EntityPlayer.class:?]
at net.minecraft.entity.player.EntityPlayerMP.<init>(EntityPlayerMP.java:158) ~[EntityPlayerMP.class:?]
at net.minecraft.server.management.ServerConfigurationManager.createPlayerForUser(ServerConfigurationManager.java:443) ~[ServerConfigurationManager.class:?]
at net.minecraft.server.network.NetHandlerLoginServer.func_147326_c(NetHandlerLoginServer.java:105) ~[NetHandlerLoginServer.class:?]
at net.minecraft.server.network.NetHandlerLoginServer.onNetworkTick(NetHandlerLoginServer.java:64) ~[NetHandlerLoginServer.class:?]
at net.minecraft.network.NetworkManager.processReceivedPackets(NetworkManager.java:244) ~[NetworkManager.class:?]
at net.minecraft.network.NetworkSystem.networkTick(NetworkSystem.java:182) ~[NetworkSystem.class:?]
... 5 more
[20:00:43] [Server thread/ERROR]: This crash report has been saved to: D:\Users\rares\Downloads\SavageWorldRP\eclipse\.\crash-reports\crash-2018-12-27_20.00.43-server.txt
[20:00:43] [Server thread/INFO]: Stopping server
[20:00:43] [Server thread/INFO]: Saving players
[20:00:43] [Server thread/INFO]: Saving worlds
[20:00:43] [Server thread/INFO]: Saving chunks for level 'Новый мир'/Overworld
[20:00:43] [Client thread/INFO] [STDOUT]: [net.minecraft.client.Minecraft:displayCrashReport:388]: ---- Minecraft Crash Report ----
// Sorry :(

Time: 27.12.18 20:00
Description: Ticking memory connection

java.lang.NullPointerException: Ticking memory connection
at rsstats.inventory.container.MainContainer.addSlots(MainContainer.java:118)
at rsstats.inventory.container.MainContainer.<init>(MainContainer.java:86)
at rsstats.data.ExtendedPlayer.<init>(ExtendedPlayer.java:119)
at rsstats.data.ExtendedPlayer.register(ExtendedPlayer.java:128)
at rsstats.common.event.ModEventHandler.onEntityConstructing(ModEventHandler.java:40)
at cpw.mods.fml.common.eventhandler.ASMEventHandler_10_ModEventHandler_onEntityConstructing_EntityConstructing.invoke(.dynamic)
at cpw.mods.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:54)
at cpw.mods.fml.common.eventhandler.EventBus.post(EventBus.java:140)
at net.minecraft.entity.Entity.<init>(Entity.java:224)
at net.minecraft.entity.EntityLivingBase.<init>(EntityLivingBase.java:155)
at net.minecraft.entity.player.EntityPlayer.<init>(EntityPlayer.java:165)
at net.minecraft.entity.player.EntityPlayerMP.<init>(EntityPlayerMP.java:158)
at net.minecraft.server.management.ServerConfigurationManager.createPlayerForUser(ServerConfigurationManager.java:443)
at net.minecraft.server.network.NetHandlerLoginServer.func_147326_c(NetHandlerLoginServer.java:105)
at net.minecraft.server.network.NetHandlerLoginServer.onNetworkTick(NetHandlerLoginServer.java:64)
at net.minecraft.network.NetworkManager.processReceivedPackets(NetworkManager.java:244)
at net.minecraft.network.NetworkSystem.networkTick(NetworkSystem.java:182)
at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:726)
at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:614)
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:118)
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:485)
at net.minecraft.server.MinecraftServer$2.run(MinecraftServer.java:752)


A detailed walkthrough of the error, its code path and all known details is as follows:
---------------------------------------------------------------------------------------

-- Head --
Stacktrace:
at rsstats.inventory.container.MainContainer.addSlots(MainContainer.java:118)
at rsstats.inventory.container.MainContainer.<init>(MainContainer.java:86)
at rsstats.data.ExtendedPlayer.<init>(ExtendedPlayer.java:119)
at rsstats.data.ExtendedPlayer.register(ExtendedPlayer.java:128)
at rsstats.common.event.ModEventHandler.onEntityConstructing(ModEventHandler.java:40)
at cpw.mods.fml.common.eventhandler.ASMEventHandler_10_ModEventHandler_onEntityConstructing_EntityConstructing.invoke(.dynamic)
at cpw.mods.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:54)
at cpw.mods.fml.common.eventhandler.EventBus.post(EventBus.java:140)
at net.minecraft.entity.Entity.<init>(Entity.java:224)
at net.minecraft.entity.EntityLivingBase.<init>(EntityLivingBase.java:155)
at net.minecraft.entity.player.EntityPlayer.<init>(EntityPlayer.java:165)
at net.minecraft.entity.player.EntityPlayerMP.<init>(EntityPlayerMP.java:158)
at net.minecraft.server.management.ServerConfigurationManager.createPlayerForUser(ServerConfigurationManager.java:443)
at net.minecraft.server.network.NetHandlerLoginServer.func_147326_c(NetHandlerLoginServer.java:105)
at net.minecraft.server.network.NetHandlerLoginServer.onNetworkTick(NetHandlerLoginServer.java:64)
at net.minecraft.network.NetworkManager.processReceivedPackets(NetworkManager.java:244)

-- Ticking connection --
Details:
Connection: net.minecraft.network.NetworkManager@448a3ead
Stacktrace:
at net.minecraft.network.NetworkSystem.networkTick(NetworkSystem.java:182)
at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:726)
at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:614)
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:118)
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:485)
at net.minecraft.server.MinecraftServer$2.run(MinecraftServer.java:752)

-- System Details --
Details:
Minecraft Version: 1.7.10
Operating System: Windows 10 (amd64) version 10.0
Java Version: 1.8.0_131, Oracle Corporation
Java VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation
Memory: 706855112 bytes (674 MB) / 1038876672 bytes (990 MB) up to 1038876672 bytes (990 MB)
JVM Flags: 3 total; -Xincgc -Xmx1024M -Xms1024M
AABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used
IntCache: cache: 1, tcache: 1, allocated: 12, tallocated: 94
FML: MCP v9.05 FML v7.10.99.99 Minecraft Forge 10.13.4.1558 5 mods loaded, 5 mods active
States: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored
UCHIJAAAA mcp{9.05} [Minecraft Coder Pack] (minecraft.jar)
UCHIJAAAA FML{7.10.99.99} [Forge Mod Loader] (forgeSrc-1.7.10-10.13.4.1558-1.7.10.jar)
UCHIJAAAA Forge{10.13.4.1558} [Minecraft Forge] (forgeSrc-1.7.10-10.13.4.1558-1.7.10.jar)
UCHIJAAAA rsstats{0.0.1a} [RSStats] (SavageWorldRP_main)
UCHIJAAAA examplemod{1.0} [Example Mod] (modid-1.0.jar)
GL info: ~~ERROR~~ RuntimeException: No OpenGL context found in the current thread.
Profiler Position: N/A (disabled)
Vec3 Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used
Player Count: 0 / 8; []
Type: Integrated Server (map_client.txt)
Is Modded: Definitely; Client brand changed to 'fml,forge'
[20:00:43] [Client thread/INFO] [STDOUT]: [net.minecraft.client.Minecraft:displayCrashReport:393]: #@!@# Game crashed! Crash report saved to: #@!@# .\crash-reports\crash-2018-12-27_20.00.43-server.txt
[20:00:43] [Client thread/INFO] [FML]: Waiting for the server to terminate/save.
[20:00:47] [Server thread/INFO]: Saving chunks for level 'Новый мир'/Nether
[20:00:47] [Server thread/INFO]: Saving chunks for level 'Новый мир'/The End
[20:00:51] [Server thread/INFO] [FML]: Unloading dimension 0
[20:00:51] [Server thread/INFO] [FML]: Unloading dimension -1
[20:00:51] [Server thread/INFO] [FML]: Unloading dimension 1
[20:00:51] [Server thread/INFO] [FML]: Applying holder lookups
[20:00:51] [Server thread/INFO] [FML]: Holder lookups applied
[20:00:51] [Server thread/INFO] [FML]: The state engine was in incorrect state SERVER_STOPPING and forced into state SERVER_STOPPED. Errors may have been discarded.
[20:00:51] [Client thread/INFO] [FML]: Server terminated.
Disconnected from the target VM, address: '127.0.0.1:60996', transport: 'socket'
AL lib: (EE) alc_cleanup: 1 device not closed
Java HotSpot(TM) 64-Bit Server VM warning: Using incremental CMS is deprecated and will likely be removed in a future release

Process finished with exit code -1
Краш-лог:
[20:00:28] [Server thread/INFO]: Preparing start region for level 0
[20:00:29] [Server thread/INFO]: Preparing spawn area: 3%
[20:00:30] [Server thread/INFO]: Preparing spawn area: 5%
[20:00:31] [Server thread/INFO]: Preparing spawn area: 9%
[20:00:32] [Server thread/INFO]: Preparing spawn area: 14%
[20:00:33] [Server thread/INFO]: Preparing spawn area: 20%
[20:00:34] [Server thread/INFO]: Preparing spawn area: 26%
[20:00:35] [Server thread/INFO]: Preparing spawn area: 34%
[20:00:36] [Server thread/INFO]: Preparing spawn area: 43%
[20:00:38] [Server thread/INFO]: Preparing spawn area: 54%
[20:00:39] [Server thread/INFO]: Preparing spawn area: 66%
[20:00:40] [Server thread/INFO]: Preparing spawn area: 79%
[20:00:41] [Server thread/INFO]: Preparing spawn area: 91%
[20:00:41] [Server thread/INFO]: Changing view distance to 12, from 10
[20:00:42] [Server thread/ERROR] [FML]: Exception caught during firing event net.minecraftforge.event.entity.EntityEvent$EntityConstructing@1fb21595:
java.lang.NullPointerException
	at rsstats.inventory.container.MainContainer.addSlots(MainContainer.java:118) ~[MainContainer.class:?]
	at rsstats.inventory.container.MainContainer.<init>(MainContainer.java:86) ~[MainContainer.class:?]
	at rsstats.data.ExtendedPlayer.<init>(ExtendedPlayer.java:119) ~[ExtendedPlayer.class:?]
	at rsstats.data.ExtendedPlayer.register(ExtendedPlayer.java:128) ~[ExtendedPlayer.class:?]
	at rsstats.common.event.ModEventHandler.onEntityConstructing(ModEventHandler.java:40) ~[ModEventHandler.class:?]
	at cpw.mods.fml.common.eventhandler.ASMEventHandler_10_ModEventHandler_onEntityConstructing_EntityConstructing.invoke(.dynamic) ~[?:?]
	at cpw.mods.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:54) ~[ASMEventHandler.class:?]
	at cpw.mods.fml.common.eventhandler.EventBus.post(EventBus.java:140) [EventBus.class:?]
	at net.minecraft.entity.Entity.<init>(Entity.java:224) [Entity.class:?]
	at net.minecraft.entity.EntityLivingBase.<init>(EntityLivingBase.java:155) [EntityLivingBase.class:?]
	at net.minecraft.entity.player.EntityPlayer.<init>(EntityPlayer.java:165) [EntityPlayer.class:?]
	at net.minecraft.entity.player.EntityPlayerMP.<init>(EntityPlayerMP.java:158) [EntityPlayerMP.class:?]
	at net.minecraft.server.management.ServerConfigurationManager.createPlayerForUser(ServerConfigurationManager.java:443) [ServerConfigurationManager.class:?]
	at net.minecraft.server.network.NetHandlerLoginServer.func_147326_c(NetHandlerLoginServer.java:105) [NetHandlerLoginServer.class:?]
	at net.minecraft.server.network.NetHandlerLoginServer.onNetworkTick(NetHandlerLoginServer.java:64) [NetHandlerLoginServer.class:?]
	at net.minecraft.network.NetworkManager.processReceivedPackets(NetworkManager.java:244) [NetworkManager.class:?]
	at net.minecraft.network.NetworkSystem.networkTick(NetworkSystem.java:182) [NetworkSystem.class:?]
	at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:726) [MinecraftServer.class:?]
	at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:614) [MinecraftServer.class:?]
	at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:118) [IntegratedServer.class:?]
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:485) [MinecraftServer.class:?]
	at net.minecraft.server.MinecraftServer$2.run(MinecraftServer.java:752) [MinecraftServer$2.class:?]
[20:00:43] [Server thread/ERROR] [FML]: Index: 1 Listeners:
[20:00:43] [Server thread/ERROR] [FML]: 0: NORMAL
[20:00:43] [Server thread/ERROR] [FML]: 1: ASM: rsstats.common.event.ModEventHandler@15b12bc5 onEntityConstructing(Lnet/minecraftforge/event/entity/EntityEvent$EntityConstructing;)V
[20:00:43] [Server thread/ERROR]: Encountered an unexpected exception
net.minecraft.util.ReportedException: Ticking memory connection
	at net.minecraft.network.NetworkSystem.networkTick(NetworkSystem.java:198) ~[NetworkSystem.class:?]
	at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:726) ~[MinecraftServer.class:?]
	at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:614) ~[MinecraftServer.class:?]
	at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:118) ~[IntegratedServer.class:?]
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:485) [MinecraftServer.class:?]
	at net.minecraft.server.MinecraftServer$2.run(MinecraftServer.java:752) [MinecraftServer$2.class:?]
Caused by: java.lang.NullPointerException
	at rsstats.inventory.container.MainContainer.addSlots(MainContainer.java:118) ~[MainContainer.class:?]
	at rsstats.inventory.container.MainContainer.<init>(MainContainer.java:86) ~[MainContainer.class:?]
	at rsstats.data.ExtendedPlayer.<init>(ExtendedPlayer.java:119) ~[ExtendedPlayer.class:?]
	at rsstats.data.ExtendedPlayer.register(ExtendedPlayer.java:128) ~[ExtendedPlayer.class:?]
	at rsstats.common.event.ModEventHandler.onEntityConstructing(ModEventHandler.java:40) ~[ModEventHandler.class:?]
	at cpw.mods.fml.common.eventhandler.ASMEventHandler_10_ModEventHandler_onEntityConstructing_EntityConstructing.invoke(.dynamic) ~[?:?]
	at cpw.mods.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:54) ~[ASMEventHandler.class:?]
	at cpw.mods.fml.common.eventhandler.EventBus.post(EventBus.java:140) ~[EventBus.class:?]
	at net.minecraft.entity.Entity.<init>(Entity.java:224) ~[Entity.class:?]
	at net.minecraft.entity.EntityLivingBase.<init>(EntityLivingBase.java:155) ~[EntityLivingBase.class:?]
	at net.minecraft.entity.player.EntityPlayer.<init>(EntityPlayer.java:165) ~[EntityPlayer.class:?]
	at net.minecraft.entity.player.EntityPlayerMP.<init>(EntityPlayerMP.java:158) ~[EntityPlayerMP.class:?]
	at net.minecraft.server.management.ServerConfigurationManager.createPlayerForUser(ServerConfigurationManager.java:443) ~[ServerConfigurationManager.class:?]
	at net.minecraft.server.network.NetHandlerLoginServer.func_147326_c(NetHandlerLoginServer.java:105) ~[NetHandlerLoginServer.class:?]
	at net.minecraft.server.network.NetHandlerLoginServer.onNetworkTick(NetHandlerLoginServer.java:64) ~[NetHandlerLoginServer.class:?]
	at net.minecraft.network.NetworkManager.processReceivedPackets(NetworkManager.java:244) ~[NetworkManager.class:?]
	at net.minecraft.network.NetworkSystem.networkTick(NetworkSystem.java:182) ~[NetworkSystem.class:?]
	... 5 more
[20:00:43] [Server thread/ERROR]: This crash report has been saved to: D:\Users\rares\Downloads\SavageWorldRP\eclipse\.\crash-reports\crash-2018-12-27_20.00.43-server.txt
[20:00:43] [Server thread/INFO]: Stopping server
[20:00:43] [Server thread/INFO]: Saving players
[20:00:43] [Server thread/INFO]: Saving worlds
[20:00:43] [Server thread/INFO]: Saving chunks for level 'Новый мир'/Overworld
[20:00:43] [Client thread/INFO] [STDOUT]: [net.minecraft.client.Minecraft:displayCrashReport:388]: ---- Minecraft Crash Report ----
// Sorry :(

Time: 27.12.18 20:00
Description: Ticking memory connection

java.lang.NullPointerException: Ticking memory connection
	at rsstats.inventory.container.MainContainer.addSlots(MainContainer.java:118)
	at rsstats.inventory.container.MainContainer.<init>(MainContainer.java:86)
	at rsstats.data.ExtendedPlayer.<init>(ExtendedPlayer.java:119)
	at rsstats.data.ExtendedPlayer.register(ExtendedPlayer.java:128)
	at rsstats.common.event.ModEventHandler.onEntityConstructing(ModEventHandler.java:40)
	at cpw.mods.fml.common.eventhandler.ASMEventHandler_10_ModEventHandler_onEntityConstructing_EntityConstructing.invoke(.dynamic)
	at cpw.mods.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:54)
	at cpw.mods.fml.common.eventhandler.EventBus.post(EventBus.java:140)
	at net.minecraft.entity.Entity.<init>(Entity.java:224)
	at net.minecraft.entity.EntityLivingBase.<init>(EntityLivingBase.java:155)
	at net.minecraft.entity.player.EntityPlayer.<init>(EntityPlayer.java:165)
	at net.minecraft.entity.player.EntityPlayerMP.<init>(EntityPlayerMP.java:158)
	at net.minecraft.server.management.ServerConfigurationManager.createPlayerForUser(ServerConfigurationManager.java:443)
	at net.minecraft.server.network.NetHandlerLoginServer.func_147326_c(NetHandlerLoginServer.java:105)
	at net.minecraft.server.network.NetHandlerLoginServer.onNetworkTick(NetHandlerLoginServer.java:64)
	at net.minecraft.network.NetworkManager.processReceivedPackets(NetworkManager.java:244)
	at net.minecraft.network.NetworkSystem.networkTick(NetworkSystem.java:182)
	at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:726)
	at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:614)
	at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:118)
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:485)
	at net.minecraft.server.MinecraftServer$2.run(MinecraftServer.java:752)


A detailed walkthrough of the error, its code path and all known details is as follows:
---------------------------------------------------------------------------------------

-- Head --
Stacktrace:
	at rsstats.inventory.container.MainContainer.addSlots(MainContainer.java:118)
	at rsstats.inventory.container.MainContainer.<init>(MainContainer.java:86)
	at rsstats.data.ExtendedPlayer.<init>(ExtendedPlayer.java:119)
	at rsstats.data.ExtendedPlayer.register(ExtendedPlayer.java:128)
	at rsstats.common.event.ModEventHandler.onEntityConstructing(ModEventHandler.java:40)
	at cpw.mods.fml.common.eventhandler.ASMEventHandler_10_ModEventHandler_onEntityConstructing_EntityConstructing.invoke(.dynamic)
	at cpw.mods.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:54)
	at cpw.mods.fml.common.eventhandler.EventBus.post(EventBus.java:140)
	at net.minecraft.entity.Entity.<init>(Entity.java:224)
	at net.minecraft.entity.EntityLivingBase.<init>(EntityLivingBase.java:155)
	at net.minecraft.entity.player.EntityPlayer.<init>(EntityPlayer.java:165)
	at net.minecraft.entity.player.EntityPlayerMP.<init>(EntityPlayerMP.java:158)
	at net.minecraft.server.management.ServerConfigurationManager.createPlayerForUser(ServerConfigurationManager.java:443)
	at net.minecraft.server.network.NetHandlerLoginServer.func_147326_c(NetHandlerLoginServer.java:105)
	at net.minecraft.server.network.NetHandlerLoginServer.onNetworkTick(NetHandlerLoginServer.java:64)
	at net.minecraft.network.NetworkManager.processReceivedPackets(NetworkManager.java:244)

-- Ticking connection --
Details:
	Connection: net.minecraft.network.NetworkManager@448a3ead
Stacktrace:
	at net.minecraft.network.NetworkSystem.networkTick(NetworkSystem.java:182)
	at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:726)
	at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:614)
	at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:118)
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:485)
	at net.minecraft.server.MinecraftServer$2.run(MinecraftServer.java:752)

-- System Details --
Details:
	Minecraft Version: 1.7.10
	Operating System: Windows 10 (amd64) version 10.0
	Java Version: 1.8.0_131, Oracle Corporation
	Java VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation
	Memory: 706855112 bytes (674 MB) / 1038876672 bytes (990 MB) up to 1038876672 bytes (990 MB)
	JVM Flags: 3 total; -Xincgc -Xmx1024M -Xms1024M
	AABB Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used
	IntCache: cache: 1, tcache: 1, allocated: 12, tallocated: 94
	FML: MCP v9.05 FML v7.10.99.99 Minecraft Forge 10.13.4.1558 5 mods loaded, 5 mods active
	States: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored
	UCHIJAAAA	mcp{9.05} [Minecraft Coder Pack] (minecraft.jar) 
	UCHIJAAAA	FML{7.10.99.99} [Forge Mod Loader] (forgeSrc-1.7.10-10.13.4.1558-1.7.10.jar) 
	UCHIJAAAA	Forge{10.13.4.1558} [Minecraft Forge] (forgeSrc-1.7.10-10.13.4.1558-1.7.10.jar) 
	UCHIJAAAA	rsstats{0.0.1a} [RSStats] (SavageWorldRP_main) 
	UCHIJAAAA	examplemod{1.0} [Example Mod] (modid-1.0.jar) 
	GL info: ~~ERROR~~ RuntimeException: No OpenGL context found in the current thread.
	Profiler Position: N/A (disabled)
	Vec3 Pool Size: 0 (0 bytes; 0 MB) allocated, 0 (0 bytes; 0 MB) used
	Player Count: 0 / 8; []
	Type: Integrated Server (map_client.txt)
	Is Modded: Definitely; Client brand changed to 'fml,forge'
[20:00:43] [Client thread/INFO] [STDOUT]: [net.minecraft.client.Minecraft:displayCrashReport:393]: #@!@# Game crashed! Crash report saved to: #@!@# .\crash-reports\crash-2018-12-27_20.00.43-server.txt
[20:00:43] [Client thread/INFO] [FML]: Waiting for the server to terminate/save.
[20:00:47] [Server thread/INFO]: Saving chunks for level 'Новый мир'/Nether
[20:00:47] [Server thread/INFO]: Saving chunks for level 'Новый мир'/The End
[20:00:51] [Server thread/INFO] [FML]: Unloading dimension 0
[20:00:51] [Server thread/INFO] [FML]: Unloading dimension -1
[20:00:51] [Server thread/INFO] [FML]: Unloading dimension 1
[20:00:51] [Server thread/INFO] [FML]: Applying holder lookups
[20:00:51] [Server thread/INFO] [FML]: Holder lookups applied
[20:00:51] [Server thread/INFO] [FML]: The state engine was in incorrect state SERVER_STOPPING and forced into state SERVER_STOPPED. Errors may have been discarded.
[20:00:51] [Client thread/INFO] [FML]: Server terminated.
Disconnected from the target VM, address: '127.0.0.1:60996', transport: 'socket'
AL lib: (EE) alc_cleanup: 1 device not closed
Java HotSpot(TM) 64-Bit Server VM warning: Using incremental CMS is deprecated and will likely be removed in a future release

Process finished with exit code -1
3,005
192
592
Начнем с того, что зачем апать через 1.5 часа...
По поиску "new EntityPlayerMP" - пусто, значит ты даже этот класс не скинул.
rsstats.common.event.ModEventHandler.onEntityConstructing(ModEventHandler.java:40)
Возможно, потому что ты не проверяешь, что там именно сервер игрок.
+ ты можешь передавать не ExtendedPlayer, а самого игрока, краша с null'ом не должно быть.
 
1,159
38
544
Начнем с того, что зачем апать через 1.5 часа...
Да, не стоило. Просто хотел поскорее получить помощь.

По поиску "new EntityPlayerMP" - пусто, значит ты даже этот класс не скинул.
Да ты должно быть шутишь. Версия 1.7.10, смотри пакет net.minecraft.entity.player и ServerConfigurationManager.

Возможно, потому что ты не проверяешь, что там именно сервер игрок.
Я разобрался. Дело в том, что MainContainer создается в конструкторе ExtendedPlayer, который в свою очередь вызывается во время регистрации IEEP евенте EntityConstructing. Так прикол в том, что MainContainer пытается использовать поле EntityPlayer#inventory, которое еще не было инициализировано, т.к. EntityConstructing посывается из конструктора Entity, а не EntityPlayer. Я решил эту проблему, инициализирую контейнер в евенте EntityJoinWorldEvent.

Т.е. по большому счету EntityPlayerMP создался и != null. Просто меня смутила надпись в IDEA.

Однако вопрос о том, можно ли постоянно хранить экземпляр MainContainer'а все равно открыт
 
1,159
38
544
Однако вопрос о том, можно ли постоянно хранить экземпляр MainContainer'а все равно открыт
Пришел к выводу, что это бессмысленно. Потому как даже если вызывать его detectAndSendChanges() каждый тик, то придется еще и убеждаться в том, что объекты, реализующие ICrafting смогут послать верные данные на клиент (а если инвентари контейнера несколько специфичные, то это почти всегда обозначает необходимость юзания хука). В итоге, мне кажется что это - плохая идея.
 
Последнее редактирование:
Сверху