Блок со своим интерфейсом (2 часть)

Блок со своим интерфейсом (2 часть)

Нет прав для скачивания
Версия(и) Minecraft
1.12.2
Блок со своим интерфейсом (2 часть). Создание своей печки
Приветствую. Это 2 часть туториала по созданию блока с интерфейсом. В рамках этого туториала мы будем создавать свою печь, у которой будет наш интерфейс и наши рецепты, сама печь будет полностью настраиваться. Если вы не читали 1 часть то советую сейчас её прочитать, чтобы примерно понимать что и как.

1. Создание TileEntity печи
Создадим класс TileEntitySimpleFurnace, это будет классом тайл-энтити нашей печи:
TileEntitySimpleFurnace.java:
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.inventory.ItemStackHelper;
import net.minecraft.inventory.SlotFurnaceFuel;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemHoe;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraft.item.ItemTool;
import net.minecraft.item.crafting.FurnaceRecipes;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ITickable;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

public class TileEntitySimpleFurnace extends TileEntity implements IInventory, ITickable, ISidedInventory
{
    // Массив слотов сверху (два слота)
    private static final int[] SLOTS_TOP = new int[] {0,1};
    // Массив слотов снизу (два слота)
    private static final int[] SLOTS_BOTTOM = new int[] {3,4};
    // Массив слотов на сторонах (один слот)
    private static final int[] SLOTS_SIDES = new int[] {2};
 
    // Лист из количества слотов, в которых по умолчанию будет находиться ItemStack.EMPTY
    // 6 - количество всех слотов в нашей печке
    private NonNullList<ItemStack> inventory = NonNullList.<ItemStack>withSize(6, ItemStack.EMPTY);
    private String customName;
 
    /*
       Эти переменные необходимы для нашей печи:
       burnTime - переменная времени горения, в неё мы будем записывать время горения предмета,
       currentBurnTime - переменная времени горения в настоящий момент,
       Дальше немного сложнее: Я создал по две переменных cookTime и totalCookTime (тотальное время готовки, если время готовки предмета будет больше или равно этой переменной - предмет приготовится) так как у меня будет по одному слоту для ингредиента для пережарки, и по два слота для выхода того, во что этот предмет пожариться.
    */
    private int burnTime;
    private int currentBurnTime;
    private int cookTime;
    private int cookTime2;
    private int totalCookTime;
    private int totalCookTime2;
 
    @Override
    public String getName()
    {
        /* Получает значение того, есть ли тайла нашей печки кастомное имя,
            1. Если есть - получает это имя,
            2. Если нет - получает стандартное имя, равное "container.simple_furnace".
        */
        return this.hasCustomName() ? this.customName : "container.simple_furnace";
    }
 
    @Override
    public boolean hasCustomName()
    {
        // Сам метод hasCustomName. Проверяет что переменная кастомного имени не равна нулу и не пустое ли оно
        return this.customName != null && !this.customName.isEmpty();
    }
 
    // Этот метод будет использоваться для установки кастомного имени нашего тайл-энтити печи
    public void setCustomName(String customName)
    {
        this.customName = customName;
    }
 
    @Override
    public ITextComponent getDisplayName()
    {
        /*
           Получает компонент имени тайл-энтити если hasCustomName вернёт true,
           и получение переведённого имени если hasCustomName вернёт false
        */
        return this.hasCustomName() ? new TextComponentString(this.getName()) : new TextComponentTranslation(this.getName());
    }
 
    @Override
    public int getSizeInventory()
    {
        // Получение размера инвентаря тайл-энтити печки
        return this.inventory.size();
    }
 
    @Override
    public boolean isEmpty()
    {
        // Цикл, благодаря которому метод проверяет стак в инвентаре тайл-энтити на то, что он не пустой
        for (ItemStack stack : this.inventory)
        {
            if (!stack.isEmpty()) return false;
        }
        return true;
    }
 
    @Override
    public ItemStack getStackInSlot(int index)
    {
        // Получает предмет в тайл-энтити по его индексу
        return (ItemStack)this.inventory.get(index);
    }
 
    @Override
    public ItemStack decrStackSize(int index, int count)
    {
        // Мы используем вспомогательный класс ItemStackHelper для высчитывания размера стака из количества и индекса
        return ItemStackHelper.getAndSplit(this.inventory, index, count);
    }
 
    @Override
    public ItemStack removeStackFromSlot(int index)
    {
        // Мы используем вспомогательный класс ItemStackHelper для получения и удаления предмета в нашем тайл-энтити по индексу.
        return ItemStackHelper.getAndRemove(this.inventory, index);
    }
 
