package com.arkcraft.module.blocks.common.tile; import java.util.Arrays; 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.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagIntArray; import net.minecraft.nbt.NBTTagList; import net.minecraft.network.NetworkManager; import net.minecraft.network.Packet; import net.minecraft.network.play.server.S35PacketUpdateTileEntity; import net.minecraft.server.gui.IUpdatePlayerListBox; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.BlockPos; import net.minecraft.util.ChatComponentText; import net.minecraft.util.ChatComponentTranslation; import net.minecraft.util.IChatComponent; import net.minecraft.util.MathHelper; import net.minecraft.world.World; import com.arkcraft.lib.LogHelper; import com.arkcraft.module.crafting.common.config.ModuleItemBalance; import com.arkcraft.module.items.ARKCraftItems; import com.arkcraft.module.items.common.general.ItemFeces; /** * @author wildbill22 */ public class TileInventoryCompostBin extends TileEntity implements IInventory, IUpdatePlayerListBox { public static final int COMPOST_SLOTS_COUNT = 8; public static final int TOTAL_SLOTS_COUNT = COMPOST_SLOTS_COUNT; public static final int LAST_INVENTORY_SLOT = TOTAL_SLOTS_COUNT - 1; public static final int FIRST_COMPOST_SLOT = 0; // Create and initialize the itemStacks variable that will store store the // itemStacks private ItemStack[] itemStacks = new ItemStack[TOTAL_SLOTS_COUNT]; // Other class variables int tick = 20; /** * The number of composting seconds remaining on the current piece of thatch */ private int[] compostTimeRemaining = new int[COMPOST_SLOTS_COUNT]; /** * The initial composting value of the currently burning thatch (in seconds * of composting duration) */ private static int compostTimeInitialValue = ModuleItemBalance.COMPOST_BIN.COMPOST_TIME_FOR_THATCH; /** * Seconds of thatch burn time remaining for the item in a slot */ public int secondsOfThatchRemaining(int i) { if (itemStacks[i] != null && getItemCompostTime(itemStacks[i]) > 0) { if (compostTimeRemaining[i] > 0) { return compostTimeRemaining[i]; } } return 0; } /** * The number of seconds the current item has been composting */ private short compostTime; /** * The number of seconds required to create a fertilizer */ private static final short COMPOST_TIME_FOR_FECES = (short) ModuleItemBalance.COMPOST_BIN.COMPOST_TIME_FOR_FECES; /** * Returns double between 0 and 1 representing % complete */ public double getFractionCompostTimeComplete() { double fraction = compostTime / (double) COMPOST_TIME_FOR_FECES; return MathHelper.clamp_double(fraction, 0.0, 1.0); } /** * The number of compost slots with feces */ private int cachedNumberOfDecomposingSlots = -1; /** * This method is called every tick to update the tile entity, i.e. - * decompose the feces in the compost bin - See if there is thatch and at * least one empty slot, if there is burn the thatch and increment compost * time - Create one fertilizer when compost time is reached Runs on both * the server and client */ @Override public void update() { if (tick >= 0) { tick--; return; } else { tick = 20; } int numberDecomposing = decomposeFeces(); if (cachedNumberOfDecomposingSlots != numberDecomposing) { cachedNumberOfDecomposingSlots = numberDecomposing; } // If there is no feces decomposing or there is no thatch or room in the // output, don't burn the thatch if (isThatchPresent() && numberDecomposing > 0 && canCompost()) { burnThatch(); compostTime += numberDecomposing; // If compostTime is reached, create a fertilizer and reset // compostTime if (compostTime >= COMPOST_TIME_FOR_FECES) { // LogHelper.info("TileInventoryCompostBin: About to create fertilizer on " // + (FMLCommonHandler.instance().getEffectiveSide() == // Side.CLIENT ? "client" : "server")); compostFertilizer(); compostTime = 0; } } else { compostTime = 0; } } /** * for each slot with feces: decreases the burn time, checks if * burnTimeRemaining = 0 and tries to consume a new piece of thatch if one * is available */ private int decomposeFeces() { int decomposingCount = 0; boolean inventoryChanged = false; // Iterate over all the compost bin slots for (int i = 0; i < COMPOST_SLOTS_COUNT; i++) { if (itemStacks[i] != null && getItemDecompostTime(itemStacks[i]) > 0 && itemStacks[i] .getItem() != ARKCraftItems.fertilizer) { if (increaseStackDamage(itemStacks[i])) { itemStacks[i] = null; inventoryChanged = true; } ++decomposingCount; } } if (inventoryChanged) { markDirty(); } return decomposingCount; } /** * for each slot with thatch: decreases the burn time, checks if * burnTimeRemaining = 0 and tries to consume a new piece of thatch if one * is available */ private void burnThatch() { boolean inventoryChanged = false; // Iterate over all the compost bin slots for (int i = 0; i < COMPOST_SLOTS_COUNT; i++) { if (itemStacks[i] != null && getItemCompostTime(itemStacks[i]) > 0) { if (compostTimeRemaining[i] > 0) { compostTimeRemaining[i]--; if (compostTimeRemaining[i] <= 0) { itemStacks[i].stackSize--; inventoryChanged = true; if (itemStacks[i].stackSize == 0) { itemStacks[i] = null; } else { compostTimeRemaining[i] = compostTimeInitialValue; } } } // Initialize a new stack just added else { compostTimeRemaining[i] = compostTimeInitialValue; } break; // Just burn one thatch at a time } } if (inventoryChanged) { markDirty(); } } /** * Check each slot for thatch */ private boolean isThatchPresent() { // Iterate over all the compost bin slots for (int i = 0; i < COMPOST_SLOTS_COUNT; i++) { if (itemStacks[i] != null && getItemCompostTime(itemStacks[i]) > 0) { return true; } } return false; } /** * Check if the compost bin is composting and there is sufficient space in * the output slots * * @return true if harvesting a berry is possible */ private boolean canCompost() { return compostFeces(false); } /** * Harvest a berry from the seed, if possible */ private void compostFertilizer() { compostFeces(true); } /** * checks that there is an item to be composted in one of the input slots * and that there is room for the result in the output slots If desired, * performs the compost to fertilizer * * @param compostFeces * If true, harvest a berry. If false, check whether harvesting * is possible, but don't change the inventory * @return false if no fertilizer can be composted, true otherwise */ private boolean compostFeces(boolean compostFeces) { Integer firstSuitableOutputSlot = null; // find the first suitable output slot- either empty, or with identical // item that has enough space for (int outputSlot = LAST_INVENTORY_SLOT; outputSlot > FIRST_COMPOST_SLOT; outputSlot--) { ItemStack outputStack = itemStacks[outputSlot]; // Empty slot? if (outputStack == null) { firstSuitableOutputSlot = outputSlot; break; } // Fertilizer item and with enough space? if (itemStacks[outputSlot].getItem() == ARKCraftItems.fertilizer) { int combinedSize = itemStacks[outputSlot].stackSize + 1; if (combinedSize <= getInventoryStackLimit() && combinedSize <= itemStacks[outputSlot] .getMaxStackSize()) { firstSuitableOutputSlot = outputSlot; break; } } } // If no suitable output slot, return false if (firstSuitableOutputSlot == null) { return false; } // If true, we create a new fertilizer if (!compostFeces) { return true; } // alter output slot if (itemStacks[firstSuitableOutputSlot] == null) { itemStacks[firstSuitableOutputSlot] = new ItemStack(ARKCraftItems.fertilizer); } else { itemStacks[firstSuitableOutputSlot].stackSize += 1; } markDirty(); return true; } /** * returns the number of seconds the given item will decompose. * * @param stack * @return Returns 0 if the given item is not a valid feces */ public static short getItemDecompostTime(ItemStack stack) { int growtime = 0; if (stack != null) { if (stack.getItem() instanceof ItemFeces) { growtime = ItemFeces.getItemGrowTime(stack); } } return (short) MathHelper.clamp_int(growtime, 0, Short.MAX_VALUE); } /** * returns the number of seconds the given item will decompose. * * @param stack * @return Returns 0 if the given item is not a valid thatching item */ public static short getItemCompostTime(ItemStack stack) { int compostTime = 0; if (stack != null) { if (stack.getItem() == ARKCraftItems.thatch) { compostTime = compostTimeInitialValue; } } return (short) MathHelper.clamp_int(compostTime, 0, Short.MAX_VALUE); } /** * Adds one one damage to stack * * @param itemStack * @return true if stack is destroyed */ private static boolean increaseStackDamage(ItemStack itemStack) { if (itemStack == null) { return true; } int itemDamage = itemStack.getItemDamage(); itemStack.setItemDamage(++itemDamage); if (itemStack.getItemDamage() >= itemStack.getItem().getMaxDamage()) { return true; } return false; } @Override public String getName() { return "tile.compost_bin.name"; } @Override public boolean hasCustomName() { return false; } @Override public IChatComponent getDisplayName() { return this.hasCustomName() ? new ChatComponentText(this.getName()) : new ChatComponentTranslation( this.getName()); } @Override public int getSizeInventory() { return itemStacks.length; } @Override public ItemStack getStackInSlot(int index) { return itemStacks[index]; } @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; } // Nothing to do, this is a furnace type inventory @Override public ItemStack getStackInSlotOnClosing(int index) { return null; } @Override public void setInventorySlotContents(int slotIndex, ItemStack itemstack) { itemStacks[slotIndex] = itemstack; if (itemstack != null && itemstack.stackSize > getInventoryStackLimit()) { itemstack.stackSize = getInventoryStackLimit(); } markDirty(); } @Override public int getInventoryStackLimit() { return 64; } @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; } @Override public void openInventory(EntityPlayer player) { } @Override public void closeInventory(EntityPlayer player) { } @Override public boolean isItemValidForSlot(int index, ItemStack stack) { return isItemValidForSlot(stack); } // Return true if stack is a valid item for the compost bin public boolean isItemValidForSlot(ItemStack stack) { if (stack != null) { // Feces? if (getItemDecompostTime(stack) > 0) { return true; } // Thatch? if (getItemCompostTime(stack) > 0) { return true; } } return false; } @Override public void clear() { Arrays.fill(itemStacks, null); } /** * Called from Chunk.setBlockIDWithMetadata, determines if this tile entity * should be re-created when the ID, or Metadata changes. Use with caution * as this will leave straggler TileEntities, or create conflicts with other * TileEntities if not used properly. * * @param world * Current world * @param pos * Tile's world position * @param oldID * The old ID of the block * @param newID * The new ID of the block (May be the same) * @return True to remove the old tile entity, false to keep it in tact {and * create a new one if the new values specify to} */ @Override public boolean shouldRefresh(World world, BlockPos pos, IBlockState oldState, IBlockState newSate) { 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 compost bin (burn time etc) and // the itemstacks in the inventory @Override public void 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("compostTime", compostTime); parentNBTTagCompound.setTag("compostTimeRemaining", new NBTTagIntArray(compostTimeRemaining)); LogHelper.info("TileInventoryCompostBin: Wrote inventory."); } // 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 compostTime = nbtTagCompound.getShort("compostTime"); compostTimeRemaining = Arrays.copyOf(nbtTagCompound.getIntArray("compostTimeRemaining"), COMPOST_SLOTS_COUNT); cachedNumberOfDecomposingSlots = -1; LogHelper.info("TileInventoryCompostBin: Read inventory."); } // When the world loads from disk, the server needs to send the TileEntity // information to the client // it uses getDescriptionPacket() and onDataPacket() to do this @Override public Packet getDescriptionPacket() { NBTTagCompound nbtTagCompound = new NBTTagCompound(); writeToNBT(nbtTagCompound); final int METADATA = 0; return new S35PacketUpdateTileEntity(this.pos, METADATA, nbtTagCompound); } @Override public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt) { readFromNBT(pkt.getNbtCompound()); } // Fields are used to send non-inventory information from the server to // interested clients // The container code caches the fields and sends the client any fields // which have changed. // The field ID is limited to byte, and the field value is limited to short. // (if you use more than this, they get cast down // in the network packets) // If you need more than this, or shorts are too small, use a custom packet // in your container instead. private static final byte COMPOST_FIELD_ID = 0; private static final byte FIRST_COMPOST_TIME_FIELD_ID = 1; private static final byte NUMBER_OF_FIELDS = FIRST_COMPOST_TIME_FIELD_ID + (byte) COMPOST_SLOTS_COUNT; @Override public int getField(int id) { if (id == COMPOST_FIELD_ID) { return compostTime; } if (id >= FIRST_COMPOST_TIME_FIELD_ID && id < NUMBER_OF_FIELDS) { return compostTimeRemaining[id - FIRST_COMPOST_TIME_FIELD_ID]; } System.err.println("Invalid field ID in TileInventoryCompost.getField:" + id); return 0; } @Override public void setField(int id, int value) { if (id == COMPOST_FIELD_ID) { compostTime = (short) value; } else if (id >= FIRST_COMPOST_TIME_FIELD_ID && id < NUMBER_OF_FIELDS) { compostTimeRemaining[id - FIRST_COMPOST_TIME_FIELD_ID] = value; } else { System.err.println("Invalid field ID in TileInventoryCompost.setField:" + id); } } @Override public int getFieldCount() { return NUMBER_OF_FIELDS; } }