Hi, Im trying to make my own custom furnace and I found a git with a custom furnace with a guide to make it https://github.com/TheGreyGhost/MinecraftByExample/tree/master/src/main/java/minecraftbyexample/mbe31_inventory_furnace
These are my classes
BlockInventoryFurnace
package com.lethalmap.stardewmod.common.furnace;
import com.lethalmap.stardewmod.Constants;
import net.minecraft.block.Block;
import net.minecraft.block.BlockRenderType;
import net.minecraft.block.BlockState;
import net.minecraft.block.ContainerBlock;
import net.minecraft.block.material.Material;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.inventory.container.INamedContainerProvider;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraftforge.fml.network.NetworkHooks;
import javax.annotation.Nullable;
public class BlockInventoryFurnace extends ContainerBlock {
public BlockInventoryFurnace() {
super(Block.Properties.create(Material.ROCK));
BlockState defaultBlockState = this.stateContainer.getBaseState().with(BURNING_SIDES_COUNT, 0);
this.setDefaultState(defaultBlockState);
setRegistryName(Constants.MODID, Constants.FURNACE);
}
// --- The block changes its appearance depending on how many of the furnace slots have burning fuel in them
// In order to do that, we add a blockstate for each state (0 -> 4), each with a corresponding model. We also change the blockLight emitted.
final static int MAX_NUMBER_OF_BURNING_SIDES = 4;
public static final IntegerProperty BURNING_SIDES_COUNT =
IntegerProperty.create("burning_sides_count", 0, MAX_NUMBER_OF_BURNING_SIDES);
protected void fillStateContainer(StateContainer.Builder<Block, BlockState> builder) {
builder.add(BURNING_SIDES_COUNT);
}
// change the furnace emitted light ("block light") depending on how many slots are burning
private static final int ALL_SIDES_LIGHT_VALUE = 15; // light value for four sides burning
private static final int ONE_SIDE_LIGHT_VALUE = 8; // light value for a single side burning
/**
* Amount of block light emitted by the furnace
*/
public int getLightValue(BlockState state) {
int lightValue = 0;
Integer burningSidesCount = state.get(BURNING_SIDES_COUNT);
if (burningSidesCount == 0) {
lightValue = 0;
} else {
// linearly interpolate the light value depending on how many slots are burning
lightValue = ONE_SIDE_LIGHT_VALUE +
(ALL_SIDES_LIGHT_VALUE - ONE_SIDE_LIGHT_VALUE) * burningSidesCount / (MAX_NUMBER_OF_BURNING_SIDES - 1);
}
lightValue = MathHelper.clamp(lightValue, 0, ALL_SIDES_LIGHT_VALUE);
return lightValue;
}
// ---------------------
/**
* Create the Tile Entity for this block.
* Forge has a default but I've included it anyway for clarity
*
* @return
*/
@Override
public TileEntity createTileEntity(BlockState state, IBlockReader world) {
return createNewTileEntity(world);
}
@Nullable
@Override
public TileEntity createNewTileEntity(IBlockReader worldIn) {
return new TileEntityFurnace();
}
// not needed if your block implements ITileEntityProvider (in this case implemented by BlockContainer), but it
// doesn't hurt to include it anyway...
@Override
public boolean hasTileEntity(BlockState state) {
return true;
}
// Called when the block is right clicked
// In this block it is used to open the block gui when right clicked by a player
public ActionResultType onBlockActivated(BlockState state, World worldIn, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult) {
if (worldIn.isRemote) return ActionResultType.SUCCESS; // on client side, don't do anything
INamedContainerProvider namedContainerProvider = this.getContainer(state, worldIn, pos);
if (namedContainerProvider != null) {
if (!(player instanceof ServerPlayerEntity))
return ActionResultType.FAIL; // should always be true, but just in case...
ServerPlayerEntity serverPlayerEntity = (ServerPlayerEntity) player;
NetworkHooks.openGui(serverPlayerEntity, namedContainerProvider, (packetBuffer) -> {
});
// (packetBuffer)->{} is just a do-nothing because we have no extra data to send
}
return ActionResultType.SUCCESS;
}
// This is where you can do something when the block is broken. In this case drop the inventory's contents
// Code is copied directly from vanilla eg ChestBlock, CampfireBlock
@Override
public void onReplaced(BlockState state, World world, BlockPos blockPos, BlockState newState, boolean isMoving) {
if (state.getBlock() != newState.getBlock()) {
TileEntity tileentity = world.getTileEntity(blockPos);
if (tileentity instanceof TileEntityFurnace) {
TileEntityFurnace tileEntityFurnace = (TileEntityFurnace) tileentity;
tileEntityFurnace.dropAllContents(world, blockPos);
}
// worldIn.updateComparatorOutputLevel(pos, this); if the inventory is used to set redstone power for comparators
super.onReplaced(state, world, blockPos, newState, isMoving); // call it last, because it removes the TileEntity
}
}
//------------------------------------------------------------
// The code below isn't necessary for illustrating the Inventory Furnace concepts, it's just used for rendering.
// For more background information see MBE03
// render using a BakedModel
// required because the default (super method) is INVISIBLE for BlockContainers.
@Override
public BlockRenderType getRenderType(BlockState iBlockState) {
return BlockRenderType.MODEL;
}
}
ContainerFurnace
package com.lethalmap.stardewmod.common.furnace;
import com.lethalmap.stardewmod.common.tiles.TileEntityList;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.container.Container;
import net.minecraft.inventory.container.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
public class ContainerFurnace extends Container {
public static ContainerFurnace createContainerServerSide(int windowID, PlayerInventory playerInventory,
FurnaceZoneContents inputZoneContents,
FurnaceZoneContents outputZoneContents,
FurnaceZoneContents fuelZoneContents,
FurnaceStateData furnaceStateData) {
return new ContainerFurnace(windowID, playerInventory,
inputZoneContents, outputZoneContents, fuelZoneContents, furnaceStateData);
}
public static ContainerFurnace createContainerClientSide(int windowID, PlayerInventory playerInventory, net.minecraft.network.PacketBuffer extraData) {
// don't need extraData for this example; if you want you can use it to provide extra information from the server, that you can use
// when creating the client container
// eg String detailedDescription = extraData.readString(128);
FurnaceZoneContents inputZoneContents = FurnaceZoneContents.createForClientSideContainer(INPUT_SLOTS_COUNT);
FurnaceZoneContents outputZoneContents = FurnaceZoneContents.createForClientSideContainer(OUTPUT_SLOTS_COUNT);
FurnaceZoneContents fuelZoneContents = FurnaceZoneContents.createForClientSideContainer(FUEL_SLOTS_COUNT);
FurnaceStateData furnaceStateData = new FurnaceStateData();
// on the client side there is no parent TileEntity to communicate with, so we:
// 1) use dummy inventories and furnace state data (tracked ints)
// 2) use "do nothing" lambda functions for canPlayerAccessInventory and markDirty
return new ContainerFurnace(windowID, playerInventory,
inputZoneContents, outputZoneContents, fuelZoneContents, furnaceStateData);
}
// must assign a slot index to each of the slots used by the GUI.
// For this container, we can see the furnace fuel, input, and output slots as well as the player inventory slots and the hotbar.
// Each time we add a Slot to the container using addSlotToContainer(), it automatically increases the slotIndex, which means
// 0 - 8 = hotbar slots (which will map to the InventoryPlayer slot numbers 0 - 8)
// 9 - 35 = player inventory slots (which map to the InventoryPlayer slot numbers 9 - 35)
// 36 - 39 = fuel slots (furnaceStateData 0 - 3)
// 40 - 44 = input slots (furnaceStateData 4 - 8)
// 45 - 49 = output slots (furnaceStateData 9 - 13)
private static final int HOTBAR_SLOT_COUNT = 9;
private static final int PLAYER_INVENTORY_ROW_COUNT = 3;
private static final int PLAYER_INVENTORY_COLUMN_COUNT = 9;
private static final int PLAYER_INVENTORY_SLOT_COUNT = PLAYER_INVENTORY_COLUMN_COUNT * PLAYER_INVENTORY_ROW_COUNT;
private static final int VANILLA_SLOT_COUNT = HOTBAR_SLOT_COUNT + PLAYER_INVENTORY_SLOT_COUNT;
public static final int FUEL_SLOTS_COUNT = TileEntityFurnace.FUEL_SLOTS_COUNT;
public static final int INPUT_SLOTS_COUNT = TileEntityFurnace.INPUT_SLOTS_COUNT;
public static final int OUTPUT_SLOTS_COUNT = TileEntityFurnace.OUTPUT_SLOTS_COUNT;
public static final int FURNACE_SLOTS_COUNT = FUEL_SLOTS_COUNT + INPUT_SLOTS_COUNT + OUTPUT_SLOTS_COUNT;
// slot index is the unique index for all slots in this container i.e. 0 - 35 for invPlayer then 36 - 49 for furnaceContents
private static final int VANILLA_FIRST_SLOT_INDEX = 0;
private static final int HOTBAR_FIRST_SLOT_INDEX = VANILLA_FIRST_SLOT_INDEX;
private static final int PLAYER_INVENTORY_FIRST_SLOT_INDEX = HOTBAR_FIRST_SLOT_INDEX + HOTBAR_SLOT_COUNT;
private static final int FIRST_FUEL_SLOT_INDEX = PLAYER_INVENTORY_FIRST_SLOT_INDEX + PLAYER_INVENTORY_SLOT_COUNT;
private static final int FIRST_INPUT_SLOT_INDEX = FIRST_FUEL_SLOT_INDEX + FUEL_SLOTS_COUNT;
private static final int FIRST_OUTPUT_SLOT_INDEX = FIRST_INPUT_SLOT_INDEX + INPUT_SLOTS_COUNT;
// slot number is the slot number within each component;
// i.e. invPlayer slots 0 - 35 (hotbar 0 - 8 then main inventory 9 to 35)
// and furnace: inputZone slots 0 - 4, outputZone slots 0 - 4, fuelZone 0 - 3
public ContainerFurnace(int windowID, PlayerInventory invPlayer,
FurnaceZoneContents inputZoneContents,
FurnaceZoneContents outputZoneContents,
FurnaceZoneContents fuelZoneContents,
FurnaceStateData furnaceStateData) {
super(TileEntityList.furnaceContainer, windowID);
if (TileEntityList.furnaceTile == null)
throw new IllegalStateException("Must initialise containerTypeContainerFurnace before constructing a ContainerFurnace!");
this.inputZoneContents = inputZoneContents;
this.outputZoneContents = outputZoneContents;
this.fuelZoneContents = fuelZoneContents;
this.furnaceStateData = furnaceStateData;
this.world = invPlayer.player.world;
trackIntArray(furnaceStateData); // tell vanilla to keep the furnaceStateData synchronised between client and server Containers
final int SLOT_X_SPACING = 18;
final int SLOT_Y_SPACING = 18;
final int HOTBAR_XPOS = 8;
final int HOTBAR_YPOS = 183;
// Add the players hotbar to the gui - the [xpos, ypos] location of each item
for (int x = 0; x < HOTBAR_SLOT_COUNT; x++) {
int slotNumber = x;
addSlot(new Slot(invPlayer, slotNumber, HOTBAR_XPOS + SLOT_X_SPACING * x, HOTBAR_YPOS));
}
final int PLAYER_INVENTORY_XPOS = 8;
final int PLAYER_INVENTORY_YPOS = 125;
// Add the rest of the players inventory to the gui
for (int y = 0; y < PLAYER_INVENTORY_ROW_COUNT; y++) {
for (int x = 0; x < PLAYER_INVENTORY_COLUMN_COUNT; x++) {
int slotNumber = HOTBAR_SLOT_COUNT + y * PLAYER_INVENTORY_COLUMN_COUNT + x;
int xpos = PLAYER_INVENTORY_XPOS + x * SLOT_X_SPACING;
int ypos = PLAYER_INVENTORY_YPOS + y * SLOT_Y_SPACING;
addSlot(new Slot(invPlayer, slotNumber, xpos, ypos));
}
}
final int FUEL_SLOTS_XPOS = 53;
final int FUEL_SLOTS_YPOS = 96;
// Add the tile fuel slots
for (int x = 0; x < FUEL_SLOTS_COUNT; x++) {
int slotNumber = x;
addSlot(new SlotFuel(fuelZoneContents, slotNumber, FUEL_SLOTS_XPOS + SLOT_X_SPACING * x, FUEL_SLOTS_YPOS));
}
final int INPUT_SLOTS_XPOS = 26;
final int INPUT_SLOTS_YPOS = 24;
// Add the tile input slots
for (int y = 0; y < INPUT_SLOTS_COUNT; y++) {
int slotNumber = y;
addSlot(new SlotSmeltableInput(inputZoneContents, slotNumber, INPUT_SLOTS_XPOS, INPUT_SLOTS_YPOS + SLOT_Y_SPACING * y));
}
final int OUTPUT_SLOTS_XPOS = 134;
final int OUTPUT_SLOTS_YPOS = 24;
// Add the tile output slots
for (int y = 0; y < OUTPUT_SLOTS_COUNT; y++) {
int slotNumber = y;
addSlot(new SlotOutput(outputZoneContents, slotNumber, OUTPUT_SLOTS_XPOS, OUTPUT_SLOTS_YPOS + SLOT_Y_SPACING * y));
}
}
// Checks each tick to make sure the player is still able to access the inventory and if not closes the gui
@Override
public boolean canInteractWith(PlayerEntity player) {
return fuelZoneContents.isUsableByPlayer(player) && inputZoneContents.isUsableByPlayer(player)
&& outputZoneContents.isUsableByPlayer(player);
}
// This is where you specify what happens when a player shift clicks a slot in the gui
// (when you shift click a slot in the TileEntity Inventory, it moves it to the first available position in the hotbar and/or
// player inventory. When you you shift-click a hotbar or player inventory item, it moves it to the first available
// position in the TileEntity inventory - either input or fuel as appropriate for the item you clicked)
// At the very least you must override this and return ItemStack.EMPTY or the game will crash when the player shift clicks a slot.
// returns ItemStack.EMPTY if the source slot is empty, or if none of the source slot item could be moved.
// otherwise, returns a copy of the source stack
// Code copied & refactored from vanilla furnace AbstractFurnaceContainer
@Override
public ItemStack transferStackInSlot(PlayerEntity player, int sourceSlotIndex) {
Slot sourceSlot = inventorySlots.get(sourceSlotIndex);
if (sourceSlot == null || !sourceSlot.getHasStack()) return ItemStack.EMPTY;
ItemStack sourceItemStack = sourceSlot.getStack();
ItemStack sourceStackBeforeMerge = sourceItemStack.copy();
boolean successfulTransfer = false;
SlotZone sourceZone = SlotZone.getZoneFromIndex(sourceSlotIndex);
switch (sourceZone) {
case OUTPUT_ZONE: // taking out of the output zone - try the hotbar first, then main inventory. fill from the end.
successfulTransfer = mergeInto(SlotZone.PLAYER_HOTBAR, sourceItemStack, true);
if (!successfulTransfer) {
successfulTransfer = mergeInto(SlotZone.PLAYER_MAIN_INVENTORY, sourceItemStack, true);
}
if (successfulTransfer) { // removing from output means we have just crafted an item -> need to inform
sourceSlot.onSlotChange(sourceItemStack, sourceStackBeforeMerge);
}
break;
case INPUT_ZONE:
case FUEL_ZONE: // taking out of input zone or fuel zone - try player main inv first, then hotbar. fill from the start
successfulTransfer = mergeInto(SlotZone.PLAYER_MAIN_INVENTORY, sourceItemStack, false);
if (!successfulTransfer) {
successfulTransfer = mergeInto(SlotZone.PLAYER_HOTBAR, sourceItemStack, false);
}
break;
case PLAYER_HOTBAR:
case PLAYER_MAIN_INVENTORY: // taking out of inventory - find the appropriate furnace zone
if (!TileEntityFurnace.getSmeltingResultForItem(world, sourceItemStack).isEmpty()) { // smeltable -> add to input
successfulTransfer = mergeInto(SlotZone.INPUT_ZONE, sourceItemStack, false);
}
if (!successfulTransfer && TileEntityFurnace.getItemBurnTime(world, sourceItemStack) > 0) { //burnable -> add to fuel from the bottom slot first
successfulTransfer = mergeInto(SlotZone.FUEL_ZONE, sourceItemStack, true);
}
if (!successfulTransfer) { // didn't fit into furnace; try player main inventory or hotbar
if (sourceZone == SlotZone.PLAYER_HOTBAR) { // main inventory
successfulTransfer = mergeInto(SlotZone.PLAYER_MAIN_INVENTORY, sourceItemStack, false);
} else {
successfulTransfer = mergeInto(SlotZone.PLAYER_HOTBAR, sourceItemStack, false);
}
}
break;
default:
throw new IllegalArgumentException("unexpected sourceZone:" + sourceZone);
}
if (!successfulTransfer) return ItemStack.EMPTY;
// If source stack is empty (the entire stack was moved) set slot contents to empty
if (sourceItemStack.isEmpty()) {
sourceSlot.putStack(ItemStack.EMPTY);
} else {
sourceSlot.onSlotChanged();
}
// if source stack is still the same as before the merge, the transfer failed somehow? not expected.
if (sourceItemStack.getCount() == sourceStackBeforeMerge.getCount()) {
return ItemStack.EMPTY;
}
sourceSlot.onTake(player, sourceItemStack);
return sourceStackBeforeMerge;
}
/**
* Try to merge from the given source ItemStack into the given SlotZone.
*
* @param destinationZone the zone to merge into
* @param sourceItemStack the itemstack to merge from
* @param fillFromEnd if true: try to merge from the end of the zone instead of from the start
* @return true if a successful transfer occurred
*/
private boolean mergeInto(SlotZone destinationZone, ItemStack sourceItemStack, boolean fillFromEnd) {
return mergeItemStack(sourceItemStack, destinationZone.firstIndex, destinationZone.lastIndexPlus1, fillFromEnd);
}
// -------- methods used by the ContainerScreen to render parts of the display
/**
* Returns the amount of fuel remaining on the currently burning item in the given fuel slot.
*
* @return fraction remaining, between 0.0 - 1.0
* @fuelSlot the number of the fuel slot (0..3)
*/
public double fractionOfFuelRemaining(int fuelSlot) {
if (furnaceStateData.burnTimeInitialValues[fuelSlot] <= 0) return 0;
double fraction = furnaceStateData.burnTimeRemainings[fuelSlot] / (double) furnaceStateData.burnTimeInitialValues[fuelSlot];
return MathHelper.clamp(fraction, 0.0, 1.0);
}
/**
* return the remaining burn time of the fuel in the given slot
*
* @param fuelSlot the number of the fuel slot (0..3)
* @return seconds remaining
*/
public int secondsOfFuelRemaining(int fuelSlot) {
if (furnaceStateData.burnTimeRemainings[fuelSlot] <= 0) return 0;
return furnaceStateData.burnTimeRemainings[fuelSlot] / 20; // 20 ticks per second
}
/**
* Returns the amount of cook time completed on the currently cooking item.
*
* @return fraction remaining, between 0 - 1
*/
public double fractionOfCookTimeComplete() {
if (furnaceStateData.cookTimeForCompletion == 0) return 0;
double fraction = furnaceStateData.cookTimeElapsed / (double) furnaceStateData.cookTimeForCompletion;
return MathHelper.clamp(fraction, 0.0, 1.0);
}
// --------- Customise the different slots (in particular - what items they will accept)
// SlotFuel is a slot for fuel items
public class SlotFuel extends Slot {
public SlotFuel(IInventory inventoryIn, int index, int xPosition, int yPosition) {
super(inventoryIn, index, xPosition, yPosition);
}
// if this function returns false, the player won't be able to insert the given item into this slot
@Override
public boolean isItemValid(ItemStack stack) {
return TileEntityFurnace.isItemValidForFuelSlot(stack);
}
}
// SlotSmeltableInput is a slot for input item
public class SlotSmeltableInput extends Slot {
public SlotSmeltableInput(IInventory inventoryIn, int index, int xPosition, int yPosition) {
super(inventoryIn, index, xPosition, yPosition);
}
// if this function returns false, the player won't be able to insert the given item into this slot
@Override
public boolean isItemValid(ItemStack stack) {
return TileEntityFurnace.isItemValidForInputSlot(stack);
}
}
// SlotOutput is a slot that will not accept any item
public class SlotOutput extends Slot {
public SlotOutput(IInventory inventoryIn, int index, int xPosition, int yPosition) {
super(inventoryIn, index, xPosition, yPosition);
}
// if this function returns false, the player won't be able to insert the given item into this slot
@Override
public boolean isItemValid(ItemStack stack) {
return TileEntityFurnace.isItemValidForOutputSlot(stack);
}
}
private FurnaceZoneContents inputZoneContents;
private FurnaceZoneContents outputZoneContents;
private FurnaceZoneContents fuelZoneContents;
private FurnaceStateData furnaceStateData;
private World world; //needed for some helper methods
/**
* Helper enum to make the code more readable
*/
private enum SlotZone {
FUEL_ZONE(FIRST_FUEL_SLOT_INDEX, FUEL_SLOTS_COUNT),
INPUT_ZONE(FIRST_INPUT_SLOT_INDEX, INPUT_SLOTS_COUNT),
OUTPUT_ZONE(FIRST_OUTPUT_SLOT_INDEX, OUTPUT_SLOTS_COUNT),
PLAYER_MAIN_INVENTORY(PLAYER_INVENTORY_FIRST_SLOT_INDEX, PLAYER_INVENTORY_SLOT_COUNT),
PLAYER_HOTBAR(HOTBAR_FIRST_SLOT_INDEX, HOTBAR_SLOT_COUNT);
SlotZone(int firstIndex, int numberOfSlots) {
this.firstIndex = firstIndex;
this.slotCount = numberOfSlots;
this.lastIndexPlus1 = firstIndex + numberOfSlots;
}
public final int firstIndex;
public final int slotCount;
public final int lastIndexPlus1;
public static SlotZone getZoneFromIndex(int slotIndex) {
for (SlotZone slotZone : SlotZone.values()) {
if (slotIndex >= slotZone.firstIndex && slotIndex < slotZone.lastIndexPlus1) return slotZone;
}
throw new IndexOutOfBoundsException("Unexpected slotIndex");
}
}
}
ContainerScreenFurnace
package com.lethalmap.stardewmod.common.furnace;
import com.lethalmap.stardewmod.Constants;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
public class ContainerScreenFurnace extends ContainerScreen<ContainerFurnace> {
private ContainerFurnace containerFurnace;
public ContainerScreenFurnace(ContainerFurnace containerFurnace, PlayerInventory playerInventory, ITextComponent title) {
super(containerFurnace, playerInventory, title);
this.containerFurnace = containerFurnace;
// Set the width and height of the gui. Should match the size of the texture!
xSize = 176;
ySize = 207;
}
// some [x,y] coordinates of graphical elements
final int COOK_BAR_XPOS = 49;
final int COOK_BAR_YPOS = 60;
final int COOK_BAR_ICON_U = 0; // texture position of white arrow icon [u,v]
final int COOK_BAR_ICON_V = 207;
final int COOK_BAR_WIDTH = 80;
final int COOK_BAR_HEIGHT = 17;
final int FLAME_XPOS = 54;
final int FLAME_YPOS = 80;
final int FLAME_ICON_U = 176; // texture position of flame icon [u,v]
final int FLAME_ICON_V = 0;
final int FLAME_WIDTH = 14;
final int FLAME_HEIGHT = 14;
final int FLAME_X_SPACING = 18;
public void render(int mouseX, int mouseY, float partialTicks) {
this.renderBackground();
super.render(mouseX, mouseY, partialTicks);
this.renderHoveredToolTip(mouseX, mouseY);
}
// Draw the Tool tip text if hovering over something of interest on the screen
protected void renderHoveredToolTip(int mouseX, int mouseY) {
if (!this.minecraft.player.inventory.getItemStack().isEmpty()) return; // no tooltip if the player is dragging something
List<String> hoveringText = new ArrayList<String>();
// If the mouse is over the progress bar add the progress bar hovering text
if (isInRect(guiLeft + COOK_BAR_XPOS, guiTop + COOK_BAR_YPOS, COOK_BAR_WIDTH, COOK_BAR_HEIGHT, mouseX, mouseY)){
hoveringText.add("Progress:");
int cookPercentage =(int)(containerFurnace.fractionOfCookTimeComplete() * 100);
hoveringText.add(cookPercentage + "%");
}
// If the mouse is over one of the burn time indicators, add the burn time indicator hovering text
for (int i = 0; i < containerFurnace.FUEL_SLOTS_COUNT; ++i) {
if (isInRect(guiLeft + FLAME_XPOS + FLAME_X_SPACING * i, guiTop + FLAME_YPOS, FLAME_WIDTH, FLAME_HEIGHT, mouseX, mouseY)) {
hoveringText.add("Fuel Time:");
hoveringText.add(containerFurnace.secondsOfFuelRemaining(i) + "s");
}
}
// If hoveringText is not empty draw the hovering text. Otherwise, use vanilla to render tooltip for the slots
if (!hoveringText.isEmpty()){
renderTooltip(hoveringText, mouseX, mouseY);
} else {
super.renderHoveredToolTip(mouseX, mouseY);
}
}
@Override
protected void drawGuiContainerBackgroundLayer(float partialTicks, int x, int y) {
RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
this.minecraft.getTextureManager().bindTexture(TEXTURE);
// width and height are the size provided to the window when initialised after creation.
// xSize, ySize are the expected size of the texture-? usually seems to be left as a default.
// The code below is typical for vanilla containers, so I've just copied that- it appears to centre the texture within
// the available window
int edgeSpacingX = (this.width - this.xSize) / 2;
int edgeSpacingY = (this.height - this.ySize) / 2;
this.blit(edgeSpacingX, edgeSpacingY, 0, 0, this.xSize, this.ySize);
// draw the cook progress bar
double cookProgress = containerFurnace.fractionOfCookTimeComplete();
blit(guiLeft + COOK_BAR_XPOS, guiTop + COOK_BAR_YPOS, COOK_BAR_ICON_U, COOK_BAR_ICON_V,
(int)(cookProgress * COOK_BAR_WIDTH), COOK_BAR_HEIGHT);
// draw the fuel remaining bar for each fuel slot flame
for (int i = 0; i < containerFurnace.FUEL_SLOTS_COUNT; ++i) {
double burnRemaining = containerFurnace.fractionOfFuelRemaining(i);
int yOffset = (int)((1.0 - burnRemaining) * FLAME_HEIGHT);
blit(guiLeft + FLAME_XPOS + FLAME_X_SPACING * i, guiTop + FLAME_YPOS + yOffset,
FLAME_ICON_U, FLAME_ICON_V + yOffset, FLAME_WIDTH, FLAME_HEIGHT - yOffset);
}
}
@Override
protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) {
super.drawGuiContainerForegroundLayer(mouseX, mouseY);
final int LABEL_XPOS = 5;
final int LABEL_YPOS = 5;
font.drawString(title.getFormattedText(), LABEL_XPOS, LABEL_YPOS, Color.darkGray.getRGB());
}
// Returns true if the given x,y coordinates are within the given rectangle
public static boolean isInRect(int x, int y, int xSize, int ySize, int mouseX, int mouseY){
return ((mouseX >= x && mouseX <= x+xSize) && (mouseY >= y && mouseY <= y+ySize));
}
// This is the resource location for the background image
private static final ResourceLocation TEXTURE = new ResourceLocation(Constants.MODID, "textures/gui/container/furnace.png");
}
FurnaceStateData
package com.lethalmap.stardewmod.common.furnace;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.IIntArray;
import java.util.Arrays;
public class FurnaceStateData implements IIntArray {
public static final int FUEL_SLOTS_COUNT = TileEntityFurnace.FUEL_SLOTS_COUNT;
/**The number of ticks that the current item has been cooking*/
public int cookTimeElapsed;
// The number of ticks required to cook the current item (i.e complete when cookTimeElapsed == cookTimeForCompletion
public int cookTimeForCompletion;
/** The initial fuel value of the currently burning fuel in each slot (in ticks of burn duration) */
public int [] burnTimeInitialValues = new int[FUEL_SLOTS_COUNT];
/** The number of burn ticks remaining on the current piece of fuel in each slot */
public int [] burnTimeRemainings = new int[FUEL_SLOTS_COUNT];
// --------- read/write to NBT for permanent storage (on disk, or packet transmission) - used by the TileEntity only
public void putIntoNBT(CompoundNBT nbtTagCompound) {
nbtTagCompound.putInt("CookTimeElapsed", cookTimeElapsed);
nbtTagCompound.putInt("CookTimeForCompletion", cookTimeElapsed);
nbtTagCompound.putIntArray("burnTimeRemainings", burnTimeRemainings);
nbtTagCompound.putIntArray("burnTimeInitial", burnTimeInitialValues);
}
public void readFromNBT(CompoundNBT nbtTagCompound) {
// Trim the arrays (or pad with 0) to make sure they have the correct number of elements
cookTimeElapsed = nbtTagCompound.getInt("CookTimeElapsed");
cookTimeForCompletion = nbtTagCompound.getInt("CookTimeForCompletion");
burnTimeRemainings = Arrays.copyOf(nbtTagCompound.getIntArray("burnTimeRemainings"), FUEL_SLOTS_COUNT);
burnTimeInitialValues = Arrays.copyOf(nbtTagCompound.getIntArray("burnTimeInitialValues"), FUEL_SLOTS_COUNT);
}
// -------- used by vanilla, not intended for mod code
// * The ints are mapped (internally) as:
// * 0 = cookTimeElapsed
// * 1 = cookTimeForCompletion
// * 2 .. FUEL_SLOTS_COUNT+1 = burnTimeInitialValues[]
// * FUEL_SLOTS_COUNT + 2 .. 2*FUEL_SLOTS_COUNT +1 = burnTimeRemainings[]
// *
private final int COOKTIME_INDEX = 0;
private final int COOKTIME_FOR_COMPLETION_INDEX = 1;
private final int BURNTIME_INITIAL_VALUE_INDEX = 2;
private final int BURNTIME_REMAINING_INDEX = BURNTIME_INITIAL_VALUE_INDEX + FUEL_SLOTS_COUNT;
private final int END_OF_DATA_INDEX_PLUS_ONE = BURNTIME_REMAINING_INDEX + FUEL_SLOTS_COUNT;
@Override
public int get(int index) {
validateIndex(index);
if (index == COOKTIME_INDEX) {
return cookTimeElapsed;
} else if (index == COOKTIME_FOR_COMPLETION_INDEX) {
return cookTimeForCompletion;
} else if (index >= BURNTIME_INITIAL_VALUE_INDEX && index < BURNTIME_REMAINING_INDEX) {
return burnTimeInitialValues[index - BURNTIME_INITIAL_VALUE_INDEX];
} else {
return burnTimeRemainings[index - BURNTIME_REMAINING_INDEX];
}
}
@Override
public void set(int index, int value) {
validateIndex(index);
if (index == COOKTIME_INDEX) {
cookTimeElapsed = value;
} else if (index == COOKTIME_FOR_COMPLETION_INDEX) {
cookTimeForCompletion = value;
} else if (index >= BURNTIME_INITIAL_VALUE_INDEX && index < BURNTIME_REMAINING_INDEX) {
burnTimeInitialValues[index - BURNTIME_INITIAL_VALUE_INDEX] = value;
} else {
burnTimeRemainings[index - BURNTIME_REMAINING_INDEX] = value;
}
}
@Override
public int size() {
return END_OF_DATA_INDEX_PLUS_ONE;
}
private void validateIndex(int index) throws IndexOutOfBoundsException {
if (index < 0 || index >= size()) {
throw new IndexOutOfBoundsException("Index out of bounds:"+index);
}
}
}
FurnaceZoneContens
package com.lethalmap.stardewmod.common.furnace;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraftforge.items.ItemStackHandler;
import java.util.function.Predicate;
public class FurnaceZoneContents implements IInventory {
/**
* Use this constructor to create a FurnaceZoneContents which is linked to its parent TileEntity.
* On the server, this link will be used by the Container to request information and provide notifications to the parent
* On the client, the link will be unused.
* There are additional notificationLambdas available; these two are explicitly specified because your TileEntity will
* nearly always need to implement at least these two
* @param size the max number of ItemStacks in the inventory
* @param canPlayerAccessInventoryLambda the function that the container should call in order to decide if the given player
* can access the container's contents not. Usually, this is a check to see
* if the player is closer than 8 blocks away.
* @param markDirtyNotificationLambda the function that the container should call in order to tell the parent TileEntity
* that the contents of its inventory have been changed and need to be saved. Usually,
* this is TileEntity::markDirty
* @return the new ChestContents.
*/
public static FurnaceZoneContents createForTileEntity(int size,
Predicate<PlayerEntity> canPlayerAccessInventoryLambda,
Notify markDirtyNotificationLambda) {
return new FurnaceZoneContents(size, canPlayerAccessInventoryLambda, markDirtyNotificationLambda);
}
/**
* Use this constructor to create a FurnaceZoneContents which is not linked to any parent TileEntity; i.e.
* is used by the client side container:
* * does not permanently store items
* * cannot ask questions/provide notifications to a parent TileEntity
* @param size the max number of ItemStacks in the inventory
* @return the new ChestContents
*/
public static FurnaceZoneContents createForClientSideContainer(int size) {
return new FurnaceZoneContents(size);
}
// ----Methods used to load / save the contents to NBT
/**
* Writes the chest contents to a CompoundNBT tag (used to save the contents to disk)
* @return the tag containing the contents
*/
public CompoundNBT serializeNBT() {
return furnaceComponentContents.serializeNBT();
}
/**
* Fills the chest contents from the nbt; resizes automatically to fit. (used to load the contents from disk)
* @param nbt
*/
public void deserializeNBT(CompoundNBT nbt) {
furnaceComponentContents.deserializeNBT(nbt);
}
// ------------- linking methods -------------
// The following group of methods are used to establish a link between the parent TileEntity and the chest contents,
// so that the container can communicate with the parent TileEntity without having to talk to it directly.
// This is important because the link to the TileEntity only exists on the server side. On the client side, the
// container gets a dummy link instead- there is no link to the client TileEntity. Linking to the client TileEntity
// is prohibited because of synchronisation clashes, i.e. vanilla would attempt to synchronise the TileEntity in two
// different ways at the same time: via the tileEntity server->client packets and via the container directly poking
// around in the inventory contents.
// I've used lambdas to make the decoupling more explicit. You could instead
// * provide an Optional TileEntity to the ChestContents constructor (and ignore the markDirty() etc calls), or
// * implement IInventory directly in your TileEntity, and construct your client-side container using an Inventory
// instead of passing it a TileEntity. (This is how vanilla does it)
//
/**
* sets the function that the container should call in order to decide if the given player can access the container's
* contents not. The lambda function is only used on the server side
*/
public void setCanPlayerAccessInventoryLambda(Predicate<PlayerEntity> canPlayerAccessInventoryLambda) {
this.canPlayerAccessInventoryLambda = canPlayerAccessInventoryLambda;
}
// the function that the container should call in order to tell the parent TileEntity that the
// contents of its inventory have been changed.
// default is "do nothing"
public void setMarkDirtyNotificationLambda(Notify markDirtyNotificationLambda) {
this.markDirtyNotificationLambda = markDirtyNotificationLambda;
}
// the function that the container should call in order to tell the parent TileEntity that the
// container has been opened by a player (eg so that the chest can animate its lid being opened)
// default is "do nothing"
public void setOpenInventoryNotificationLambda(Notify openInventoryNotificationLambda) {
this.openInventoryNotificationLambda = openInventoryNotificationLambda;
}
// the function that the container should call in order to tell the parent TileEntity that the
// container has been closed by a player
// default is "do nothing"
public void setCloseInventoryNotificationLambda(Notify closeInventoryNotificationLambda) {
this.closeInventoryNotificationLambda = closeInventoryNotificationLambda;
}
// ---------- These methods are used by the container to ask whether certain actions are permitted
// If you need special behaviour (eg a chest can only be used by a particular player) then either modify this method
// or ask the parent TileEntity.
@Override
public boolean isUsableByPlayer(PlayerEntity player) {
return canPlayerAccessInventoryLambda.test(player); // on the client, this does nothing. on the server, ask our parent TileEntity.
}
@Override
public boolean isItemValidForSlot(int index, ItemStack stack) {
return furnaceComponentContents.isItemValid(index, stack);
}
// ----- Methods used to inform the parent tile entity that something has happened to the contents
// you can make direct calls to the parent if you like, I've used lambdas because I think it shows the separation
// of responsibilities more clearly.
@FunctionalInterface
public interface Notify { // Some folks use Runnable, but I prefer not to use it for non-thread-related tasks
void invoke();
}
@Override
public void markDirty() {
markDirtyNotificationLambda.invoke();
}
@Override
public void openInventory(PlayerEntity player) {
openInventoryNotificationLambda.invoke();
}
@Override
public void closeInventory(PlayerEntity player) {
closeInventoryNotificationLambda.invoke();
}
//---------These following methods are called by Vanilla container methods to manipulate the inventory contents ---
@Override
public int getSizeInventory() {
return furnaceComponentContents.getSlots();
}
@Override
public boolean isEmpty() {
for (int i = 0; i < furnaceComponentContents.getSlots(); ++i) {
if (!furnaceComponentContents.getStackInSlot(i).isEmpty()) return false;
}
return true;
}
@Override
public ItemStack getStackInSlot(int index) {
return furnaceComponentContents.getStackInSlot(index);
}
@Override
public ItemStack decrStackSize(int index, int count) {
if (count < 0) throw new IllegalArgumentException("count should be >= 0:" + count);
return furnaceComponentContents.extractItem(index, count, false);
}
@Override
public ItemStack removeStackFromSlot(int index) {
int maxPossibleItemStackSize = furnaceComponentContents.getSlotLimit(index);
return furnaceComponentContents.extractItem(index, maxPossibleItemStackSize, false);
}
@Override
public void setInventorySlotContents(int index, ItemStack stack) {
furnaceComponentContents.setStackInSlot(index, stack);
}
@Override
public void clear() {
for (int i = 0; i < furnaceComponentContents.getSlots(); ++i) {
furnaceComponentContents.setStackInSlot(i, ItemStack.EMPTY);
}
}
//--------- useful functions that aren't in IInventory but are useful anyway
/**
* Tries to insert the given ItemStack into the given slot.
* @param index the slot to insert into
* @param itemStackToInsert the itemStack to insert. Is not mutated by the function.
* @return if successful insertion: ItemStack.EMPTY. Otherwise, the leftover itemstack
* (eg if ItemStack has a size of 23, and only 12 will fit, then ItemStack with a size of 11 is returned
*/
public ItemStack increaseStackSize(int index, ItemStack itemStackToInsert) {
ItemStack leftoverItemStack = furnaceComponentContents.insertItem(index, itemStackToInsert, false);
return leftoverItemStack;
}
/**
* Checks if the given slot will accept all of the given itemStack
* @param index the slot to insert into
* @param itemStackToInsert the itemStack to insert
* @return if successful insertion: ItemStack.EMPTY. Otherwise, the leftover itemstack
* (eg if ItemStack has a size of 23, and only 12 will fit, then ItemStack with a size of 11 is returned
*/
public boolean doesItemStackFit(int index, ItemStack itemStackToInsert) {
ItemStack leftoverItemStack = furnaceComponentContents.insertItem(index, itemStackToInsert, true);
return leftoverItemStack.isEmpty();
}
// ---------
private FurnaceZoneContents(int size) {
this.furnaceComponentContents = new ItemStackHandler(size);
}
private FurnaceZoneContents(int size, Predicate<PlayerEntity> canPlayerAccessInventoryLambda, Notify markDirtyNotificationLambda) {
this.furnaceComponentContents = new ItemStackHandler(size);
this.canPlayerAccessInventoryLambda = canPlayerAccessInventoryLambda;
this.markDirtyNotificationLambda = markDirtyNotificationLambda;
}
// the function that the container should call in order to decide if the
// given player can access the container's Inventory or not. Only valid server side
// default is "true".
private Predicate<PlayerEntity> canPlayerAccessInventoryLambda = x-> true;
// the function that the container should call in order to tell the parent TileEntity that the
// contents of its inventory have been changed.
// default is "do nothing"
private Notify markDirtyNotificationLambda = ()->{};
// the function that the container should call in order to tell the parent TileEntity that the
// container has been opened by a player (eg so that the chest can animate its lid being opened)
// default is "do nothing"
private Notify openInventoryNotificationLambda = ()->{};
// the function that the container should call in order to tell the parent TileEntity that the
// container has been closed by a player
// default is "do nothing"
private Notify closeInventoryNotificationLambda = ()->{};
private final ItemStackHandler furnaceComponentContents;
}
TileEntityFurnace
package com.lethalmap.stardewmod.common.furnace;
import com.lethalmap.stardewmod.common.tiles.TileEntityList;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventory;
import net.minecraft.inventory.InventoryHelper;
import net.minecraft.inventory.container.Container;
import net.minecraft.inventory.container.INamedContainerProvider;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.FurnaceRecipe;
import net.minecraft.item.crafting.IRecipeType;
import net.minecraft.item.crafting.RecipeManager;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.play.server.SUpdateTileEntityPacket;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.Optional;
public class TileEntityFurnace extends TileEntity implements INamedContainerProvider, ITickableTileEntity {
public static final int FUEL_SLOTS_COUNT = 4;
public static final int INPUT_SLOTS_COUNT = 5;
public static final int OUTPUT_SLOTS_COUNT = 5;
public static final int TOTAL_SLOTS_COUNT = FUEL_SLOTS_COUNT + INPUT_SLOTS_COUNT + OUTPUT_SLOTS_COUNT;
private FurnaceZoneContents fuelZoneContents;
private FurnaceZoneContents inputZoneContents;
private FurnaceZoneContents outputZoneContents;
private final FurnaceStateData furnaceStateData = new FurnaceStateData();
public TileEntityFurnace(){
super(TileEntityList.furnaceTile);
fuelZoneContents = FurnaceZoneContents.createForTileEntity(FUEL_SLOTS_COUNT,
this::canPlayerAccessInventory, this::markDirty);
inputZoneContents = FurnaceZoneContents.createForTileEntity(INPUT_SLOTS_COUNT,
this::canPlayerAccessInventory, this::markDirty);
outputZoneContents = FurnaceZoneContents.createForTileEntity(OUTPUT_SLOTS_COUNT,
this::canPlayerAccessInventory, this::markDirty);
}
// Return true if the given player is able to use this block. In this case it checks that
// 1) the world tileentity hasn't been replaced in the meantime, and
// 2) the player isn't too far away from the centre of the block
public boolean canPlayerAccessInventory(PlayerEntity player) {
if (this.world.getTileEntity(this.pos) != this) return false;
final double X_CENTRE_OFFSET = 0.5;
final double Y_CENTRE_OFFSET = 0.5;
final double Z_CENTRE_OFFSET = 0.5;
final double MAXIMUM_DISTANCE_SQ = 8.0 * 8.0;
return player.getDistanceSq(pos.getX() + X_CENTRE_OFFSET, pos.getY() + Y_CENTRE_OFFSET, pos.getZ() + Z_CENTRE_OFFSET) < MAXIMUM_DISTANCE_SQ;
}
/**
* Get the number of slots which have fuel burning in them.
* @return number of slots with burning fuel, 0 - FUEL_SLOTS_COUNT
*/
public int numberOfBurningFuelSlots() {
int burningCount = 0;
for (int burnTime : furnaceStateData.burnTimeRemainings) {
if (burnTime > 0) ++burningCount;
}
return burningCount;
}
// This method is called every tick to update the tile entity, i.e.
// - see if the fuel has run out, and if so turn the furnace "off" and slowly uncook the current item (if any)
// - see if the current smelting input item has finished smelting; if so, convert it to output
// - burn fuel slots
// It runs both on the server and the client but we only need to do updates on the server side.
@Override
public void tick() {
if (world.isRemote) return; // do nothing on client.
ItemStack currentlySmeltingItem = getCurrentlySmeltingInputItem();
// if user has changed the input slots, reset the smelting time
if (!ItemStack.areItemsEqual(currentlySmeltingItem, currentlySmeltingItemLastTick)) { // == and != don't work!
furnaceStateData.cookTimeElapsed = 0;
}
currentlySmeltingItemLastTick = currentlySmeltingItem.copy();
if (!currentlySmeltingItem.isEmpty()) {
int numberOfFuelBurning = burnFuel();
// If fuel is available, keep cooking the item, otherwise start "uncooking" it at double speed
if (numberOfFuelBurning > 0) {
furnaceStateData.cookTimeElapsed += numberOfFuelBurning;
} else {
furnaceStateData.cookTimeElapsed -= 2;
}
if (furnaceStateData.cookTimeElapsed < 0) furnaceStateData.cookTimeElapsed = 0;
int cookTimeForCurrentItem = getCookTime(this.world, currentlySmeltingItem);
furnaceStateData.cookTimeForCompletion = cookTimeForCurrentItem;
// If cookTime has reached maxCookTime smelt the item and reset cookTime
if (furnaceStateData.cookTimeElapsed >= cookTimeForCurrentItem) {
smeltFirstSuitableInputItem();
furnaceStateData.cookTimeElapsed = 0;
}
} else {
furnaceStateData.cookTimeElapsed = 0;
}
// when the number of burning slots changes, we need to force the block to re-render, otherwise the change in
// state will not be visible. Likewise, we need to force a lighting recalculation.
// The block update (for renderer) is only required on client side, but the lighting is required on both, since
// the client needs it for rendering and the server needs it for crop growth etc
int numberBurning = numberOfBurningFuelSlots();
BlockState currentBlockState = world.getBlockState(this.pos);
BlockState newBlockState = currentBlockState.with(BlockInventoryFurnace.BURNING_SIDES_COUNT, numberBurning);
if (!newBlockState.equals(currentBlockState)) {
final int FLAGS = SetBlockStateFlag.get(SetBlockStateFlag.BLOCK_UPDATE, SetBlockStateFlag.SEND_TO_CLIENTS);
world.setBlockState(this.pos, newBlockState, FLAGS);
markDirty();
}
}
/**
* for each fuel slot: decreases the burn time, checks if burnTimeRemainings = 0 and tries to consume a new piece of fuel if one is available
* @return the number of fuel slots which are burning
*/
private int burnFuel() {
int burningCount = 0;
boolean inventoryChanged = false;
for (int fuelIndex = 0; fuelIndex < FUEL_SLOTS_COUNT; fuelIndex++) {
if (furnaceStateData.burnTimeRemainings[fuelIndex] > 0) {
--furnaceStateData.burnTimeRemainings[fuelIndex];
++burningCount;
}
if (furnaceStateData.burnTimeRemainings[fuelIndex] == 0) {
ItemStack fuelItemStack = fuelZoneContents.getStackInSlot(fuelIndex);
if (!fuelItemStack.isEmpty() && getItemBurnTime(this.world, fuelItemStack) > 0) {
// If the stack in this slot isn't empty and is fuel, set burnTimeRemainings & burnTimeInitialValues to the
// item's burn time and decrease the stack size
int burnTimeForItem = getItemBurnTime(this.world, fuelItemStack);
furnaceStateData.burnTimeRemainings[fuelIndex] = burnTimeForItem;
furnaceStateData.burnTimeInitialValues[fuelIndex] = burnTimeForItem;
fuelZoneContents.decrStackSize(fuelIndex, 1);
++burningCount;
inventoryChanged = true;
// If the stack size now equals 0 set the slot contents to the item container item. This is for fuel
// item such as lava buckets so that the bucket is not consumed. If the item dose not have
// a container item, getContainerItem returns ItemStack.EMPTY which sets the slot contents to empty
if (fuelItemStack.isEmpty()) {
ItemStack containerItem = fuelItemStack.getContainerItem();
fuelZoneContents.setInventorySlotContents(fuelIndex, containerItem);
}
}
}
}
if (inventoryChanged) markDirty();
return burningCount;
}
/**
* Check if any of the input item are smeltable and there is sufficient space in the output slots
* @return the ItemStack of the first input item that can be smelted; ItemStack.EMPTY if none
*/
private ItemStack getCurrentlySmeltingInputItem() {return smeltFirstSuitableInputItem(false);}
/**
* Smelt an input item into an output slot, if possible
*/
private void smeltFirstSuitableInputItem() {
smeltFirstSuitableInputItem(true);
}
/**
* checks that there is an item to be smelted in one of the input slots and that there is room for the result in the output slots
* If desired, performs the smelt
* @param performSmelt if true, perform the smelt. if false, check whether smelting is possible, but don't change the inventory
* @return a copy of the ItemStack of the input item smelted or to-be-smelted
*/
private ItemStack smeltFirstSuitableInputItem(boolean performSmelt)
{
Integer firstSuitableInputSlot = null;
Integer firstSuitableOutputSlot = null;
ItemStack result = ItemStack.EMPTY;
// finds the first input slot which is smeltable and whose result fits into an output slot (stacking if possible)
for (int inputIndex = 0; inputIndex < INPUT_SLOTS_COUNT; inputIndex++) {
ItemStack itemStackToSmelt = inputZoneContents.getStackInSlot(inputIndex);
if (!itemStackToSmelt.isEmpty()) {
result = getSmeltingResultForItem(this.world, itemStackToSmelt);
if (!result.isEmpty()) {
// find the first suitable output slot- either empty, or with identical item that has enough space
for (int outputIndex = 0; outputIndex < OUTPUT_SLOTS_COUNT; outputIndex++) {
if (willItemStackFit(outputZoneContents, outputIndex, result)) {
firstSuitableInputSlot = inputIndex;
firstSuitableOutputSlot = outputIndex;
break;
}
}
if (firstSuitableInputSlot != null) break;
}
}
}
if (firstSuitableInputSlot == null) return ItemStack.EMPTY;
ItemStack returnvalue = inputZoneContents.getStackInSlot(firstSuitableInputSlot).copy();
if (!performSmelt) return returnvalue;
// alter input and output
inputZoneContents.decrStackSize(firstSuitableInputSlot, 1);
outputZoneContents.increaseStackSize(firstSuitableOutputSlot, result);
markDirty();
return returnvalue;
}
/**
* Will the given ItemStack fully fit into the target slot?
* @param furnaceZoneContents
* @param slotIndex
* @param itemStackOrigin
* @return true if the given ItemStack will fit completely; false otherwise
*/
public boolean willItemStackFit(FurnaceZoneContents furnaceZoneContents, int slotIndex, ItemStack itemStackOrigin) {
ItemStack itemStackDestination = furnaceZoneContents.getStackInSlot(slotIndex);
if (itemStackDestination.isEmpty() || itemStackOrigin.isEmpty()) {
return true;
}
if (!itemStackOrigin.isItemEqual(itemStackDestination)) {
return false;
}
int sizeAfterMerge = itemStackDestination.getCount() + itemStackOrigin.getCount();
if (sizeAfterMerge <= furnaceZoneContents.getInventoryStackLimit() && sizeAfterMerge <= itemStackDestination.getMaxStackSize()) {
return true;
}
return false;
}
// returns the smelting result for the given stack. Returns ItemStack.EMPTY if the given stack can not be smelted
public static ItemStack getSmeltingResultForItem(World world, ItemStack itemStack) {
Optional<FurnaceRecipe> matchingRecipe = getMatchingRecipeForInput(world, itemStack);
if (!matchingRecipe.isPresent()) return ItemStack.EMPTY;
return matchingRecipe.get().getRecipeOutput().copy(); // beware! You must deep copy otherwise you will alter the recipe itself
}
// returns the number of ticks the given item will burn. Returns 0 if the given item is not a valid fuel
public static int getItemBurnTime(World world, ItemStack stack)
{
int burntime = net.minecraftforge.common.ForgeHooks.getBurnTime(stack);
return burntime;
}
// gets the recipe which matches the given input, or Missing if none.
public static Optional<FurnaceRecipe> getMatchingRecipeForInput(World world, ItemStack itemStack) {
RecipeManager recipeManager = world.getRecipeManager();
Inventory singleItemInventory = new Inventory(itemStack);
Optional<FurnaceRecipe> matchingRecipe = recipeManager.getRecipe(IRecipeType.SMELTING, singleItemInventory, world);
return matchingRecipe;
}
/**
* Gets the cooking time for this recipe input
* @param world
* @param itemStack the input item to be smelted
* @return cooking time (ticks) or 0 if no matching recipe
*/
public static int getCookTime(World world, ItemStack itemStack) {
Optional<FurnaceRecipe> matchingRecipe = getMatchingRecipeForInput(world, itemStack);
if (!matchingRecipe.isPresent()) return 0;
return matchingRecipe.get().getCookTime();
}
// Return true if the given stack is allowed to be inserted in the given slot
// Unlike the vanilla furnace, we allow anything to be placed in the fuel slots
static public boolean isItemValidForFuelSlot(ItemStack itemStack)
{
return true;
}
// Return true if the given stack is allowed to be inserted in the given slot
// Unlike the vanilla furnace, we allow anything to be placed in the input slots
static public boolean isItemValidForInputSlot(ItemStack itemStack)
{
return true;
}
// Return true if the given stack is allowed to be inserted in the given slot
static public boolean isItemValidForOutputSlot(ItemStack itemStack)
{
return false;
}
//------------------------------
private final String FUEL_SLOTS_NBT = "fuelSlots";
private final String INPUT_SLOTS_NBT = "inputSlots";
private final String OUTPUT_SLOTS_NBT = "outputSlots";
// This is where you save any data that you don't want to lose when the tile entity unloads
// In this case, it saves the state of the furnace (burn time etc) and the itemstacks stored in the fuel, input, and output slots
@Override
public CompoundNBT write(CompoundNBT parentNBTTagCompound)
{
super.write(parentNBTTagCompound); // The super call is required to save and load the tile's location
furnaceStateData.putIntoNBT(parentNBTTagCompound);
parentNBTTagCompound.put(FUEL_SLOTS_NBT, fuelZoneContents.serializeNBT());
parentNBTTagCompound.put(INPUT_SLOTS_NBT, inputZoneContents.serializeNBT());
parentNBTTagCompound.put(OUTPUT_SLOTS_NBT, outputZoneContents.serializeNBT());
return parentNBTTagCompound;
}
// This is where you load the data that you saved in writeToNBT
@Override
public void read(CompoundNBT nbtTagCompound)
{
super.read(nbtTagCompound); // The super call is required to save and load the tile's location
furnaceStateData.readFromNBT(nbtTagCompound);
CompoundNBT inventoryNBT = nbtTagCompound.getCompound(FUEL_SLOTS_NBT);
fuelZoneContents.deserializeNBT(inventoryNBT);
inventoryNBT = nbtTagCompound.getCompound(INPUT_SLOTS_NBT);
inputZoneContents.deserializeNBT(inventoryNBT);
inventoryNBT = nbtTagCompound.getCompound(OUTPUT_SLOTS_NBT);
outputZoneContents.deserializeNBT(inventoryNBT);
if (fuelZoneContents.getSizeInventory() != FUEL_SLOTS_COUNT
|| inputZoneContents.getSizeInventory() != INPUT_SLOTS_COUNT
|| outputZoneContents.getSizeInventory() != OUTPUT_SLOTS_COUNT
)
throw new IllegalArgumentException("Corrupted NBT: Number of inventory slots did not match expected.");
}
// // When the world loads from disk, the server needs to send the TileEntity information to the client
// // it uses getUpdatePacket(), getUpdateTag(), onDataPacket(), and handleUpdateTag() to do this
@Override
@Nullable
public SUpdateTileEntityPacket getUpdatePacket()
{
CompoundNBT updateTagDescribingTileEntityState = getUpdateTag();
final int METADATA = 42; // arbitrary.
return new SUpdateTileEntityPacket(this.pos, METADATA, updateTagDescribingTileEntityState);
}
@Override
public void onDataPacket(NetworkManager net, SUpdateTileEntityPacket pkt) {
CompoundNBT updateTagDescribingTileEntityState = pkt.getNbtCompound();
handleUpdateTag(updateTagDescribingTileEntityState);
}
/* Creates a tag containing the TileEntity information, used by vanilla to transmit from server to client
Warning - although our getUpdatePacket() uses this method, vanilla also calls it directly, so don't remove it.
*/
@Override
public CompoundNBT getUpdateTag()
{
CompoundNBT nbtTagCompound = new CompoundNBT();
write(nbtTagCompound);
return nbtTagCompound;
}
/* Populates this TileEntity with information from the tag, used by vanilla to transmit from server to client
* The vanilla default is suitable for this example but I've included an explicit definition anyway.
*/
@Override
public void handleUpdateTag(CompoundNBT tag) { read(tag); }
/**
* When this tile entity is destroyed, drop all of its contents into the world
* @param world
* @param blockPos
*/
public void dropAllContents(World world, BlockPos blockPos) {
InventoryHelper.dropInventoryItems(world, blockPos, fuelZoneContents);
InventoryHelper.dropInventoryItems(world, blockPos, inputZoneContents);
InventoryHelper.dropInventoryItems(world, blockPos, outputZoneContents);
}
// ------------- The following two methods are used to make the TileEntity perform as a NamedContainerProvider, i.e.
// 1) Provide a name used when displaying the container, and
// 2) Creating an instance of container on the server, and linking it to the inventory items stored within the TileEntity
/**
* standard code to look up what the human-readable name is.
* Can be useful when the tileentity has a customised name (eg "David's footlocker")
*/
@Override
public ITextComponent getDisplayName() {
return new TranslationTextComponent("container.minecraftbyexample.mbe31_container_registry_name");
}
/**
* The name is misleading; createMenu has nothing to do with creating a Screen, it is used to create the Container on the server only
* @param windowID
* @param playerInventory
* @param playerEntity
* @return
*/
@Nullable
@Override
public Container createMenu(int windowID, PlayerInventory playerInventory, PlayerEntity playerEntity) {
return ContainerFurnace.createContainerServerSide(windowID, playerInventory,
inputZoneContents, outputZoneContents, fuelZoneContents, furnaceStateData);
}
private ItemStack currentlySmeltingItemLastTick = ItemStack.EMPTY;
}
ModContainerType (Where I register the screens)
import net.minecraftforge.registries.ForgeRegistries;
public final class ModContainerTypes {
public static ContainerType<BackpackContainer> backpack;
public static ContainerType<ContainerFurnace> furnace;
private ModContainerTypes() {
}
public static void registerContainerTypes(RegistryEvent.Register<ContainerType<?>> event) {
backpack = register("backpack", new ContainerType<>(BackpackContainer::new));
TileEntityList.furnaceContainer = IForgeContainerType.create(ContainerFurnace::createContainerClientSide);
register(Constants.FURNACECONTAINER, TileEntityList.furnaceContainer);
}
public static void registerScreens(FMLClientSetupEvent event) {
ScreenManager.registerFactory(backpack, BackpackContainerScreen::new);
ScreenManager.registerFactory(furnace, ContainerScreenFurnace::new);
}
private static <T extends Container> ContainerType<T> register(String name, ContainerType<T> type) {
type.setRegistryName(Constants.MODID, name);
ForgeRegistries.CONTAINERS.register(type);
return type;
}
}
And this method is where I register the TileEntity
@SubscribeEvent
public static void onTileEntityTypeRegistration(final RegistryEvent.Register<TileEntityType<?>> event) {
TileEntityList.furnaceTile = TileEntityType.Builder.create(TileEntityFurnace::new, BlockList.blockfurnace)
.build(null);
// you probably don't need a datafixer --> null should be fine
TileEntityList.furnaceTile.setRegistryName(Constants.MODID, Constants.FURNACETILEENTITY);
event.getRegistry().register(TileEntityList.furnaceTile);
}
The main problem is the furnace doesn´t open, the block is already registered and the item too. But when I placed it I cant open it. I think the problem is the registration of Screen and Container, but I don´t know