    @Override
    // Данный метод будет использоваться для того, чтобы положить приготовленный предмет в соответствующий слот если он не полон
    public void setInventorySlotContents(int index, ItemStack stack)
    {
        // Получаем стак из инвентаря тайл-энтити по индексу, который дан в аргументах метода
        ItemStack itemstack = (ItemStack)this.inventory.get(index);
        // Создаём булеан flag в которую записываем проверку на то, что стак не пустой и предмет в переменной itemstack равняется предмету из аргументов метода
        boolean flag = !stack.isEmpty() && stack.isItemEqual(itemstack) && ItemStack.areItemStackTagsEqual(stack, itemstack);
        // Устанавливаем стак в ячейку, которую мы получили по индексу в нашем тайл-энтити
        this.inventory.set(index, stack);
     
        // Проверка, что количество предметов в стаке больше значения 64 (это значение возвращает метод getInventoryStackLimit)
        if(stack.getCount() > this.getInventoryStackLimit())
            // Устанавливает количество предметов в стаке - 64
            stack.setCount(this.getInventoryStackLimit());
        // Если переданный индекс равен нулю и наша переменная flag равна false
        if(index == 0 && !flag)
        {
            // Устанавливаем время готовки предмета в нашу переменную максимального времени готовки предмета
            this.totalCookTime = this.getCookTime(stack);
            // Устанавливаем переменную готовки на 0
            this.cookTime = 0;
            // Он даёт игре понять что данные были изменены с момента последнего сохранения и их нужно перезаписать.
            this.markDirty();
         
        } else if (index == 1 && !flag)
        {
            this.totalCookTime2 = this.getCookTime(stack);
            this.cookTime2 = 0;
            this.markDirty();
        }
    }
 
