001package net.minecraft.tileentity;
002
003import cpw.mods.fml.common.FMLLog;
004import cpw.mods.fml.relauncher.Side;
005import cpw.mods.fml.relauncher.SideOnly;
006import java.util.HashMap;
007import java.util.Map;
008import java.util.logging.Level;
009
010import net.minecraft.block.Block;
011import net.minecraft.crash.CrashReportCategory;
012import net.minecraft.nbt.NBTTagCompound;
013import net.minecraft.network.INetworkManager;
014import net.minecraft.network.packet.Packet;
015import net.minecraft.network.packet.Packet132TileEntityData;
016import net.minecraft.server.MinecraftServer;
017import net.minecraft.util.AxisAlignedBB;
018import net.minecraft.world.World;
019
020public class TileEntity
021{
022    /**
023     * A HashMap storing string names of classes mapping to the actual java.lang.Class type.
024     */
025    private static Map nameToClassMap = new HashMap();
026
027    /**
028     * A HashMap storing the classes and mapping to the string names (reverse of nameToClassMap).
029     */
030    private static Map classToNameMap = new HashMap();
031
032    /** The reference to the world. */
033    public World worldObj;
034
035    /** The x coordinate of the tile entity. */
036    public int xCoord;
037
038    /** The y coordinate of the tile entity. */
039    public int yCoord;
040
041    /** The z coordinate of the tile entity. */
042    public int zCoord;
043    protected boolean tileEntityInvalid;
044    public int blockMetadata = -1;
045
046    /** the Block type that this TileEntity is contained within */
047    public Block blockType;
048
049    /**
050     * Adds a new two-way mapping between the class and its string name in both hashmaps.
051     */
052    public static void addMapping(Class par0Class, String par1Str)
053    {
054        if (nameToClassMap.containsKey(par1Str))
055        {
056            throw new IllegalArgumentException("Duplicate id: " + par1Str);
057        }
058        else
059        {
060            nameToClassMap.put(par1Str, par0Class);
061            classToNameMap.put(par0Class, par1Str);
062        }
063    }
064
065    /**
066     * Returns the worldObj for this tileEntity.
067     */
068    public World getWorldObj()
069    {
070        return this.worldObj;
071    }
072
073    /**
074     * Sets the worldObj for this tileEntity.
075     */
076    public void setWorldObj(World par1World)
077    {
078        this.worldObj = par1World;
079    }
080
081    public boolean func_70309_m()
082    {
083        return this.worldObj != null;
084    }
085
086    /**
087     * Reads a tile entity from NBT.
088     */
089    public void readFromNBT(NBTTagCompound par1NBTTagCompound)
090    {
091        this.xCoord = par1NBTTagCompound.getInteger("x");
092        this.yCoord = par1NBTTagCompound.getInteger("y");
093        this.zCoord = par1NBTTagCompound.getInteger("z");
094    }
095
096    /**
097     * Writes a tile entity to NBT.
098     */
099    public void writeToNBT(NBTTagCompound par1NBTTagCompound)
100    {
101        String s = (String)classToNameMap.get(this.getClass());
102
103        if (s == null)
104        {
105            throw new RuntimeException(this.getClass() + " is missing a mapping! This is a bug!");
106        }
107        else
108        {
109            par1NBTTagCompound.setString("id", s);
110            par1NBTTagCompound.setInteger("x", this.xCoord);
111            par1NBTTagCompound.setInteger("y", this.yCoord);
112            par1NBTTagCompound.setInteger("z", this.zCoord);
113        }
114    }
115
116    /**
117     * Allows the entity to update its state. Overridden in most subclasses, e.g. the mob spawner uses this to count
118     * ticks and creates a new spawn inside its implementation.
119     */
120    public void updateEntity() {}
121
122    /**
123     * Creates a new entity and loads its data from the specified NBT.
124     */
125    public static TileEntity createAndLoadEntity(NBTTagCompound par0NBTTagCompound)
126    {
127        TileEntity tileentity = null;
128
129        Class oclass = null;
130
131        try
132        {
133            oclass = (Class)nameToClassMap.get(par0NBTTagCompound.getString("id"));
134
135            if (oclass != null)
136            {
137                tileentity = (TileEntity)oclass.newInstance();
138            }
139        }
140        catch (Exception exception)
141        {
142            exception.printStackTrace();
143        }
144
145        if (tileentity != null)
146        {
147            try
148            {
149                tileentity.readFromNBT(par0NBTTagCompound);
150            }
151            catch (Exception e)
152            {
153                FMLLog.log(Level.SEVERE, e,
154                        "A TileEntity %s(%s) has thrown an exception during loading, its state cannot be restored. Report this to the mod author",
155                        par0NBTTagCompound.getString("id"), oclass.getName());
156                tileentity = null;
157            }
158        }
159        else
160        {
161            MinecraftServer.getServer().getLogAgent().logWarning("Skipping TileEntity with id " + par0NBTTagCompound.getString("id"));
162        }
163
164        return tileentity;
165    }
166
167    /**
168     * Returns block data at the location of this entity (client-only).
169     */
170    public int getBlockMetadata()
171    {
172        if (this.blockMetadata == -1)
173        {
174            this.blockMetadata = this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord);
175        }
176
177        return this.blockMetadata;
178    }
179
180    /**
181     * Called when an the contents of an Inventory change, usually
182     */
183    public void onInventoryChanged()
184    {
185        if (this.worldObj != null)
186        {
187            this.blockMetadata = this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord);
188            this.worldObj.updateTileEntityChunkAndDoNothing(this.xCoord, this.yCoord, this.zCoord, this);
189
190            if (this.getBlockType() != null)
191            {
192                this.worldObj.func_96440_m(this.xCoord, this.yCoord, this.zCoord, this.getBlockType().blockID);
193            }
194        }
195    }
196
197    @SideOnly(Side.CLIENT)
198
199    /**
200     * Returns the square of the distance between this entity and the passed in coordinates.
201     */
202    public double getDistanceFrom(double par1, double par3, double par5)
203    {
204        double d3 = (double)this.xCoord + 0.5D - par1;
205        double d4 = (double)this.yCoord + 0.5D - par3;
206        double d5 = (double)this.zCoord + 0.5D - par5;
207        return d3 * d3 + d4 * d4 + d5 * d5;
208    }
209
210    @SideOnly(Side.CLIENT)
211    public double getMaxRenderDistanceSquared()
212    {
213        return 4096.0D;
214    }
215
216    /**
217     * Gets the block type at the location of this entity (client-only).
218     */
219    public Block getBlockType()
220    {
221        if (this.blockType == null)
222        {
223            this.blockType = Block.blocksList[this.worldObj.getBlockId(this.xCoord, this.yCoord, this.zCoord)];
224        }
225
226        return this.blockType;
227    }
228
229    /**
230     * Overriden in a sign to provide the text.
231     */
232    public Packet getDescriptionPacket()
233    {
234        return null;
235    }
236
237    /**
238     * returns true if tile entity is invalid, false otherwise
239     */
240    public boolean isInvalid()
241    {
242        return this.tileEntityInvalid;
243    }
244
245    /**
246     * invalidates a tile entity
247     */
248    public void invalidate()
249    {
250        this.tileEntityInvalid = true;
251    }
252
253    /**
254     * validates a tile entity
255     */
256    public void validate()
257    {
258        this.tileEntityInvalid = false;
259    }
260
261    /**
262     * Called when a client event is received with the event number and argument, see World.sendClientEvent
263     */
264    public boolean receiveClientEvent(int par1, int par2)
265    {
266        return false;
267    }
268
269    /**
270     * Causes the TileEntity to reset all it's cached values for it's container block, blockID, metaData and in the case
271     * of chests, the adjcacent chest check
272     */
273    public void updateContainingBlockInfo()
274    {
275        this.blockType = null;
276        this.blockMetadata = -1;
277    }
278
279    public void func_85027_a(CrashReportCategory par1CrashReportCategory)
280    {
281        par1CrashReportCategory.addCrashSectionCallable("Name", new CallableTileEntityName(this));
282        CrashReportCategory.func_85068_a(par1CrashReportCategory, this.xCoord, this.yCoord, this.zCoord, this.getBlockType().blockID, this.getBlockMetadata());
283        par1CrashReportCategory.addCrashSectionCallable("Actual block type", new CallableTileEntityID(this));
284        par1CrashReportCategory.addCrashSectionCallable("Actual block data value", new CallableTileEntityData(this));
285    }
286
287    static Map getClassToNameMap()
288    {
289        return classToNameMap;
290    }
291
292    static
293    {
294        addMapping(TileEntityFurnace.class, "Furnace");
295        addMapping(TileEntityChest.class, "Chest");
296        addMapping(TileEntityEnderChest.class, "EnderChest");
297        addMapping(TileEntityRecordPlayer.class, "RecordPlayer");
298        addMapping(TileEntityDispenser.class, "Trap");
299        addMapping(TileEntityDropper.class, "Dropper");
300        addMapping(TileEntitySign.class, "Sign");
301        addMapping(TileEntityMobSpawner.class, "MobSpawner");
302        addMapping(TileEntityNote.class, "Music");
303        addMapping(TileEntityPiston.class, "Piston");
304        addMapping(TileEntityBrewingStand.class, "Cauldron");
305        addMapping(TileEntityEnchantmentTable.class, "EnchantTable");
306        addMapping(TileEntityEndPortal.class, "Airportal");
307        addMapping(TileEntityCommandBlock.class, "Control");
308        addMapping(TileEntityBeacon.class, "Beacon");
309        addMapping(TileEntitySkull.class, "Skull");
310        addMapping(TileEntityDaylightDetector.class, "DLDetector");
311        addMapping(TileEntityHopper.class, "Hopper");
312        addMapping(TileEntityComparator.class, "Comparator");
313    }
314
315    // -- BEGIN FORGE PATCHES --
316    /**
317     * Determines if this TileEntity requires update calls.
318     * @return True if you want updateEntity() to be called, false if not
319     */
320    public boolean canUpdate()
321    {
322        return true;
323    }
324
325    /**
326     * Called when you receive a TileEntityData packet for the location this
327     * TileEntity is currently in. On the client, the NetworkManager will always
328     * be the remote server. On the server, it will be whomever is responsible for
329     * sending the packet.
330     *
331     * @param net The NetworkManager the packet originated from
332     * @param pkt The data packet
333     */
334    public void onDataPacket(INetworkManager net, Packet132TileEntityData pkt)
335    {
336    }
337
338    /**
339     * Called when the chunk this TileEntity is on is Unloaded.
340     */
341    public void onChunkUnload()
342    {
343    }
344
345    /**
346     * Called from Chunk.setBlockIDWithMetadata, determines if this tile entity should be re-created when the ID, or Metadata changes.
347     * Use with caution as this will leave straggler TileEntities, or create conflicts with other TileEntities if not used properly.
348     *
349     * @param oldID The old ID of the block
350     * @param newID The new ID of the block (May be the same)
351     * @param oldMeta The old metadata of the block
352     * @param newMeta The new metadata of the block (May be the same)
353     * @param world Current world
354     * @param x X Postion
355     * @param y Y Position
356     * @param z Z Position
357     * @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}
358     */
359    public boolean shouldRefresh(int oldID, int newID, int oldMeta, int newMeta, World world, int x, int y, int z)
360    {
361        return true;
362    }
363
364    public boolean shouldRenderInPass(int pass)
365    {
366        return pass == 0;
367    }
368    /**
369     * Sometimes default render bounding box: infinite in scope. Used to control rendering on {@link TileEntitySpecialRenderer}.
370     */
371    public static final AxisAlignedBB INFINITE_EXTENT_AABB = AxisAlignedBB.getBoundingBox(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
372
373    /**
374     * Return an {@link AxisAlignedBB} that controls the visible scope of a {@link TileEntitySpecialRenderer} associated with this {@link TileEntity}
375     * Defaults to the collision bounding box {@link Block#getCollisionBoundingBoxFromPool(World, int, int, int)} associated with the block
376     * at this location.
377     *
378     * @return an appropriately size {@link AxisAlignedBB} for the {@link TileEntity}
379     */
380    @SideOnly(Side.CLIENT)
381    public AxisAlignedBB getRenderBoundingBox()
382    {
383        AxisAlignedBB bb = INFINITE_EXTENT_AABB;
384        Block type = getBlockType();
385        if (type == Block.enchantmentTable)
386        {
387            bb = AxisAlignedBB.getAABBPool().getAABB(xCoord, yCoord, zCoord, xCoord + 1, yCoord + 1, zCoord + 1);
388        }
389        else if (type == Block.chest)
390        {
391            bb = AxisAlignedBB.getAABBPool().getAABB(xCoord - 1, yCoord, zCoord - 1, xCoord + 2, yCoord + 2, zCoord + 2);
392        }
393        else if (type != null && type != Block.beacon)
394        {
395            AxisAlignedBB cbb = getBlockType().getCollisionBoundingBoxFromPool(worldObj, xCoord, yCoord, zCoord);
396            if (cbb != null)
397            {
398                bb = cbb;
399            }
400        }
401        return bb;
402    }
403}