- Версия(и) Minecraft
- 1.12.2
Блок со своим интерфейсом (2 часть). Создание своей печки
Приветствую. Это 2 часть туториала по созданию блока с интерфейсом. В рамках этого туториала мы будем создавать свою печь, у которой будет наш интерфейс и наши рецепты, сама печь будет полностью настраиваться. Если вы не читали 1 часть то советую сейчас её прочитать, чтобы примерно понимать что и как.
1. Создание TileEntity печи
Создадим класс TileEntitySimpleFurnace, это будет классом тайл-энтити нашей печи:
В основном код был взят с тайл-энтити ванильной печи, однако я оставил комментарии почти под каждой строкой, чтобы вы смогли понять, какие переменные нужно менять или какие нужно добавить для своей печки.
Теперь создаём класс регистрации нашего тайл-энтити (его мы будем вызывать при регистрации. Логично):
Для слотов выхода (так как это не обычное хранилище) мы должны создать наши собственные слоты, которые будут являться выходом и топливом нашей печки. (классы наших слотов мы будем использовать для добавления слотов в рендере интерфейса печки):
И класс слота топлива:
Объяснять я думаю, что они делают не стоит. Просто наследуем обычный слот и добавляем некоторые параметры для выходного слота (слот горения почти не отличается от обычного)
А теперь, наконец, создадим сам класс блока нашей печи, под названием "BlockSimpleFurnace":
[
Теперь создаём класс регистрации нашего нашего гуи, он аналогичен тому, что был в 1 части туториала.
2. Настройка слотов печи / Добавление своих рецептов
Создадим класс ContainerSimpleFurnace, где мы сможем настроить расположение слотов на интерфейсе:
А также классы GuiSimpleFurnace, где мы сможем настроить расположение модели огня (или других текстур, например как в industrial craft) и прогресс-бара готовки. И SimpleRecipes, где мы как раз таки и сможем добавить свои рецепты для печки:
Вот эти строки отвечают за добавление рецептов:
Для теста советую сначала добавить простой рецепт (третий). Далее вы сможете добавить рецепты под ваши нужды.
P.S. Совсем забыл об этом сказать, поэтому добавляю это когда уже написал и выложил туториал: третий параметр отвечает за получение определенного количества опыта за один переплавленный предмет/блок. Вы можете своё количество за рецепт.
Регистрируем необходимые классы.
Тайл энтити мы регистрируем в CommonProxy (preInit):
А саму гуишку в CommonProxy в init:
3. Проверка готовой печи
Запускаем игру и выдаём себе нашу печь командой: "/give (ваш ник) bwioit:simple_furnace",
ставим, проверяем!
Как видим всё работает.
На этом 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",
ставим, проверяем!
Как видим всё работает.
На этом 2 часть туториала подошла к концу. Надеюсь я кому-то помог и вы смогли сделать свою собственную печь!