    @Override
    public void readFromNBT(NBTTagCompound compound)
    {
        super.readFromNBT(compound);
     
        // Переписанный ванильный метод сохранения значений тайл-энтити на диск в нбт, здесь вам нужно будет только изменить название переменных (таких как BurnTime, CookTime, CookTime2, CookTimeTotal, TotalCookTime2) или заменить/добавить своих.
        this.inventory = NonNullList.<ItemStack>withSize(this.getSizeInventory(), ItemStack.EMPTY);
        ItemStackHelper.loadAllItems(compound, this.inventory);
        this.burnTime = compound.getInteger("BurnTime");
        this.cookTime = compound.getInteger("CookTime");
        this.totalCookTime = compound.getInteger("CookTimeTotal");
        this.cookTime2 = compound.getInteger("CookTime2");
        this.totalCookTime2 = compound.getInteger("TotalCookTime2");
        this.currentBurnTime = getItemBurnTime((ItemStack)this.inventory.get(2));
     
        if(compound.hasKey("CustomName", 8)) this.setCustomName(compound.getString("CustomName"));
    }
 
    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound compound)
    {
        super.writeToNBT(compound);
     
        // Чтение с диска. Аналогично записи, замените здесь названия на свои если будет нужно
        compound.setInteger("BurnTime", (short)this.burnTime);
        compound.setInteger("CookTime", (short)this.cookTime);
        compound.setInteger("CookTimeTotal", (short)this.totalCookTime);
        compound.setInteger("CookTime2", (short)this.cookTime2);
        compound.setInteger("TotalCookTime2", (short)this.totalCookTime2);
        ItemStackHelper.saveAllItems(compound, this.inventory);
     
        if(this.hasCustomName()) compound.setString("CustomName", this.customName);
        return compound;
    }
 
    @Override
    public int getInventoryStackLimit()
    {
        return 64;
    }
 
    public boolean isBurning()
    {
        // Проверяем что предмет на стадии готовки.
        // в этом методе мы возвращаем true или false, если время горения больше нуля
        return this.burnTime > 0;
    }
 
    @SideOnly(Side.CLIENT)
    public static boolean isBurning(IInventory inventory)
    {
        // Проверяет что предмет на стадии готовки на стороне клиента.
        // возвращает тоже самое, что и предыдущий метод, только получает переменную burnTime с помощью метода, который мы рассмотрим позже
        return inventory.getField(0) > 0;
    }
 
    public void update()
    {
        // Создаём булеан значение с значением проверки на горение
        boolean flag = this.isBurning();
        // Булеан flag1 будет всегда равно false пока мы не изменим его далее.
        boolean flag1 = false;
     
        if(this.isBurning()) {
            // Метод (рассмотрим позже), который проверяет может ли быть переплавлены предметы, индексы которых мы дали
            if(this.canSmelt(0, 3) && this.canSmelt(1, 4))
            {
                // Уменьшаем время горения на 2 если предмет можно пожарить. Если нет - уменьшаем переменную burnTime на единицу каждый раз
                this.burnTime -= 2;
            } else --this.burnTime;
        }
        // Проверка на серверный мир
        if(!this.world.isRemote) {
            // Получение предмета топлива из соответствующего слота под индексом 2 (в моём случае)
            ItemStack fuel = (ItemStack)this.inventory.get(2);
            // Проверка что слоты с топливом и предметами пережарки не пустые и что печь на стадии горения
            if(this.isBurning() || (!fuel.isEmpty() && !(((ItemStack)this.inventory.get(0)).isEmpty())) || (!fuel.isEmpty() && !(((ItemStack)this.inventory.get(1)).isEmpty())))
            {
                // Ещё раз проверяем что печь на стадии горения а также проверяем - можно ли переплавить предметы, что лежат в каждом из наших слотов
                if((!this.isBurning() && this.canSmelt(0, 3)) || (!this.isBurning() && this.canSmelt(1, 4)) || (!this.isBurning() && this.canSmelt(0, 3) && this.canSmelt(1, 4)))
                {
                    // Устанавливаем нашей переменной времени горения топлива - время горения определенного топлива, которое мы используем (метод getItemBurnTime рассмотрим позже)
                    this.burnTime = getItemBurnTime(fuel);
                    // Устанавливаем нашей переменной текущего времени горения - время горения
                    this.currentBurnTime = this.burnTime;
                 
                    if(this.isBurning())
                    {
                        // Если печь горит - меняем значение flag1 на true
                        flag1 = true;
                        // Проверяем что слот топлива не пустой.
                        if(!fuel.isEmpty())
                        {
                            // Если не пустой - удаляем по одному предмету топлива
                            Item item = fuel.getItem();
                            fuel.shrink(1);
                     
                            if(fuel.isEmpty())
                            {
                                // Если слот топлива пустой
                                ItemStack item1 = item.getContainerItem(fuel);
                                this.inventory.set(2, item1);
                            }
                        }
                    }
                }
         
                // Проверка что предметы в первых слотах для готовки можно переплавить
                if(this.isBurning() && this.canSmelt(0, 3))
                {
                    // Если печь горит, то каждый раз мы будем прибавлять значению готовки - единицу
                    ++this.cookTime;
                 
                    // Если значение готовки равно общему значению готовки предмета
                    if(this.cookTime == this.totalCookTime)
                    {
                        this.cookTime = 0;
                        this.totalCookTime = this.getCookTime((ItemStack)this.inventory.get(0));
                        // Переплавляем/готовим предмет
                        this.smeltItem(0, 3);
                        flag1 = true;
                    }
                } else {
                    this.cookTime = 0;
                }
                // Проверка что предметы во вторых слотах для готовки можно переплавить
                if (this.isBurning() && this.canSmelt(1, 4))
                {
                    ++this.cookTime2;
                 
                    if(this.cookTime2 == this.totalCookTime2)
                    {
                        this.cookTime2 = 0;
                        this.totalCookTime2 = this.getCookTime((ItemStack)this.inventory.get(1));
                        this.smeltItem(1, 4);
                        flag1 = true;
                     
                    }
                } else {
                    this.cookTime2 = 0;
                }
         
            } else if (!this.isBurning() && this.cookTime > 0)
            {
                this.cookTime = MathHelper.clamp(this.cookTime - 2, 0, this.totalCookTime);
            } else if (!this.isBurning() && this.cookTime2 > 0)
            {
                this.cookTime2 = MathHelper.clamp(this.cookTime2 - 2, 0, this.totalCookTime2);
            }
            if(flag != this.isBurning())
            {
                flag = true;
             
                // Устанавливаем стейт тайл-энтити печки, что она горит (это будет видно в мире)
                BlockSimpleFurnace.setState(this.isBurning(), this.world, this.pos);
            }
        }
        // каждый раз когда переменная flag1 равна true - обновляем
        if (flag1) this.markDirty();
    }
 
    public int getCookTime(ItemStack input1)
    {
        // Возвращает стандартное (самое минимальное) время горения
        return 200;
    }
 
    private boolean canSmelt(int inputSlot, int outputSlot)
    {
        // Проверяет что слот с предметом для готовки не пустой
        if (((ItemStack)this.inventory.get(inputSlot)).isEmpty()) return false;
        else
        {
            // Получает ожидаемый результат крафта из класса с нашими рецептами (рассмотрим его позже)
            ItemStack result = SimpleRecipes.getInstance().getSmeltingResult((ItemStack) this.inventory.get(inputSlot));
            if(result.isEmpty()) return false;
            else {
                ItemStack output = (ItemStack)this.inventory.get(outputSlot);
                if(output.isEmpty()) return true;
                if(!output.isItemEqual(result)) return false;
                int res = output.getCount() + result.getCount();
             
                return res <= getInventoryStackLimit() && res <= output.getMaxStackSize();
            }
        }
    }
 
    public void smeltItem(int inputSlot, int outputSlot)
    {
        // Проверяем что предмет в слоте готовки может быть переплавлен
        if(this.canSmelt(inputSlot, outputSlot))
        {
            // Получаем предмет в слоте готовки (который мы жарим)
            ItemStack input = (ItemStack)this.inventory.get(inputSlot);
            // Получаем ожидаемый предмет результата крафта (из нашего класса со своими крафтами)
            ItemStack result = SimpleRecipes.getInstance().getSmeltingResult(input);
            // Получаем выходной слот а также предмет в нём
            ItemStack output = (ItemStack)this.inventory.get(outputSlot);
         
            // Устанавливаем выходному слоту предмет, который должен получиться из предмета для переплавки и увеличиваем на один, если такой предмет там уже лежит.
            if(output.isEmpty()) this.inventory.set(outputSlot, result.copy());
            else if(output.getItem() == result.getItem()) output.grow(result.getCount());
            // Уменьшаем на 1 единицу предмет в слоте готовки
            input.shrink(1);
        }
    }
 
    @SuppressWarnings("deprecation")
    public static int getItemBurnTime(ItemStack fuel)
    {
        if(fuel.isEmpty()) return 0;
        else
        {
            Item item = fuel.getItem();

            // Возвращаем время горения для каждого из поддерживаемых нашей печкой блоков (ванильное)
            if (item instanceof ItemBlock && Block.getBlockFromItem(item) != Blocks.AIR)
            {
                Block block = Block.getBlockFromItem(item);

                if (block == Blocks.WOODEN_SLAB) return 150;
                if (block.getDefaultState().getMaterial() == Material.WOOD) return 300;
                if (block == Blocks.COAL_BLOCK) return 16000;
            }

                        // Возвращаем время горения для каждого из поддерживаемых нашей печкой предметов (ванильное)
            if (item instanceof ItemTool && "WOOD".equals(((ItemTool)item).getToolMaterialName())) return 200;
            if (item instanceof ItemSword && "WOOD".equals(((ItemSword)item).getToolMaterialName())) return 200;
            if (item instanceof ItemHoe && "WOOD".equals(((ItemHoe)item).getMaterialName())) return 200;
            if (item == Items.STICK) return 100;
            if (item == Items.COAL) return 1600;
            if (item == Items.LAVA_BUCKET) return 20000;
            if (item == Item.getItemFromBlock(Blocks.SAPLING)) return 100;
            if (item == Items.BLAZE_ROD) return 2400;

            return GameRegistry.getFuelValue(fuel);
        }
    }
     
    public static boolean isItemFuel(ItemStack fuel)
    {
        // Проверка, является ли предмет топливом.
        // (возвращает true - если время горения топлива больше нуля)
        return getItemBurnTime(fuel) > 0;
    }
 
    // Метод, который проверят: является ли тайл энтити нужным нам тайл энтити
    // и если нет, то метод возвращает false
    // если же является - то мы проверяем что расстояние между нашим хранилищем (тайл энтити)
    // не больше/равно 64 - и возвращаем true.
    public boolean isUsableByPlayer(EntityPlayer player)
    {
        return this.world.getTileEntity(this.pos) != this ? false : player.getDistanceSq((double)this.pos.getX() + 0.5D, (double)this.pos.getY() + 0.5D, (double)this.pos.getZ() + 0.5D) <= 64.0D;
    }
 
    @Override
    public void openInventory(EntityPlayer player) {}
 
    @Override
    public void closeInventory(EntityPlayer player) {}
 
    @Override
    public boolean isItemValidForSlot(int index, ItemStack stack)
    {
        if(index == 3) return false;
        else if(index != 2) return true;
        else return isItemFuel(stack);
    }
 
    public String getGuiI() {
        return "tm:advanced_furnace";
    }

    // Метод получения серверных переменных для клиента (который мы использовали выше)
    public int getField(int id)
    {
        // Здесь я использовал свитч, чтобы вернуть каждую переменную через свой уникальный айди, который мы должны будем передать в методе.
        switch(id) {
        case 0:
            return this.burnTime;
        case 1:
            return this.currentBurnTime;
        case 2:
            return this.cookTime;
        case 3:
            return this.totalCookTime;
        case 4:
            return this.cookTime2;
        case 5:
            return this.totalCookTime2;
        default:
            return 0;
        }
    }

    // Метод установки значения в серверные переменные (для клиента)
    public void setField(int id, int value)
    {
        switch(id) {
        case 0:
            this.burnTime = value;
            break;
        case 1:
            this.currentBurnTime = value;
            break;
        case 2:
            this.cookTime = value;
            break;
        case 3:
            this.totalCookTime = value;
            break;
        case 4:
            this.cookTime2 = value;
            break;
        case 5:
            this.totalCookTime2 = value;
        }
    }
 
    @Override
    public int getFieldCount()
    {
        // Здесь вы должны вернуть общее количество элементов switch (0,1,2,3,4,5)
        // так как у меня их 6 - возвращаем 6
        return 6;
    }
 
    @Override
    public void clear()
    {
        // метод очистки всего инвентаря, пригодится когда нам потребуется очистить инвентарь перед выбросом предметов в мир (если печку сломают)
        this.inventory.clear();
    }
 
    public boolean isItemValid(int index, ItemStack stack)
    {
        if(index == 3 || index == 4 || index == 5)
        {
            return false;
        } else if(index == 0 || index == 1)
        {
            return true;
        } else
        {
            ItemStack itemstack = this.inventory.get(1);
            return isItemFuel(stack) || SlotFurnaceFuel.isBucket(stack) && itemstack.getItem() != Items.BUCKET;
        }
    }

    @Override
    public int[] getSlotsForFace(EnumFacing side)
    {
        if (side == EnumFacing.DOWN)
        {
            return SLOTS_BOTTOM;
        }
        else
        {
            return side == EnumFacing.UP ? SLOTS_TOP : SLOTS_SIDES;
        }
    }

    @Override
    public boolean canInsertItem(int index, ItemStack stack, EnumFacing direction)
    {
        return this.isItemValidForSlot(index, stack);
    }

    @Override
    // Этот метод (а также другие два метода такие как canInsertItem) позволит нам загружать в нашу печь предметы воронкой.
    public boolean canExtractItem(int index, ItemStack stack, EnumFacing direction)
    {
        if (direction == EnumFacing.DOWN && index == 1)
        {
            Item item = stack.getItem();

            // Проверка, что предмет из воронки не равен воде или ведру воды. Только если вам не нужна жарить воду...
            if (item != Items.WATER_BUCKET && item != Items.BUCKET)
            {
                return false;
            }
        }

        return true;
    }
}
В основном код был взят с тайл-энтити ванильной печи, однако я оставил комментарии почти под каждой строкой, чтобы вы смогли понять, какие переменные нужно менять или какие нужно добавить для своей печки.

