
BurstSword
Members-
Posts
11 -
Joined
-
Last visited
Recent Profile Visitors
The recent visitors block is disabled and is not being shown to other users.
BurstSword's Achievements

Tree Puncher (2/8)
0
Reputation
-
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
-
Yeah, I found that tonight!, thank you . Now I have another problem, I´ve got a tag for some items, and i want an advancement for collect every item of the tag, is there any way to use the tag instead of writing every item in the triggers?
-
Hi, Im making some advancements for my mod and I want one to trigger when break a specific block, but I only found that with Bee Nests. Is there any trigger like that or I have to do it myself? In that case, how can I do it?
-
I know how to program in Java, but there is 0 documentation, and sry if i dont understand a parameter called a_45437624_az
-
Im looking at it, but I don´t know what to do with that
-
I got my block class for the plant extending FlowerBlock package com.lethalmap.stardewmod.common.blocks; import com.lethalmap.stardewmod.Constants; import net.minecraft.block.*; import net.minecraft.block.material.Material; import net.minecraft.potion.Effects; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.shapes.ISelectionContext; import net.minecraft.util.math.shapes.VoxelShape; import net.minecraft.world.IBlockReader; public class Worms extends FlowerBlock { protected static final VoxelShape SHAPE = Block.makeCuboidShape(5.0D, 0.0D, 5.0D, 11.0D, 5.0D, 11.0D); public Worms() { super(Effects.WEAKNESS,9,Block.Properties.create(Material.PLANTS).doesNotBlockMovement().hardnessAndResistance(0).sound(SoundType.PLANT)); setRegistryName(Constants.MODID,Constants.WORMS); } /** * Get the OffsetType for this Block. Determines if the model is rendered slightly offset. */ public Block.OffsetType getOffsetType() { return Block.OffsetType.XZ; } @Override public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) { Vec3d vec3d = state.getOffset(worldIn, pos); return this.SHAPE.withOffset(vec3d.x, vec3d.y, vec3d.z); } } And got a class to generate things over the world, but I dont know how to generate this flower package com.lethalmap.stardewmod.common.world; import com.lethalmap.stardewmod.common.blocks.BlockList; import net.minecraft.world.biome.Biome; import net.minecraft.world.gen.GenerationStage; import net.minecraft.world.gen.blockstateprovider.SimpleBlockStateProvider; import net.minecraft.world.gen.feature.*; import net.minecraftforge.registries.ForgeRegistries; public class WormGeneration { public void WormGeneration(){ for(Biome biome : ForgeRegistries.BIOMES) { biome.addFeature(GenerationStage.Decoration.VEGETAL_DECORATION, new DecoratedFeature(Feature.DECORATED_FLOWER, new SimpleBlockStateProvider(BlockList.worms)); } } } I don´t know what to do to generate this, Im quite noob modding
-
Can I see an implementation of this? Only with that link I don´t know how to make it work
-
Could you explain me what it is or send me a link to see it? Thank you
-
@SubscribeEvent public void lootLoad(LootTableLoadEvent evt) { if (evt.getName().toString().equals("minecraft:chests/buried_treasure")) { evt.getTable().addPool(LootPool.builder().addEntry(TableLootEntry.builder(new ResourceLocation(Constants.MODID, "inject/buried_treasure"))).build()); } else if (evt.getName().toString().equals("minecraft:gameplay/fishing")) { evt.getTable().addPool(LootPool.builder().addEntry(TableLootEntry.builder(new ResourceLocation(Constants.MODID, "inject/fishing"))).build()); } else if (evt.getName().toString().equals("minecraft:entity/phantom")) { evt.getTable().addPool(LootPool.builder().addEntry(TableLootEntry.builder(new ResourceLocation(Constants.MODID, "inject/phantom"))).build()); } } This is my code for injections in the loot tables, but now if I want to change monsters loot tables i have to write one else if ( or switch cases) for each monster, I want to know if there is an easier way to do it. Thanks you for the fast reply
-
Hi, Im so noob modding and I want to know if I can add my custom items to only monster loot tables with lootLoad() without writing infinite cases or else ifs Thanks