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.init.Items; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; 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.EnumSkyBlock; import net.minecraft.world.World; import com.arkcraft.lib.LogHelper; import com.arkcraft.module.blocks.common.general.BlockCropPlot; import com.arkcraft.module.crafting.common.config.ModuleItemBalance; import com.arkcraft.module.items.common.general.ItemARKSeed; import com.arkcraft.module.items.common.general.ItemFeces; /** * @author wildbill22 */ public class TileInventoryCropPlot extends TileEntity implements IInventory, IUpdatePlayerListBox { public static final int WATER_SLOTS_COUNT = 1; public static final int FERTILIZER_SLOTS_COUNT = 6; public static final int SEED_SLOTS_COUNT = 1; public static final int OUTPUT_SLOTS_COUNT = 1; public static final int TOTAL_SLOTS_COUNT = WATER_SLOTS_COUNT + FERTILIZER_SLOTS_COUNT + SEED_SLOTS_COUNT + OUTPUT_SLOTS_COUNT; public static final int WATER_SLOT = 0; public static final int SEED_SLOT = 1; public static final int FIRST_FERTILIZER_SLOT = 2; public static final int BERRY_SLOT = 8; public static final int FIRST_OUTPUT_SLOT = WATER_SLOTS_COUNT + FERTILIZER_SLOTS_COUNT + SEED_SLOTS_COUNT; // 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 grow ticks remaining for the water */ private short waterTimeRemaining; /** * The initial grow ticks value of one bucket of water (in ticks of grow * duration) */ private short waterTimeInitialValue = (short) ModuleItemBalance.CROP_PLOT.SECONDS_OF_WATER_PER_BUCKET; // vanilla // value // is // 1080 // = // 18 // minutes /** * The maximum amount of water allowed in reservoir */ private short MAXIMUM_WATER_TIME = (short) (waterTimeInitialValue * 5); // maximum // is // 32,767 // for // a // short /** * The number of ticks the current item has been growing */ private short growTime; /** * The number of ticks required to grow a berry */ private static final short GROW_TIME_FOR_BERRY = (short) ModuleItemBalance.CROP_PLOT.GROW_TIME_FOR_BERRY; // vanilla // value // is // 45 // seconds /** * The growth stage, 5 is fruitling */ private short growthStage = 0; private int cachedNumberOfFertilizerSlots = -1; /** * Returns the amount of fertilizer remaining on the currently burning item * in the given fertilizer slot. * * @param fertilizerSlot * the number of the fertilizer slot (0..5) * @return fraction remaining, between 0 - 1 */ public double fractionOfFertilizerRemaining(int fertilizerSlot) { if (itemStacks[fertilizerSlot] == null) { return 0.0D; } double fraction = secondsOfFertilizerRemaining(fertilizerSlot) / itemStacks[fertilizerSlot] .getMaxDamage(); return MathHelper.clamp_double(fraction, 0.0, 1.0); } /** * return the remaining grow time of the fertilizer in the given slot * * @param fertilizerSlot * the number of the fertilizer slot (0..5) * @return seconds remaining */ public int secondsOfFertilizerRemaining(int fertilizerSlot) { if (itemStacks[fertilizerSlot] != null) { return itemStacks[fertilizerSlot].getMaxDamage() - itemStacks[fertilizerSlot] .getItemDamage(); } else { return 0; } } /** * Get the number of slots which have fertilizer burning in them. * * @return number of slots with burning fertilizer, 0 - * FERTILIZER_SLOTS_COUNT */ public int numberOfBurningFertilizerSlots() { int burningCount = 0; for (int fertilizerSlot = FIRST_FERTILIZER_SLOT; fertilizerSlot < FIRST_FERTILIZER_SLOT + FERTILIZER_SLOTS_COUNT; fertilizerSlot++) { if (itemStacks[fertilizerSlot] != null) { ++burningCount; } } return burningCount; } /** * Returns the amount of grow time completed on the currently growing item. * * @return fraction remaining, between 0 - 1 */ public double fractionOfGrowTimeComplete() { // if (growthStage < 5) { // return 0.0D; // } else { double fraction = growTime / (double) GROW_TIME_FOR_BERRY; 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 fertilizer has run out, and if so turn the crop plot "off" // and slowly un-grow the current item (if any) // - see if any of the items have finished growing and ready to harvest // It runs both on the server and the client. @Override public void update() { if (tick >= 0) { tick--; return; } else { tick = 20; } // If there is no seed or water, or there is no room in the output, // reset growTime and return if (canHarvest()) { int numberOfFertilizerBurning = burnFertilizer(); // If fertilizer is available, keep growing the item, otherwise // start "un-growing" it at double speed if (numberOfFertilizerBurning > 0) { growTime += numberOfFertilizerBurning; } else { growTime -= 2; increaseStackDamage(itemStacks[SEED_SLOT]); } if (growTime < 0) { growTime = 0; } if (growthStage < 5) { setGrowthStage(true); } // If growTime has reached maximum, harvest a berry and reset // growTime if (growthStage == 5 && growTime >= GROW_TIME_FOR_BERRY) { // LogHelper.info("TileInventoryCropPlot: About to harvest a berry on " // + (FMLCommonHandler.instance().getEffectiveSide() == // Side.CLIENT ? "client" : "server")); harvestBerry(); growTime = 0; } } else { setGrowthStage(false); } int numberBurning = numberOfBurningFertilizerSlots(); if (cachedNumberOfFertilizerSlots != numberBurning) { cachedNumberOfFertilizerSlots = numberBurning; } } /** * boolean grow - if false, set growthStage back to 0 */ private void setGrowthStage(boolean grow) { short prevGrowthStage = growthStage; if (grow) { switch (growthStage) { // not seeded case 0: growthStage++; break; // seeded case 1: if (growTime > ModuleItemBalance.CROP_PLOT.SECONDS_UNTIL_FRUITLING / 4) { growthStage++; } break; // seedling case 2: if (growTime > ModuleItemBalance.CROP_PLOT.SECONDS_UNTIL_FRUITLING / 3) { growthStage++; } break; // midling case 3: if (growTime > ModuleItemBalance.CROP_PLOT.SECONDS_UNTIL_FRUITLING / 2) { growthStage++; } break; // growthling case 4: if (growTime > ModuleItemBalance.CROP_PLOT.SECONDS_UNTIL_FRUITLING) { growthStage++; growTime = 1; } break; // fruitling default: LogHelper.info("TileInventoryCropPlot: setGrowthStage: Oops!"); } } else { growthStage = 0; growTime = 0; } // when the growth stage 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 if (prevGrowthStage != growthStage) { IBlockState state = worldObj.getBlockState(pos); worldObj.setBlockState(pos, state.withProperty(BlockCropPlot.AGE, Integer.valueOf(growthStage)), 2); if (worldObj.isRemote) { worldObj.markBlockForUpdate(pos); } worldObj.checkLightFor(EnumSkyBlock.BLOCK, pos); LogHelper.info("TileInventoryCropPlot: Growth stage is now " + growthStage); } } /** * for each fertilizer slot: decreases the burn time, checks if * burnTimeRemaining = 0 and tries to consume a new piece of fertilizer if * one is available * * @return the number of fertilizer slots which are burning */ private int burnFertilizer() { int burningCount = 0; boolean inventoryChanged = false; // Consume any water buckets if (itemStacks[WATER_SLOT] != null && itemStacks[WATER_SLOT].getItem() == Items.water_bucket) { itemStacks[WATER_SLOT] = itemStacks[WATER_SLOT].getItem().getContainerItem( itemStacks[WATER_SLOT]); waterTimeRemaining += waterTimeInitialValue; if (waterTimeRemaining > MAXIMUM_WATER_TIME) { waterTimeRemaining = MAXIMUM_WATER_TIME; } inventoryChanged = true; } if (worldObj.isRaining()) { waterTimeRemaining++; if (waterTimeRemaining > MAXIMUM_WATER_TIME) { waterTimeRemaining = MAXIMUM_WATER_TIME; } } else if (waterTimeRemaining > 0) { waterTimeRemaining--; } // Iterate over all the fuel slots for (int i = 0; i < FERTILIZER_SLOTS_COUNT; i++) { int fertilizerSlotNumber = i + FIRST_FERTILIZER_SLOT; if (itemStacks[fertilizerSlotNumber] != null) { if (increaseStackDamage(itemStacks[fertilizerSlotNumber])) { itemStacks[fertilizerSlotNumber] = null; inventoryChanged = true; } ++burningCount; break; // Just use one fertilizer at a time } } if (inventoryChanged) { markDirty(); } return burningCount; } /** * @return growth stage */ public int getGrowthStage() { return this.growthStage; } /** * Check if the plot is harvestable and there is sufficient space in the * output slots * * @return true if harvesting a berry is possible */ private boolean canHarvest() { return harvestBerry(false); } /** * Harvest a berry from the seed, if possible */ private void harvestBerry() { harvestBerry(true); } /** * checks that there is an item to be harvested in one of the input slots * and that there is room for the result in the output slots If desired, * performs the berry harvest * * @param harvestBerry * If true, harvest a berry. If false, check whether harvesting * is possible, but don't change the inventory * @return false if no berry can be harvested, true otherwise */ private boolean harvestBerry(boolean harvestBerry) { Integer firstSuitableInputSlot = null; Integer firstSuitableOutputSlot = null; ItemStack result = null; boolean canHarvestBerry = true; // finds the first input slot which is smeltable and whose result fits // into an output slot (stacking if possible) for (int inputSlot = SEED_SLOT; inputSlot < SEED_SLOT + SEED_SLOTS_COUNT; inputSlot++) { if (itemStacks[inputSlot] != null) { result = getGrowingResultForItem(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; } } } } // Damage seed if present and no water if (firstSuitableInputSlot == null) { return false; } if (itemStacks[WATER_SLOT] != null) { if (itemStacks[WATER_SLOT].getItem() != Items.water_bucket && waterTimeRemaining <= 0) { canHarvestBerry = false; } } else if (waterTimeRemaining <= 0) { waterTimeRemaining = 0; canHarvestBerry = false; } // TODO: Add code to prevent more than one seed being in the seed slot if (!canHarvestBerry) { if (increaseStackDamage(itemStacks[firstSuitableInputSlot])) { ; } itemStacks[firstSuitableInputSlot] = null; return false; } // If berry seed was just put it, set stage to seeded if (growthStage == 0) { growthStage = 1; } // If true, we harvest berry if (!harvestBerry) { return true; } // alter output slot 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 growth result for the given stack. Returns null if the given // stack can not be grown public static ItemStack getGrowingResultForItem(ItemStack stack) { // TODO: Can't return a seed with only one output slot. Will have to // wait for a new design with more output slots. // Random rand = new Random(); // if (rand.nextInt(100) == 0) // return stack; // else return ItemARKSeed.getBerryForSeed(stack); } // returns the number of ticks the given item will grow. Returns 0 if the // given item is not a valid fertilizer public static short getItemGrowTime(ItemStack stack) { int growtime = ItemFeces.getItemGrowTime(stack); return (short) MathHelper.clamp_int(growtime, 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 "container.crop_plot.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; } @Override public ItemStack getStackInSlotOnClosing(int index) { // TODO Auto-generated method stub 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) { // TODO Auto-generated method stub } @Override public void closeInventory(EntityPlayer player) { // TODO Auto-generated method stub } @Override public boolean isItemValidForSlot(int index, ItemStack stack) { // TODO Auto-generated method stub return false; } @Override public void clear() { Arrays.fill(itemStacks, null); } // Returns double between 0 and 1 representing % full level public double fractionWaterLevelRemaining() { double fraction = waterTimeRemaining / (double) MAXIMUM_WATER_TIME; return MathHelper.clamp_double(fraction, 0.0, 1.0); } // Return true if stack is a valid fertilizer for the crop plot public boolean isItemValidForFertilizerSlot(ItemStack stack) { if (stack != null && stack.getItem() instanceof ItemFeces) { return true; } else { return false; } } // Return true if stack is a water for the crop plot public boolean isItemValidForWaterSlot(ItemStack stack) { if (stack != null && stack.getItem() == Items.water_bucket) { return true; } else { return false; } } // Return true if stack is a valid seed for the crop plot public boolean isItemValidForSeedSlot(ItemStack stack) { if (stack != null && stack.getItem() instanceof ItemARKSeed) { return true; } else { 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 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("growthStage", (short) growthStage); parentNBTTagCompound.setShort("waterTimeRemaining", (short) waterTimeRemaining); parentNBTTagCompound.setShort("growTime", growTime); LogHelper.info("TileInventoryCropPlot: 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 growthStage = nbtTagCompound.getShort("growthStage"); waterTimeRemaining = nbtTagCompound.getShort("waterTimeRemaining"); growTime = nbtTagCompound.getShort("growTime"); cachedNumberOfFertilizerSlots = -1; LogHelper.info("TileInventoryCropPlot: 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 GROWTH_STAGE_FIELD_ID = 0; private static final byte WATER_FIELD_ID = 1; private static final byte GROW_FIELD_ID = 2; private static final byte NUMBER_OF_FIELDS = 3; @Override public int getField(int id) { if (id == GROWTH_STAGE_FIELD_ID) { return growthStage; } if (id == WATER_FIELD_ID) { return waterTimeRemaining; } if (id == GROW_FIELD_ID) { return growTime; } System.err.println("Invalid field ID in TileInventoryCropPlot.getField:" + id); return 0; } @Override public void setField(int id, int value) { if (id == GROWTH_STAGE_FIELD_ID) { growthStage = (short) value; } else if (id == WATER_FIELD_ID) { waterTimeRemaining = (short) value; } else if (id == GROW_FIELD_ID) { growTime = (short) value; } else { System.err.println("Invalid field ID in TileInventoryCropPlot.setField:" + id); } } @Override public int getFieldCount() { return NUMBER_OF_FIELDS; } /** * 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; } }