Теперь создаём класс регистрации нашего тайл-энтити (его мы будем вызывать при регистрации. Логично):
TileEntityHandler.java:
import guteemon.bwioit.Main;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.registry.GameRegistry;

public class TileEntityHandler
{
    public static void registerTileEntites()
    {
        GameRegistry.registerTileEntity(TileEntitySimpleFurnace.class, new ResourceLocation(Main.MODID + "simple_furnace"));
    }
}

Для слотов выхода (так как это не обычное хранилище) мы должны создать наши собственные слоты, которые будут являться выходом и топливом нашей печки. (классы наших слотов мы будем использовать для добавления слотов в рендере интерфейса печки):
SlotSimpleFurnaceOutput.java:
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;

public class SlotSimpleFurnaceOutput extends Slot
{
    @SuppressWarnings("unused")
    private final EntityPlayer player;
    private int removeCount;

    public SlotSimpleFurnaceOutput(EntityPlayer player, IInventory inventory, int index, int x, int y)
    {
        super(inventory, index, x, y);
        this.player = player;
    }
 
    @Override
    public boolean isItemValid(ItemStack stack)
    {
        return false;
    }
 
    @Override
    public ItemStack onTake(EntityPlayer thePlayer, ItemStack stack)
    {
        this.onCrafting(stack);
        super.onTake(thePlayer, stack);
        return stack;
    }
 
