package com.carpentersblocks.tileentity; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Random; import net.minecraft.block.Block; import net.minecraft.block.BlockDirectional; import net.minecraft.init.Blocks; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.nbt.NBTTagShort; import net.minecraft.network.NetworkManager; import net.minecraft.network.Packet; import net.minecraft.network.play.server.S35PacketUpdateTileEntity; import net.minecraft.tileentity.TileEntity; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; import com.carpentersblocks.block.BlockCoverable; import com.carpentersblocks.util.Attribute; import com.carpentersblocks.util.BlockProperties; import com.carpentersblocks.util.handler.DesignHandler; import com.carpentersblocks.util.protection.IProtected; import com.carpentersblocks.util.protection.ProtectedObject; import com.carpentersblocks.util.registry.FeatureRegistry; public class TEBase extends TileEntity implements IProtected { public static final String TAG_ATTR = "cbAttribute"; public static final String TAG_ATTR_LIST = "cbAttrList"; public static final String TAG_METADATA = "cbMetadata"; public static final String TAG_OWNER = "cbOwner"; public static final String TAG_CHISEL_DESIGN = "cbChiselDesign"; public static final String TAG_DESIGN = "cbDesign"; public static final byte[] ATTR_COVER = { 0, 1, 2, 3, 4, 5, 6 }; public static final byte[] ATTR_DYE = { 7, 8, 9, 10, 11, 12, 13 }; public static final byte[] ATTR_OVERLAY = { 14, 15, 16, 17, 18, 19, 20 }; public static final byte ATTR_ILLUMINATOR = 21; public static final byte ATTR_PLANT = 22; public static final byte ATTR_SOIL = 23; public static final byte ATTR_FERTILIZER = 24; public static final byte ATTR_UPGRADE = 25; /** Map holding all block attributes. */ protected Map<Byte, Attribute> cbAttrMap = new HashMap<Byte, Attribute>(); /** Chisel design for each side and base block. */ protected String[] cbChiselDesign = { "", "", "", "", "", "", "" }; /** Holds specific block information like facing, states, etc. */ protected int cbMetadata; /** Design name. */ protected String cbDesign = ""; /** Owner of tile entity. */ protected String cbOwner = ""; /** Indicates lighting calculations are underway. **/ protected static boolean calcLighting = false; /** Holds last stored metadata. **/ private int tempMetadata; /** The most recent light value of block. **/ private int lightValue = -1; /** Comment **/ @Override public void readFromNBT(NBTTagCompound nbt) { super.readFromNBT(nbt); cbAttrMap.clear(); if (nbt.hasKey("owner")) { TileEntityHelper.updateMappingsOnRead(this, nbt); } else { NBTTagList nbttaglist = nbt.getTagList(TAG_ATTR_LIST, 10); for (int idx = 0; idx < nbttaglist.tagCount(); ++idx) { NBTTagCompound nbt1 = nbttaglist.getCompoundTagAt(idx); Attribute attribute = Attribute.loadAttributeFromNBT(nbt1); if (attribute.getItemStack() != null) { attribute.getItemStack().stackSize = 1; // All ItemStacks pre-3.2.7 DEV R3 stored original stack sizes, reduce them here. byte attrId = (byte) (nbt1.getByte(TAG_ATTR) & 255); cbAttrMap.put(attrId, attribute); } } for (int idx = 0; idx < 7; ++idx) { cbChiselDesign[idx] = nbt.getString(TAG_CHISEL_DESIGN + "_" + idx); } // Handle 3.3.7 structure changes convertDataToInt(nbt); cbDesign = nbt.getString(TAG_DESIGN); cbOwner = nbt.getString(TAG_OWNER); } // Block either loaded or changed, update lighting and render state updateWorldAndLighting(); } @Override public void writeToNBT(NBTTagCompound nbt) { super.writeToNBT(nbt); NBTTagList tagList = new NBTTagList(); Iterator iterator = cbAttrMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); NBTTagCompound nbt1 = new NBTTagCompound(); nbt1.setByte(TAG_ATTR, (Byte) entry.getKey()); ((Attribute)entry.getValue()).writeToNBT(nbt1); tagList.appendTag(nbt1); } nbt.setTag(TAG_ATTR_LIST, tagList); for (int idx = 0; idx < 7; ++idx) { nbt.setString(TAG_CHISEL_DESIGN + "_" + idx, cbChiselDesign[idx]); } nbt.setInteger(TAG_METADATA, cbMetadata); nbt.setString(TAG_DESIGN, cbDesign); nbt.setString(TAG_OWNER, cbOwner); } /** * Handles data conversion from short to int for update 3.3.7. * * @param nbt the {@link NBTTagCompound} * @return <code>true</code> if data was converted */ private boolean convertDataToInt(NBTTagCompound nbt) { // 3.3.7 DEV converted cbMetadata to integer if (nbt.getTag(TAG_METADATA) instanceof NBTTagShort) { cbMetadata = nbt.getShort(TAG_METADATA); return true; } else { cbMetadata = nbt.getInteger(TAG_METADATA); return false; } } @Override /** * Overridden in a sign to provide the text. */ public Packet getDescriptionPacket() { NBTTagCompound nbt = new NBTTagCompound(); writeToNBT(nbt); return new S35PacketUpdateTileEntity(xCoord, yCoord, zCoord, 0, nbt); } @Override /** * Called when you receive a TileEntityData packet for the location this * TileEntity is currently in. On the client, the NetworkManager will always * be the remote server. On the server, it will be whomever is responsible for * sending the packet. * * @param net The NetworkManager the packet originated from * @param pkt The data packet */ public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt) { readFromNBT(pkt.func_148857_g()); } /** * 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 oldID The old ID of the block * @param newID The new ID of the block (May be the same) * @param oldMeta The old metadata of the block * @param newMeta The new metadata of the block (May be the same) * @param world Current world * @param x X Position * @param y Y Position * @param z Z Position * @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(Block oldBlock, Block newBlock, int oldMeta, int newMeta, World world, int x, int y, int z) { /* * This is a curious method. * * Essentially, when doing most block logic server-side, changes * to blocks will momentarily "flash" to their default state * when rendering client-side. This is most noticeable when adding * or removing covers for the first time. * * Making the tile entity refresh only when the block is first created * is not only reasonable, but fixes this behavior. */ return oldBlock != newBlock; } /** * Copies owner from TEBase object. */ public void copyOwner(final TEBase TE) { cbOwner = TE.getOwner(); markDirty(); } /** * Sets owner of tile entity. */ @Override public void setOwner(ProtectedObject obj) { cbOwner = obj.toString(); markDirty(); } @Override public String getOwner() { return cbOwner; } @Override /** * Determines if this TileEntity requires update calls. * @return True if you want updateEntity() to be called, false if not */ public boolean canUpdate() { return false; } public boolean hasAttribute(byte attrId) { return cbAttrMap.containsKey(attrId); } public ItemStack getAttribute(byte attrId) { Attribute attribute = cbAttrMap.get(attrId); if (attribute != null) { return attribute.getItemStack(); } return null; } public ItemStack getAttributeForDrop(byte attrId) { ItemStack itemStack = cbAttrMap.get(attrId).getItemStack(); // If cover, check for rotation and restore default metadata if (attrId <= ATTR_COVER[6]) { setDefaultMetadata(itemStack); } return itemStack; } /** * Will restore cover to default state before returning {@link ItemStack}. * <p> * Corrects log rotation, among other things. * * @param rand a {@link Random} reference * @param itemStack the {@link ItemStack} * @return the cover {@link ItemStack} in it's default state */ private ItemStack setDefaultMetadata(ItemStack itemStack) { Block block = BlockProperties.toBlock(itemStack); // Correct rotation metadata before dropping block if (BlockProperties.blockRotates(itemStack) || block instanceof BlockDirectional) { int dmgDrop = block.damageDropped(itemStack.getItemDamage()); Item itemDrop = block.getItemDropped(itemStack.getItemDamage(), getWorldObj().rand, /* Fortune */ 0); /* Check if block drops itself, and, if so, correct the damage value to the block's default. */ if (itemDrop != null && itemDrop.equals(itemStack.getItem()) && dmgDrop != itemStack.getItemDamage()) { itemStack.setItemDamage(dmgDrop); } } return itemStack; } public void addAttribute(byte attrId, ItemStack itemStack) { if (hasAttribute(attrId) || itemStack == null) { return; } // Reduce stack size to 1 and save attribute ItemStack reducedStack = ItemStack.copyItemStack(itemStack); reducedStack.stackSize = 1; cbAttrMap.put(attrId, new Attribute(reducedStack)); // Produce world events if specific attributes are set World world = getWorldObj(); Block block = BlockProperties.toBlock(itemStack); if (attrId < 7) { if (attrId == ATTR_COVER[6]) { int metadata = itemStack.getItemDamage(); world.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, metadata, 0); } world.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord, block); } else if (attrId == ATTR_PLANT | attrId == ATTR_SOIL) { world.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord, block); } if (attrId == ATTR_FERTILIZER) { /* Play sound when fertilizing plants.. though I've never heard it before. */ getWorldObj().playAuxSFX(2005, xCoord, yCoord, zCoord, 0); } updateWorldAndLighting(); markDirty(); } /** * Will remove the attribute from map once block drop is complete. * <p> * Should only be called externally by {@link BlockCoverable#onBlockEventReceived}. * * @param attrId */ public void onAttrDropped(byte attrId) { cbAttrMap.remove(attrId); updateWorldAndLighting(); markDirty(); } /** * Initiates block drop event, which will remove attribute from tile entity. * * @param attrId the attribute ID */ public void createBlockDropEvent(byte attrId) { getWorldObj().addBlockEvent(xCoord, yCoord, zCoord, getBlockType(), BlockCoverable.EVENT_ID_DROP_ATTR, attrId); } public void removeAttributes(int side) { createBlockDropEvent(ATTR_COVER[side]); createBlockDropEvent(ATTR_DYE[side]); createBlockDropEvent(ATTR_OVERLAY[side]); if (side == 6) { createBlockDropEvent(ATTR_ILLUMINATOR); } } /** * Returns whether block has pattern. */ public boolean hasChiselDesign(int side) { return DesignHandler.listChisel.contains(getChiselDesign(side)); } /** * Returns pattern. */ public String getChiselDesign(int side) { return cbChiselDesign[side]; } /** * Sets pattern. */ public boolean setChiselDesign(int side, String iconName) { if (!cbChiselDesign.equals(iconName)) { cbChiselDesign[side] = iconName; getWorldObj().markBlockForUpdate(xCoord, yCoord, zCoord); markDirty(); return true; } return false; } public void removeChiselDesign(int side) { if (!cbChiselDesign.equals("")) { cbChiselDesign[side] = ""; getWorldObj().markBlockForUpdate(xCoord, yCoord, zCoord); markDirty(); } } /** * Gets block-specific data. * * @return the data */ public int getData() { return cbMetadata; } /** * Sets block-specific data. */ public boolean setData(int data) { if (data != getData()) { cbMetadata = data; getWorldObj().markBlockForUpdate(xCoord, yCoord, zCoord); markDirty(); return true; } return false; } public boolean hasDesign() { return DesignHandler.getListForType(getBlockDesignType()).contains(cbDesign); } public String getDesign() { return cbDesign; } public boolean setDesign(String name) { if (!cbDesign.equals(name)) { cbDesign = name; getWorldObj().markBlockForUpdate(xCoord, yCoord, zCoord); markDirty(); return true; } return false; } public boolean removeDesign() { return setDesign(""); } public String getBlockDesignType() { String name = getBlockType().getUnlocalizedName(); return name.substring(new String("tile.blockCarpenters").length()).toLowerCase(); } public boolean setNextDesign() { return setDesign(DesignHandler.getNext(getBlockDesignType(), cbDesign)); } public boolean setPrevDesign() { return setDesign(DesignHandler.getPrev(getBlockDesignType(), cbDesign)); } /** * Sets block metadata without causing a render update. * <p> * As part of mimicking a cover block, the metadata must be changed * to better represent the cover properties. * <p> * This is normally followed up by calling {@link setMetadataFromCover}. */ public void setMetadata(int metadata) { tempMetadata = getWorldObj().getBlockMetadata(xCoord, yCoord, zCoord); getWorldObj().setBlockMetadataWithNotify(xCoord, yCoord, zCoord, metadata, 4); } /** * Restores default metadata for block from base cover. */ public void restoreMetadata() { getWorldObj().setBlockMetadataWithNotify(xCoord, yCoord, zCoord, tempMetadata, 4); } ///////////////////////////////////////////////////////////// // Code below implemented strictly for light updates ///////////////////////////////////////////////////////////// /** * Grabs light value from cache. * <p> * If not cached, will calculate value first. * * @return the light value */ public int getLightValue() { if (lightValue == -1 && !calcLighting) { updateCachedLighting(); } return lightValue; } /** * Returns the current block light value. This is the only method * that will grab the tile entity to calculate lighting, which * is a very expensive operation to call while rendering, as it is * called often. * * @param blockAccess the {@link IBlockAccess} object * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate * @return a light value from 0 to 15 */ protected int getDynamicLightValue() { int value = 0; if (FeatureRegistry.enableIllumination && hasAttribute(ATTR_ILLUMINATOR)) { return 15; } else { // Find greatest light output from attributes calcLighting = true; Iterator it = cbAttrMap.entrySet().iterator(); while (it.hasNext()) { Map.Entry pair = (Map.Entry)it.next(); ItemStack itemStack = BlockProperties.getCallableItemStack(((Attribute)pair.getValue()).getItemStack()); Block block = BlockProperties.toBlock(itemStack); if (block != Blocks.air) { // Determine metadata-sensitive light value (usually recursive, and not useful) setMetadata(itemStack.getItemDamage()); int sensitiveLight = block.getLightValue(getWorldObj(), xCoord, yCoord, zCoord); restoreMetadata(); if (sensitiveLight > 0) { value = Math.max(value, sensitiveLight); } else { // Grab default light value for block value = Math.max(value, block.getLightValue()); } } } calcLighting = false; } return value; } /** * Updates light value and world lightmap. */ private void updateCachedLighting() { lightValue = getDynamicLightValue(); getWorldObj().func_147451_t(xCoord, yCoord, zCoord); // Updates block lightmap, should help with spawns } /** * Performs world update and refreshes lighting. */ private void updateWorldAndLighting() { World world = getWorldObj(); if (world != null) { updateCachedLighting(); world.markBlockForUpdate(xCoord, yCoord, zCoord); } } }