Posted November 20, 20168 yr Hey all! So I have a custom tile here, and it seems to crash when I try to open it with Caused by: java.lang.RuntimeException: class com.lambda.PlentifulMisc.blocks.tile.TileEnityCrystallizer is missing a mapping! This is a bug! at super.writeToNBT(parentNBTTagCompound); // The super call is required to save and load the tiles location and writeToNBT(nbtTagCompound); Can't really figure out the problem though. here is the tile package com.lambda.PlentifulMisc.blocks.tile; import net.minecraft.tileentity.TileEntity; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.FurnaceRecipes; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagIntArray; import net.minecraft.nbt.NBTTagList; import net.minecraft.network.NetworkManager; import net.minecraft.network.play.server.SPacketUpdateTileEntity; import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityFurnace; import net.minecraft.util.ITickable; import net.minecraft.util.math.MathHelper; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentString; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.EnumSkyBlock; import javax.annotation.Nullable; import java.util.Arrays; /** * User: brandon3055 * Date: 06/01/2015 * * TileInventorySmelting is an advanced sided inventory that works like a vanilla furnace except that it has 5 input and output slots, * 4 fuel slots and cooks at up to four times the speed. * The input slots are used sequentially rather than in parallel, i.e. the first slot cooks, then the second, then the third, etc * The fuel slots are used in parallel. The more slots burning in parallel, the faster the cook time. * The code is heavily based on TileEntityFurnace. */ public class TileEnityCrystallizer extends TileEntity implements IInventory, ITickable { // Create and initialize the itemStacks variable that will store store the itemStacks 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; public static final int FIRST_FUEL_SLOT = 0; public static final int FIRST_INPUT_SLOT = FIRST_FUEL_SLOT + FUEL_SLOTS_COUNT; public static final int FIRST_OUTPUT_SLOT = FIRST_INPUT_SLOT + INPUT_SLOTS_COUNT; private ItemStack[] itemStacks = new ItemStack[TOTAL_SLOTS_COUNT]; /** The number of burn ticks remaining on the current piece of fuel */ private int [] burnTimeRemaining = new int[FUEL_SLOTS_COUNT]; /** The initial fuel value of the currently burning fuel (in ticks of burn duration) */ private int [] burnTimeInitialValue = new int[FUEL_SLOTS_COUNT]; /**The number of ticks the current item has been cooking*/ private short cookTime; /**The number of ticks required to cook an item*/ private static final short COOK_TIME_FOR_COMPLETION = 200; // vanilla value is 200 = 10 seconds private int cachedNumberOfBurningSlots = -1; /** * Returns the amount of fuel remaining on the currently burning item in the given fuel slot. * @fuelSlot the number of the fuel slot (0..3) * @return fraction remaining, between 0 - 1 */ public double fractionOfFuelRemaining(int fuelSlot) { if (burnTimeInitialValue[fuelSlot] <= 0 ) return 0; double fraction = burnTimeRemaining[fuelSlot] / (double)burnTimeInitialValue[fuelSlot]; return MathHelper.clamp_double(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 (burnTimeRemaining[fuelSlot] <= 0 ) return 0; return burnTimeRemaining[fuelSlot] / 20; // 20 ticks per second } /** * 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 : burnTimeRemaining) { if (burnTime > 0) ++burningCount; } return burningCount; } /** * Returns the amount of cook time completed on the currently cooking item. * @return fraction remaining, between 0 - 1 */ public double fractionOfCookTimeComplete() { double fraction = cookTime / (double)COOK_TIME_FOR_COMPLETION; return MathHelper.clamp_double(fraction, 0.0, 1.0); } // 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 any of the items have finished smelting // It runs both on the server and the client. @Override public void update() { // If there is nothing to smelt or there is no room in the output, reset cookTime and return if (canSmelt()) { int numberOfFuelBurning = burnFuel(); // If fuel is available, keep cooking the item, otherwise start "uncooking" it at double speed if (numberOfFuelBurning > 0) { cookTime += numberOfFuelBurning; } else { cookTime -= 2; } if (cookTime < 0) cookTime = 0; // If cookTime has reached maxCookTime smelt the item and reset cookTime if (cookTime >= COOK_TIME_FOR_COMPLETION) { smeltItem(); cookTime = 0; } } else { cookTime = 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(); if (cachedNumberOfBurningSlots != numberBurning) { cachedNumberOfBurningSlots = numberBurning; if (worldObj.isRemote) { IBlockState iblockstate = this.worldObj.getBlockState(pos); final int FLAGS = 3; // I'm not sure what these flags do, exactly. worldObj.notifyBlockUpdate(pos, iblockstate, iblockstate, FLAGS); } worldObj.checkLightFor(EnumSkyBlock.BLOCK, pos); } } /** * for each fuel slot: decreases the burn time, checks if burnTimeRemaining = 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; // Iterate over all the fuel slots for (int i = 0; i < FUEL_SLOTS_COUNT; i++) { int fuelSlotNumber = i + FIRST_FUEL_SLOT; if (burnTimeRemaining[i] > 0) { --burnTimeRemaining[i]; ++burningCount; } if (burnTimeRemaining[i] == 0) { if (itemStacks[fuelSlotNumber] != null && getItemBurnTime(itemStacks[fuelSlotNumber]) > 0) { // If the stack in this slot is not null and is fuel, set burnTimeRemaining & burnTimeInitialValue to the // item's burn time and decrease the stack size burnTimeRemaining[i] = burnTimeInitialValue[i] = getItemBurnTime(itemStacks[fuelSlotNumber]); --itemStacks[fuelSlotNumber].stackSize; ++burningCount; inventoryChanged = true; // If the stack size now equals 0 set the slot contents to the items container item. This is for fuel // items such as lava buckets so that the bucket is not consumed. If the item dose not have // a container item getContainerItem returns null which sets the slot contents to null if (itemStacks[fuelSlotNumber].stackSize == 0) { itemStacks[fuelSlotNumber] = itemStacks[fuelSlotNumber].getItem().getContainerItem(itemStacks[fuelSlotNumber]); } } } } if (inventoryChanged) markDirty(); return burningCount; } /** * Check if any of the input items are smeltable and there is sufficient space in the output slots * @return true if smelting is possible */ private boolean canSmelt() {return smeltItem(false);} /** * Smelt an input item into an output slot, if possible */ private void smeltItem() {smeltItem(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 false if no items can be smelted, true otherwise */ private boolean smeltItem(boolean performSmelt) { Integer firstSuitableInputSlot = null; Integer firstSuitableOutputSlot = null; ItemStack result = null; // finds the first input slot which is smeltable and whose result fits into an output slot (stacking if possible) for (int inputSlot = FIRST_INPUT_SLOT; inputSlot < FIRST_INPUT_SLOT + INPUT_SLOTS_COUNT; inputSlot++) { if (itemStacks[inputSlot] != null) { result = getSmeltingResultForItem(itemStacks[inputSlot]); if (result != null) { // find the first suitable output slot- either empty, or with identical item that has enough space for (int outputSlot = FIRST_OUTPUT_SLOT; outputSlot < FIRST_OUTPUT_SLOT + OUTPUT_SLOTS_COUNT; outputSlot++) { ItemStack outputStack = itemStacks[outputSlot]; if (outputStack == null) { firstSuitableInputSlot = inputSlot; firstSuitableOutputSlot = outputSlot; break; } if (outputStack.getItem() == result.getItem() && (!outputStack.getHasSubtypes() || outputStack.getMetadata() == outputStack.getMetadata()) && ItemStack.areItemStackTagsEqual(outputStack, result)) { int combinedSize = itemStacks[outputSlot].stackSize + result.stackSize; if (combinedSize <= getInventoryStackLimit() && combinedSize <= itemStacks[outputSlot].getMaxStackSize()) { firstSuitableInputSlot = inputSlot; firstSuitableOutputSlot = outputSlot; break; } } } if (firstSuitableInputSlot != null) break; } } } if (firstSuitableInputSlot == null) return false; if (!performSmelt) return true; // alter input and output itemStacks[firstSuitableInputSlot].stackSize--; if (itemStacks[firstSuitableInputSlot].stackSize <=0) itemStacks[firstSuitableInputSlot] = null; if (itemStacks[firstSuitableOutputSlot] == null) { itemStacks[firstSuitableOutputSlot] = result.copy(); // Use deep .copy() to avoid altering the recipe } else { itemStacks[firstSuitableOutputSlot].stackSize += result.stackSize; } markDirty(); return true; } // returns the smelting result for the given stack. Returns null if the given stack can not be smelted public static ItemStack getSmeltingResultForItem(ItemStack stack) { return FurnaceRecipes.instance().getSmeltingResult(stack); } // returns the number of ticks the given item will burn. Returns 0 if the given item is not a valid fuel public static short getItemBurnTime(ItemStack stack) { int burntime = TileEntityFurnace.getItemBurnTime(stack); // just use the vanilla values return (short)MathHelper.clamp_int(burntime, 0, Short.MAX_VALUE); } // Gets the number of slots in the inventory @Override public int getSizeInventory() { return itemStacks.length; } // Gets the stack in the given slot @Override public ItemStack getStackInSlot(int i) { return itemStacks[i]; } /** * Removes some of the units from itemstack in the given slot, and returns as a separate itemstack * @param slotIndex the slot number to remove the items from * @param count the number of units to remove * @return a new itemstack containing the units removed from the slot */ @Override public ItemStack decrStackSize(int slotIndex, int count) { ItemStack itemStackInSlot = getStackInSlot(slotIndex); if (itemStackInSlot == null) return null; ItemStack itemStackRemoved; if (itemStackInSlot.stackSize <= count) { itemStackRemoved = itemStackInSlot; setInventorySlotContents(slotIndex, null); } else { itemStackRemoved = itemStackInSlot.splitStack(count); if (itemStackInSlot.stackSize == 0) { setInventorySlotContents(slotIndex, null); } } markDirty(); return itemStackRemoved; } // overwrites the stack in the given slotIndex with the given stack @Override public void setInventorySlotContents(int slotIndex, ItemStack itemstack) { itemStacks[slotIndex] = itemstack; if (itemstack != null && itemstack.stackSize > getInventoryStackLimit()) { itemstack.stackSize = getInventoryStackLimit(); } markDirty(); } // This is the maximum number if items allowed in each slot // This only affects things such as hoppers trying to insert items you need to use the container to enforce this for players // inserting items via the gui @Override public int getInventoryStackLimit() { return 64; } // 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 @Override public boolean isUseableByPlayer(EntityPlayer player) { if (this.worldObj.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; } // 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 fuel slots static public boolean isItemValidForInputSlot(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 fuel slots static public boolean isItemValidForOutputSlot(ItemStack itemStack) { return false; } //------------------------------ // 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 NBTTagCompound writeToNBT(NBTTagCompound parentNBTTagCompound) { super.writeToNBT(parentNBTTagCompound); // The super call is required to save and load the tiles location // // Save the stored item stacks // to use an analogy with Java, this code generates an array of hashmaps // The itemStack in each slot is converted to an NBTTagCompound, which is effectively a hashmap of key->value pairs such // as slot=1, id=2353, count=1, etc // Each of these NBTTagCompound are then inserted into NBTTagList, which is similar to an array. NBTTagList dataForAllSlots = new NBTTagList(); for (int i = 0; i < this.itemStacks.length; ++i) { if (this.itemStacks[i] != null) { NBTTagCompound dataForThisSlot = new NBTTagCompound(); dataForThisSlot.setByte("Slot", (byte) i); this.itemStacks[i].writeToNBT(dataForThisSlot); dataForAllSlots.appendTag(dataForThisSlot); } } // the array of hashmaps is then inserted into the parent hashmap for the container parentNBTTagCompound.setTag("Items", dataForAllSlots); // Save everything else parentNBTTagCompound.setShort("CookTime", cookTime); parentNBTTagCompound.setTag("burnTimeRemaining", new NBTTagIntArray(burnTimeRemaining)); parentNBTTagCompound.setTag("burnTimeInitial", new NBTTagIntArray(burnTimeInitialValue)); return parentNBTTagCompound; } // This is where you load the data that you saved in writeToNBT @Override public void readFromNBT(NBTTagCompound nbtTagCompound) { super.readFromNBT(nbtTagCompound); // The super call is required to save and load the tiles location final byte NBT_TYPE_COMPOUND = 10; // See NBTBase.createNewByType() for a listing NBTTagList dataForAllSlots = nbtTagCompound.getTagList("Items", NBT_TYPE_COMPOUND); Arrays.fill(itemStacks, null); // set all slots to empty for (int i = 0; i < dataForAllSlots.tagCount(); ++i) { NBTTagCompound dataForOneSlot = dataForAllSlots.getCompoundTagAt(i); byte slotNumber = dataForOneSlot.getByte("Slot"); if (slotNumber >= 0 && slotNumber < this.itemStacks.length) { this.itemStacks[slotNumber] = ItemStack.loadItemStackFromNBT(dataForOneSlot); } } // Load everything else. Trim the arrays (or pad with 0) to make sure they have the correct number of elements cookTime = nbtTagCompound.getShort("CookTime"); burnTimeRemaining = Arrays.copyOf(nbtTagCompound.getIntArray("burnTimeRemaining"), FUEL_SLOTS_COUNT); burnTimeInitialValue = Arrays.copyOf(nbtTagCompound.getIntArray("burnTimeInitial"), FUEL_SLOTS_COUNT); cachedNumberOfBurningSlots = -1; } // // 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 SPacketUpdateTileEntity getUpdatePacket() { NBTTagCompound updateTagDescribingTileEntityState = getUpdateTag(); final int METADATA = 0; return new SPacketUpdateTileEntity(this.pos, METADATA, updateTagDescribingTileEntityState); } @Override public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) { NBTTagCompound 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 NBTTagCompound getUpdateTag() { NBTTagCompound nbtTagCompound = new NBTTagCompound(); writeToNBT(nbtTagCompound); return nbtTagCompound; } /* Populates this TileEntity with information from the tag, used by vanilla to transmit from server to client Warning - although our onDataPacket() uses this method, vanilla also calls it directly, so don't remove it. */ @Override public void handleUpdateTag(NBTTagCompound tag) { this.readFromNBT(tag); } //------------------------ // set all slots to empty @Override public void clear() { Arrays.fill(itemStacks, null); } // will add a key for this container to the lang file so we can name it in the GUI @Override public String getName() { return "container.Crystallizer.name"; } @Override public boolean hasCustomName() { return false; } @Nullable @Override public ITextComponent getDisplayName() { return this.hasCustomName() ? new TextComponentString(this.getName()) : new TextComponentTranslation(this.getName()); } private static final byte COOK_FIELD_ID = 0; private static final byte FIRST_BURN_TIME_REMAINING_FIELD_ID = 1; private static final byte FIRST_BURN_TIME_INITIAL_FIELD_ID = FIRST_BURN_TIME_REMAINING_FIELD_ID + (byte)FUEL_SLOTS_COUNT; private static final byte NUMBER_OF_FIELDS = FIRST_BURN_TIME_INITIAL_FIELD_ID + (byte)FUEL_SLOTS_COUNT; @Override public int getField(int id) { if (id == COOK_FIELD_ID) return cookTime; if (id >= FIRST_BURN_TIME_REMAINING_FIELD_ID && id < FIRST_BURN_TIME_REMAINING_FIELD_ID + FUEL_SLOTS_COUNT) { return burnTimeRemaining[id - FIRST_BURN_TIME_REMAINING_FIELD_ID]; } if (id >= FIRST_BURN_TIME_INITIAL_FIELD_ID && id < FIRST_BURN_TIME_INITIAL_FIELD_ID + FUEL_SLOTS_COUNT) { return burnTimeInitialValue[id - FIRST_BURN_TIME_INITIAL_FIELD_ID]; } System.err.println("Invalid field ID in TileInventorySmelting.getField:" + id); return 0; } @Override public void setField(int id, int value) { if (id == COOK_FIELD_ID) { cookTime = (short)value; } else if (id >= FIRST_BURN_TIME_REMAINING_FIELD_ID && id < FIRST_BURN_TIME_REMAINING_FIELD_ID + FUEL_SLOTS_COUNT) { burnTimeRemaining[id - FIRST_BURN_TIME_REMAINING_FIELD_ID] = value; } else if (id >= FIRST_BURN_TIME_INITIAL_FIELD_ID && id < FIRST_BURN_TIME_INITIAL_FIELD_ID + FUEL_SLOTS_COUNT) { burnTimeInitialValue[id - FIRST_BURN_TIME_INITIAL_FIELD_ID] = value; } else { System.err.println("Invalid field ID in TileInventorySmelting.setField:" + id); } } @Override public int getFieldCount() { return NUMBER_OF_FIELDS; } @Override public boolean isItemValidForSlot(int slotIndex, ItemStack itemstack) { return false; } /** * This method removes the entire contents of the given slot and returns it. * Used by containers such as crafting tables which return any items in their slots when you close the GUI * @param slotIndex * @return */ @Override public ItemStack removeStackFromSlot(int slotIndex) { ItemStack itemStack = getStackInSlot(slotIndex); if (itemStack != null) setInventorySlotContents(slotIndex, null); return itemStack; } @Override public void openInventory(EntityPlayer player) {} @Override public void closeInventory(EntityPlayer player) {} } Not new to java >> New to modding.
November 20, 20168 yr You need to register a name for your TileEntity class with GameRegistry.registerTileEntity in preInit. Please don't PM me to ask for help. Asking your question in a public thread preserves it for people who are having the same problem in the future.
November 20, 20168 yr Author Thanks! My registry was in ServerProxy instead of common! Not new to java >> New to modding.
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.