    @Override
    public ItemStack decrStackSize(int amount)
    {
        if(this.getHasStack()) this.removeCount += Math.min(amount, this.getStack().getCount());
        return super.decrStackSize(amount);
    }
}
И класс слота топлива:
SlotSimpleFurnaceFuel.java:
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;

public class SlotSimpleFurnaceFuel extends Slot
{
    public SlotSimpleFurnaceFuel(IInventory inventory, int index, int x, int y)
    {
        super(inventory, index, x, y);
    }
 
    @Override
    public boolean isItemValid(ItemStack stack)
    {
        return TileEntitySimpleFurnace.isItemFuel(stack);
    }

    @Override
    public int getItemStackLimit(ItemStack stack)
    {
        return super.getItemStackLimit(stack);
    }
}
Объяснять я думаю, что они делают не стоит. Просто наследуем обычный слот и добавляем некоторые параметры для выходного слота (слот горения почти не отличается от обычного)

А теперь, наконец, создадим сам класс блока нашей печи, под названием "BlockSimpleFurnace":
BlockSimpleFurnace.java:
import java.util.Random;

import guteemon.bwioit.BlocksRegister;
import guteemon.bwioit.Main;
import net.minecraft.block.Block;
import net.minecraft.block.BlockHorizontal;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.block.properties.IProperty;
import net.minecraft.block.properties.PropertyBool;
import net.minecraft.block.properties.PropertyDirection;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.InventoryHelper;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumBlockRenderType;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.Mirror;
import net.minecraft.util.Rotation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;

public class BlockSimpleFurnace extends Block implements ITileEntityProvider
{
    public static final PropertyDirection FACING = BlockHorizontal.FACING;
    public static final PropertyBool BURNING = PropertyBool.create("burning");

