Jump to content

BurstSword

Members
  • Posts

    11
  • Joined

  • Last visited

Everything posted by BurstSword

  1. 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
  2. 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?
  3. 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?
  4. I know how to program in Java, but there is 0 documentation, and sry if i dont understand a parameter called a_45437624_az
  5. Im looking at it, but I don´t know what to do with that
  6. 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
  7. Can I see an implementation of this? Only with that link I don´t know how to make it work
  8. Could you explain me what it is or send me a link to see it? Thank you
  9. @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
  10. 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
×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.