    public BlockSimpleFurnace(String name, Material material)
    {
        super(Material.IRON);
        this.setSoundType(SoundType.STONE);
        this.setHardness(1.5F);
        this.setResistance(10.0F);
        this.setUnlocalizedName(name);
        this.setRegistryName(name);
        // Стандартный стейт печки - она не горит и повернута к северу
        this.setDefaultState(this.blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH).withProperty(BURNING, false));
    }

    @Override
    public Item getItemDropped(IBlockState state, Random rand, int fortune)
    {
        // Возвращаем блок печки, который будет выпадать если мы её сломаем
        return Item.getItemFromBlock(BlocksRegister.SIMPLE_FURNACE);
    }
 
    @Override
    public ItemStack getItem(World worldIn, BlockPos pos, IBlockState state)
    {
        // Возвращаем предмет из блока печки
        return new ItemStack(BlocksRegister.SIMPLE_FURNACE);
    }
 
    @Override
    public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ)
    {
        // Проверка на сервер
        if(!worldIn.isRemote)
        {
            // Открываем гуи печки
            playerIn.openGui(Main.INSTANCE, 0, worldIn, pos.getX(), pos.getY(), pos.getZ());
        }
        return true;
    }
 
    @Override
    public void onBlockAdded(World worldIn, BlockPos pos, IBlockState state)
    {
        // Проверка на сервер
        if(!worldIn.isRemote)
        {
            IBlockState north = worldIn.getBlockState(pos.north());
            IBlockState south = worldIn.getBlockState(pos.south());
            IBlockState west = worldIn.getBlockState(pos.west());
            IBlockState east = worldIn.getBlockState(pos.east());
            EnumFacing face = (EnumFacing)state.getValue(FACING);
         
            // Устанавливаем поворот печки при постановке в мир
            if (face == EnumFacing.NORTH && north.isFullBlock() && !south.isFullBlock()) face = EnumFacing.SOUTH;
            else if (face == EnumFacing.SOUTH && south.isFullBlock() && !north.isFullBlock()) face = EnumFacing.NORTH;
            else if (face == EnumFacing.WEST && west.isFullBlock() && !east.isFullBlock()) face = EnumFacing.EAST;
            else if (face == EnumFacing.EAST && east.isFullBlock() && !west.isFullBlock()) face = EnumFacing.WEST;
            worldIn.setBlockState(pos, state.withProperty(FACING, face), 2);
        }
    }
 
    public static void setState(boolean active, World worldIn, BlockPos pos)
    {
        IBlockState state = worldIn.getBlockState(pos);
        TileEntity tileentity = worldIn.getTileEntity(pos);
     
        if(active) worldIn.setBlockState(pos, BlocksRegister.SIMPLE_FURNACE.getDefaultState().withProperty(FACING, state.getValue(FACING)).withProperty(BURNING, true), 3);
        else worldIn.setBlockState(pos, BlocksRegister.SIMPLE_FURNACE.getDefaultState().withProperty(FACING, state.getValue(FACING)).withProperty(BURNING, false), 3);
     
        if(tileentity != null)
        {
            tileentity.validate();
            worldIn.setTileEntity(pos, tileentity);
        }
    }
 
    @Override
    public TileEntity createNewTileEntity(World worldIn, int meta)
    {
        return new TileEntitySimpleFurnace();
    }
 
    @Override
    public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer, EnumHand hand)
    {
        return this.getDefaultState().withProperty(FACING, placer.getHorizontalFacing().getOpposite());
    }
 
    @Override
    public void onBlockPlacedBy(World worldIn, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack)
    {
        worldIn.setBlockState(pos, this.getDefaultState().withProperty(FACING, placer.getHorizontalFacing().getOpposite()), 2);
    }
 
    @Override
    public void breakBlock(World worldIn, BlockPos pos, IBlockState state)
    {
        TileEntitySimpleFurnace tileentity = (TileEntitySimpleFurnace) worldIn.getTileEntity(pos);
        InventoryHelper.dropInventoryItems(worldIn, pos, tileentity);
        super.breakBlock(worldIn, pos, state);
    }
 
    @Override
    public EnumBlockRenderType getRenderType(IBlockState state)
    {
        return EnumBlockRenderType.MODEL;
    }
 
 
    @Override
    public IBlockState withRotation(IBlockState state, Rotation rot)
    {
        return state.withProperty(FACING, rot.rotate((EnumFacing)state.getValue(FACING)));
    }
 
    @Override
    public IBlockState withMirror(IBlockState state, Mirror mirrorIn)
    {
        return state.withRotation(mirrorIn.toRotation((EnumFacing)state.getValue(FACING)));
    }
 
    @Override
    protected BlockStateContainer createBlockState()
    {
        return new BlockStateContainer(this, new IProperty[] {BURNING, FACING});
    }
 
    @Override
    public IBlockState getStateFromMeta(int meta)
    {
        EnumFacing facing = EnumFacing.getFront(meta);
        if (facing.getAxis() == EnumFacing.Axis.Y) facing = EnumFacing.NORTH;
        return this.getDefaultState().withProperty(FACING, facing);
    }
 
    @Override
    public int getMetaFromState(IBlockState state)
    {
        return ((EnumFacing)state.getValue(FACING)).getIndex();
    }
 
    @Override
    public boolean canHarvestBlock(IBlockAccess world, BlockPos pos, EntityPlayer player)
    {
        return true;
    }
}
[

Теперь создаём класс регистрации нашего нашего гуи, он аналогичен тому, что был в 1 части туториала.
GuiHandler.java:
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.IGuiHandler;

public class GuiHandler implements IGuiHandler
{
    @Override
    public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        if(ID == 0)
        {
            return new ContainerSimpleFurnace(player.inventory, (TileEntitySimpleFurnace) world.getTileEntity(new BlockPos(x, y, z)));
        }
        return null;
    }

    @Override
    public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        if(ID == 0)
        {
            return new GuiSimpleFurnace(player.inventory, (TileEntitySimpleFurnace) world.getTileEntity(new BlockPos(x, y, z)));
        }
        return null;
    }
}

2. Настройка слотов печи / Добавление своих рецептов

Создадим класс ContainerSimpleFurnace, где мы сможем настроить расположение слотов на интерфейсе:
ContainerSimpleFurnace.java:
public class ContainerSimpleFurnace extends Container
{
    private final TileEntitySimpleFurnace tileentity;
    private int cookTime, totalCookTime, cookTime2, totalCookTime2, burnTime, currentBurnTime;
 
    public ContainerSimpleFurnace(InventoryPlayer player, TileEntitySimpleFurnace tileentity)
    {
        this.tileentity = tileentity;
     
        this.addSlotToContainer(new Slot(tileentity, 0, 56, 17));
        this.addSlotToContainer(new Slot(tileentity, 1, 83, 53));
        // Добавляем новый слот (наш), который будет являтся слотом топлива
        this.addSlotToContainer(new SlotSimpleFurnaceFuel(tileentity, 2, 56, 53));
        // Добавляем два слота для выходных (приготовленных) предметов. Для моей печки нужно будет сразу два
        this.addSlotToContainer(new SlotSimpleFurnaceOutput(player.player, tileentity, 3, 118, 21));
        this.addSlotToContainer(new SlotSimpleFurnaceOutput(player.player, tileentity, 4, 118, 53));
     
        // Инвентарь
        for(int y = 0; y < 3; y++)
        {
            for(int x = 0; x < 9; x++)
            {
                this.addSlotToContainer(new Slot(player, x + y * 9 + 9, 8 + x * 18, 84 + y * 18));
            }
        }
     
        // Хотбар
        for(int x = 0; x < 9; x++)
        {
            this.addSlotToContainer(new Slot(player, x, 8 + x * 18, 142));
        }
    }
 
    @Override
    public void addListener(IContainerListener listener)
    {
        super.addListener(listener);
        listener.sendAllWindowProperties(this, this.tileentity);
    }
 
    @Override
    public void detectAndSendChanges()
    {
        super.detectAndSendChanges();
     
        for(int i = 0; i < this.listeners.size(); ++i)
        {
            IContainerListener listener = (IContainerListener)this.listeners.get(i);
         
            if(this.cookTime != this.tileentity.getField(2)) listener.sendWindowProperty(this, 2, this.tileentity.getField(2));
            if(this.cookTime2 != this.tileentity.getField(4)) listener.sendWindowProperty(this, 4, this.tileentity.getField(4));
            if(this.burnTime != this.tileentity.getField(0)) listener.sendWindowProperty(this, 0, this.tileentity.getField(0));
            if(this.currentBurnTime != this.tileentity.getField(1)) listener.sendWindowProperty(this, 1, this.tileentity.getField(1));
            if(this.totalCookTime != this.tileentity.getField(3)) listener.sendWindowProperty(this, 3, this.tileentity.getField(3));
            if(this.totalCookTime2 != this.tileentity.getField(5)) listener.sendWindowProperty(this, 5, this.tileentity.getField(5));
        }
     
        this.cookTime = this.tileentity.getField(2);
        this.cookTime2 = this.tileentity.getField(4);
        this.burnTime = this.tileentity.getField(0);
        this.currentBurnTime = this.tileentity.getField(1);
        this.totalCookTime = this.tileentity.getField(3);
        this.totalCookTime2 = this.tileentity.getField(5);
    }
 
    @Override
    @SideOnly(Side.CLIENT)
    public void updateProgressBar(int id, int data)
    {
        this.tileentity.setField(id, data);
    }

    @Override
    public boolean canInteractWith(EntityPlayer playerIn)
    {
        return this.tileentity.isUsableByPlayer(playerIn);
    }
 
    @Override
    public ItemStack transferStackInSlot(EntityPlayer playerIn, int index)
    {
        ItemStack stack = ItemStack.EMPTY;
        Slot slot = (Slot)this.inventorySlots.get(index);
     
        if(slot != null && slot.getHasStack())
        {
            ItemStack stack1 = slot.getStack();
            stack = stack1.copy();
            if(index == 3 || index == 4)
            {
                if(!this.mergeItemStack(stack1, 5, 41, true)) return ItemStack.EMPTY;
                slot.onSlotChange(stack1, stack);
            } else if(index != 2 && index != 1 && index != 0)
            {            
                if(!SimpleRecipes.getInstance().getSmeltingResult(stack1).isEmpty())
                {
                    if(!this.mergeItemStack(stack1, 0, 2, false))
                    {
                        return ItemStack.EMPTY;
                    } else if(TileEntitySimpleFurnace.isItemFuel(stack1))
                    {
                     
                        if(!this.mergeItemStack(stack1, 2, 3, false)) return ItemStack.EMPTY;
                    } else if(index >= 5 && index < 32) {
                        if(!this.mergeItemStack(stack1, 32, 41, false)) return ItemStack.EMPTY;
                    } else if(index >= 32 && index < 41 && !this.mergeItemStack(stack1, 5, 32, false))
                    {
                        return ItemStack.EMPTY;
                    }
                }
            } else if(!this.mergeItemStack(stack1, 5, 41, false))
            {
                return ItemStack.EMPTY;
            }
            if(stack1.isEmpty())
            {
                slot.putStack(ItemStack.EMPTY);
            } else {
                slot.onSlotChanged();
            }
            if(stack1.getCount() == stack.getCount()) return ItemStack.EMPTY;
            slot.onTake(playerIn, stack1);
        }
        return stack;
    }
}

А также классы GuiSimpleFurnace, где мы сможем настроить расположение модели огня (или других текстур, например как в industrial craft) и прогресс-бара готовки. И SimpleRecipes, где мы как раз таки и сможем добавить свои рецепты для печки:
GuiSimpleFurnace:
public class GuiSimpleFurnace extends GuiContainer
{
    private static final ResourceLocation TEXTURES = new ResourceLocation(Main.MODID + ":textures/gui/simple_furnace.png");
 
    private final InventoryPlayer player;
    private final TileEntitySimpleFurnace tileentity;
 
    public GuiSimpleFurnace(InventoryPlayer player, TileEntitySimpleFurnace tileentity)
    {
        super(new ContainerSimpleFurnace(player, tileentity));
        this.player = player;
        this.tileentity = tileentity;
    }
 
    @Override
    protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY)
    {
        String tileName = this.tileentity.getDisplayName().getUnformattedText();
        this.fontRenderer.drawString(tileName, (this.xSize / 2 - this.fontRenderer.getStringWidth(tileName) / 2), 7, 4210752);
        this.fontRenderer.drawString(this.player.getDisplayName().getUnformattedText(), 8, this.ySize - 96 + 2, 4210752);
    }
 
    @Override
    protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY)
    {
        GlStateManager.color(1.0F, 1.0F, 1.0F);
     
        this.mc.getTextureManager().bindTexture(TEXTURES);
     
        this.drawTexturedModalRect(this.guiLeft, this.guiTop, 0, 0, this.xSize, this.ySize);
     
        if(TileEntitySimpleFurnace.isBurning(tileentity))
        {
            // 13 - ширина и высота модели огня
            int k = this.getBurnLeftScaled(13);
         
            // 57 - от левого края до огня
            // 37 - от верхней точки до огня
            this.drawTexturedModalRect(this.guiLeft + 57, this.guiTop + 37 + 12 - k, 176, 12 - k, 14, k + 1);
        }
     
        // 24 - ширина модели прогресс бара готовки
        int l = this.getCookProgressScaled(24, 2, 3);
     
        // 80 - от левого края до прогресс бара
        // 21 - от верхней точки до прогресс бара
        this.drawTexturedModalRect(this.guiLeft + 80, this.guiTop + 21, 176, 14, l + 1, 16);
    }
 
    private int getBurnLeftScaled(int pixels)
    {
        int i = this.tileentity.getField(1);
        if(i == 0) i = 200;
        return this.tileentity.getField(0) * pixels / i;
    }
 
    private int getCookProgressScaled(int pixels, int slot, int slot2)
    {
        int i = this.tileentity.getField(slot);
        int j = this.tileentity.getField(slot2);
        return j != 0 && i != 0 ? i * pixels / j : 0;
    }
}

SimpleRecipes.java:
public class SimpleRecipes
{
    private static final SimpleRecipes INSTANCE = new SimpleRecipes();
    private final Map<ItemStack, ItemStack> smeltingList = Maps.<ItemStack, ItemStack>newHashMap();
    private final Map<ItemStack, Float> experienceList = Maps.<ItemStack, Float>newHashMap();

    public static SimpleRecipes getInstance()
    {
        return INSTANCE;
    }

    private SimpleRecipes()
    {
        //this.addSmeltingRecipeForBlock(Blocks.NETHERRACK, new ItemStack(Items.NETHERBRICK), 0.1F);
        //this.addSmeltingRecipe(new ItemStack(Blocks.wate, 1, 1), new ItemStack(Blocks.SPONGE, 1, 0), 0.1F);
        this.addSmelting(Items.GLASS_BOTTLE, new ItemStack(Items.EXPERIENCE_BOTTLE), 0.1F);
    }

    public void addSmeltingRecipeForBlock(Block input, ItemStack stack, float experience)
    {
        this.addSmelting(Item.getItemFromBlock(input), stack, experience);
    }

    public void addSmelting(Item input, ItemStack stack, float experience)
    {
        this.addSmeltingRecipe(new ItemStack(input, 1, 32767), stack, experience);
    }

    public void addSmeltingRecipe(ItemStack input, ItemStack stack, float experience)
    {
        if (getSmeltingResult(input) != ItemStack.EMPTY) return;
        this.smeltingList.put(input, stack);
        this.experienceList.put(stack, Float.valueOf(experience));
    }

    public ItemStack getSmeltingResult(ItemStack stack)
    {
        for (Entry<ItemStack, ItemStack> entry : this.smeltingList.entrySet())
        {
            if (this.compareItemStacks(stack, entry.getKey()))
            {
                return entry.getValue();
            }
        }

        return ItemStack.EMPTY;
    }

    private boolean compareItemStacks(ItemStack stack1, ItemStack stack2)
    {
        return stack2.getItem() == stack1.getItem() && (stack2.getMetadata() == 32767 || stack2.getMetadata() == stack1.getMetadata());
    }

    public Map<ItemStack, ItemStack> getSmeltingList()
    {
        return this.smeltingList;
    }

    public float getSmeltingExperience(ItemStack stack)
    {
        float ret = stack.getItem().getSmeltingExperience(stack);
        if (ret != -1) return ret;

        for (Entry<ItemStack, Float> entry : this.experienceList.entrySet())
        {
            if (this.compareItemStacks(stack, entry.getKey()))
            {
                return ((Float)entry.getValue()).floatValue();
            }
        }

        return 0.0F;
    }
}
Вот эти строки отвечают за добавление рецептов:
Java:
// Добавление рецепта для предмета из блока (взято из ванили)
this.addSmeltingRecipeForBlock(Blocks.NETHERRACK, new ItemStack(Items.NETHERBRICK), 0.1F);

// Добавление рецепта для переплавки блока из предмета (взято из ванили)
this.addSmeltingRecipe(new ItemStack(Blocks.wate, 1, 1), new ItemStack(Blocks.SPONGE, 1, 0), 0.1F);

// Добавление простого рецепта
this.addSmelting(Items.GLASS_BOTTLE, new ItemStack(Items.EXPERIENCE_BOTTLE), 0.1F);
Для теста советую сначала добавить простой рецепт (третий). Далее вы сможете добавить рецепты под ваши нужды.
P.S. Совсем забыл об этом сказать, поэтому добавляю это когда уже написал и выложил туториал: третий параметр отвечает за получение определенного количества опыта за один переплавленный предмет/блок. Вы можете своё количество за рецепт.

Регистрируем необходимые классы.
Тайл энтити мы регистрируем в CommonProxy (preInit):
TileEntityHandler.registerTileEntites();

А саму гуишку в CommonProxy в init:
NetworkRegistry.INSTANCE.registerGuiHandler(Main.INSTANCE, new GuiHandler());


3. Проверка готовой печи

Запускаем игру и выдаём себе нашу печь командой: "/give (ваш ник) bwioit:simple_furnace",
ставим, проверяем!
screenshot.268.jpg
screenshot.269.jpg
screenshot.270.jpg

Как видим всё работает.

На этом 2 часть туториала подошла к концу. Надеюсь я кому-то помог и вы смогли сделать свою собственную печь!
Автор
sk9zist :l
Скачивания
9
Просмотры
2,732
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок

Другие ресурсы пользователя sk9zist :l

Последние обновления

  1. Переработка туториала

    Была переработана половина туториала.
Назад
Сверху