001package net.minecraft.tileentity;
002
003import java.util.Iterator;
004import java.util.List;
005import net.minecraft.block.Block;
006import net.minecraft.block.BlockChest;
007import net.minecraft.entity.player.EntityPlayer;
008import net.minecraft.inventory.ContainerChest;
009import net.minecraft.inventory.IInventory;
010import net.minecraft.inventory.InventoryLargeChest;
011import net.minecraft.item.ItemStack;
012import net.minecraft.nbt.NBTTagCompound;
013import net.minecraft.nbt.NBTTagList;
014import net.minecraft.util.AxisAlignedBB;
015
016public class TileEntityChest extends TileEntity implements IInventory
017{
018    private ItemStack[] chestContents = new ItemStack[36];
019
020    /** Determines if the check for adjacent chests has taken place. */
021    public boolean adjacentChestChecked = false;
022
023    /** Contains the chest tile located adjacent to this one (if any) */
024    public TileEntityChest adjacentChestZNeg;
025
026    /** Contains the chest tile located adjacent to this one (if any) */
027    public TileEntityChest adjacentChestXPos;
028
029    /** Contains the chest tile located adjacent to this one (if any) */
030    public TileEntityChest adjacentChestXNeg;
031
032    /** Contains the chest tile located adjacent to this one (if any) */
033    public TileEntityChest adjacentChestZPosition;
034
035    /** The current angle of the lid (between 0 and 1) */
036    public float lidAngle;
037
038    /** The angle of the lid last tick */
039    public float prevLidAngle;
040
041    /** The number of players currently using this chest */
042    public int numUsingPlayers;
043
044    /** Server sync counter (once per 20 ticks) */
045    private int ticksSinceSync;
046    private int field_94046_i = -1;
047    private String field_94045_s;
048
049    /**
050     * Returns the number of slots in the inventory.
051     */
052    public int getSizeInventory()
053    {
054        return 27;
055    }
056
057    /**
058     * Returns the stack in slot i
059     */
060    public ItemStack getStackInSlot(int par1)
061    {
062        return this.chestContents[par1];
063    }
064
065    /**
066     * Removes from an inventory slot (first arg) up to a specified number (second arg) of items and returns them in a
067     * new stack.
068     */
069    public ItemStack decrStackSize(int par1, int par2)
070    {
071        if (this.chestContents[par1] != null)
072        {
073            ItemStack itemstack;
074
075            if (this.chestContents[par1].stackSize <= par2)
076            {
077                itemstack = this.chestContents[par1];
078                this.chestContents[par1] = null;
079                this.onInventoryChanged();
080                return itemstack;
081            }
082            else
083            {
084                itemstack = this.chestContents[par1].splitStack(par2);
085
086                if (this.chestContents[par1].stackSize == 0)
087                {
088                    this.chestContents[par1] = null;
089                }
090
091                this.onInventoryChanged();
092                return itemstack;
093            }
094        }
095        else
096        {
097            return null;
098        }
099    }
100
101    /**
102     * When some containers are closed they call this on each slot, then drop whatever it returns as an EntityItem -
103     * like when you close a workbench GUI.
104     */
105    public ItemStack getStackInSlotOnClosing(int par1)
106    {
107        if (this.chestContents[par1] != null)
108        {
109            ItemStack itemstack = this.chestContents[par1];
110            this.chestContents[par1] = null;
111            return itemstack;
112        }
113        else
114        {
115            return null;
116        }
117    }
118
119    /**
120     * Sets the given item stack to the specified slot in the inventory (can be crafting or armor sections).
121     */
122    public void setInventorySlotContents(int par1, ItemStack par2ItemStack)
123    {
124        this.chestContents[par1] = par2ItemStack;
125
126        if (par2ItemStack != null && par2ItemStack.stackSize > this.getInventoryStackLimit())
127        {
128            par2ItemStack.stackSize = this.getInventoryStackLimit();
129        }
130
131        this.onInventoryChanged();
132    }
133
134    /**
135     * Returns the name of the inventory.
136     */
137    public String getInvName()
138    {
139        return this.isInvNameLocalized() ? this.field_94045_s : "container.chest";
140    }
141
142    /**
143     * If this returns false, the inventory name will be used as an unlocalized name, and translated into the player's
144     * language. Otherwise it will be used directly.
145     */
146    public boolean isInvNameLocalized()
147    {
148        return this.field_94045_s != null && this.field_94045_s.length() > 0;
149    }
150
151    public void func_94043_a(String par1Str)
152    {
153        this.field_94045_s = par1Str;
154    }
155
156    /**
157     * Reads a tile entity from NBT.
158     */
159    public void readFromNBT(NBTTagCompound par1NBTTagCompound)
160    {
161        super.readFromNBT(par1NBTTagCompound);
162        NBTTagList nbttaglist = par1NBTTagCompound.getTagList("Items");
163        this.chestContents = new ItemStack[this.getSizeInventory()];
164
165        if (par1NBTTagCompound.hasKey("CustomName"))
166        {
167            this.field_94045_s = par1NBTTagCompound.getString("CustomName");
168        }
169
170        for (int i = 0; i < nbttaglist.tagCount(); ++i)
171        {
172            NBTTagCompound nbttagcompound1 = (NBTTagCompound)nbttaglist.tagAt(i);
173            int j = nbttagcompound1.getByte("Slot") & 255;
174
175            if (j >= 0 && j < this.chestContents.length)
176            {
177                this.chestContents[j] = ItemStack.loadItemStackFromNBT(nbttagcompound1);
178            }
179        }
180    }
181
182    /**
183     * Writes a tile entity to NBT.
184     */
185    public void writeToNBT(NBTTagCompound par1NBTTagCompound)
186    {
187        super.writeToNBT(par1NBTTagCompound);
188        NBTTagList nbttaglist = new NBTTagList();
189
190        for (int i = 0; i < this.chestContents.length; ++i)
191        {
192            if (this.chestContents[i] != null)
193            {
194                NBTTagCompound nbttagcompound1 = new NBTTagCompound();
195                nbttagcompound1.setByte("Slot", (byte)i);
196                this.chestContents[i].writeToNBT(nbttagcompound1);
197                nbttaglist.appendTag(nbttagcompound1);
198            }
199        }
200
201        par1NBTTagCompound.setTag("Items", nbttaglist);
202
203        if (this.isInvNameLocalized())
204        {
205            par1NBTTagCompound.setString("CustomName", this.field_94045_s);
206        }
207    }
208
209    /**
210     * Returns the maximum stack size for a inventory slot. Seems to always be 64, possibly will be extended. *Isn't
211     * this more of a set than a get?*
212     */
213    public int getInventoryStackLimit()
214    {
215        return 64;
216    }
217
218    /**
219     * Do not make give this method the name canInteractWith because it clashes with Container
220     */
221    public boolean isUseableByPlayer(EntityPlayer par1EntityPlayer)
222    {
223        return this.worldObj.getBlockTileEntity(this.xCoord, this.yCoord, this.zCoord) != this ? false : par1EntityPlayer.getDistanceSq((double)this.xCoord + 0.5D, (double)this.yCoord + 0.5D, (double)this.zCoord + 0.5D) <= 64.0D;
224    }
225
226    /**
227     * Causes the TileEntity to reset all it's cached values for it's container block, blockID, metaData and in the case
228     * of chests, the adjcacent chest check
229     */
230    public void updateContainingBlockInfo()
231    {
232        super.updateContainingBlockInfo();
233        this.adjacentChestChecked = false;
234    }
235
236    private void func_90009_a(TileEntityChest par1TileEntityChest, int par2)
237    {
238        if (par1TileEntityChest.isInvalid())
239        {
240            this.adjacentChestChecked = false;
241        }
242        else if (this.adjacentChestChecked)
243        {
244            switch (par2)
245            {
246                case 0:
247                    if (this.adjacentChestZPosition != par1TileEntityChest)
248                    {
249                        this.adjacentChestChecked = false;
250                    }
251
252                    break;
253                case 1:
254                    if (this.adjacentChestXNeg != par1TileEntityChest)
255                    {
256                        this.adjacentChestChecked = false;
257                    }
258
259                    break;
260                case 2:
261                    if (this.adjacentChestZNeg != par1TileEntityChest)
262                    {
263                        this.adjacentChestChecked = false;
264                    }
265
266                    break;
267                case 3:
268                    if (this.adjacentChestXPos != par1TileEntityChest)
269                    {
270                        this.adjacentChestChecked = false;
271                    }
272            }
273        }
274    }
275
276    /**
277     * Performs the check for adjacent chests to determine if this chest is double or not.
278     */
279    public void checkForAdjacentChests()
280    {
281        if (!this.adjacentChestChecked)
282        {
283            this.adjacentChestChecked = true;
284            this.adjacentChestZNeg = null;
285            this.adjacentChestXPos = null;
286            this.adjacentChestXNeg = null;
287            this.adjacentChestZPosition = null;
288
289            if (this.func_94044_a(this.xCoord - 1, this.yCoord, this.zCoord))
290            {
291                this.adjacentChestXNeg = (TileEntityChest)this.worldObj.getBlockTileEntity(this.xCoord - 1, this.yCoord, this.zCoord);
292            }
293
294            if (this.func_94044_a(this.xCoord + 1, this.yCoord, this.zCoord))
295            {
296                this.adjacentChestXPos = (TileEntityChest)this.worldObj.getBlockTileEntity(this.xCoord + 1, this.yCoord, this.zCoord);
297            }
298
299            if (this.func_94044_a(this.xCoord, this.yCoord, this.zCoord - 1))
300            {
301                this.adjacentChestZNeg = (TileEntityChest)this.worldObj.getBlockTileEntity(this.xCoord, this.yCoord, this.zCoord - 1);
302            }
303
304            if (this.func_94044_a(this.xCoord, this.yCoord, this.zCoord + 1))
305            {
306                this.adjacentChestZPosition = (TileEntityChest)this.worldObj.getBlockTileEntity(this.xCoord, this.yCoord, this.zCoord + 1);
307            }
308
309            if (this.adjacentChestZNeg != null)
310            {
311                this.adjacentChestZNeg.func_90009_a(this, 0);
312            }
313
314            if (this.adjacentChestZPosition != null)
315            {
316                this.adjacentChestZPosition.func_90009_a(this, 2);
317            }
318
319            if (this.adjacentChestXPos != null)
320            {
321                this.adjacentChestXPos.func_90009_a(this, 1);
322            }
323
324            if (this.adjacentChestXNeg != null)
325            {
326                this.adjacentChestXNeg.func_90009_a(this, 3);
327            }
328        }
329    }
330
331    private boolean func_94044_a(int par1, int par2, int par3)
332    {
333        Block block = Block.blocksList[this.worldObj.getBlockId(par1, par2, par3)];
334        return block != null && block instanceof BlockChest ? ((BlockChest)block).field_94443_a == this.func_98041_l() : false;
335    }
336
337    /**
338     * Allows the entity to update its state. Overridden in most subclasses, e.g. the mob spawner uses this to count
339     * ticks and creates a new spawn inside its implementation.
340     */
341    public void updateEntity()
342    {
343        super.updateEntity();
344        this.checkForAdjacentChests();
345        ++this.ticksSinceSync;
346        float f;
347
348        if (!this.worldObj.isRemote && this.numUsingPlayers != 0 && (this.ticksSinceSync + this.xCoord + this.yCoord + this.zCoord) % 200 == 0)
349        {
350            this.numUsingPlayers = 0;
351            f = 5.0F;
352            List list = this.worldObj.getEntitiesWithinAABB(EntityPlayer.class, AxisAlignedBB.getAABBPool().getAABB((double)((float)this.xCoord - f), (double)((float)this.yCoord - f), (double)((float)this.zCoord - f), (double)((float)(this.xCoord + 1) + f), (double)((float)(this.yCoord + 1) + f), (double)((float)(this.zCoord + 1) + f)));
353            Iterator iterator = list.iterator();
354
355            while (iterator.hasNext())
356            {
357                EntityPlayer entityplayer = (EntityPlayer)iterator.next();
358
359                if (entityplayer.openContainer instanceof ContainerChest)
360                {
361                    IInventory iinventory = ((ContainerChest)entityplayer.openContainer).getLowerChestInventory();
362
363                    if (iinventory == this || iinventory instanceof InventoryLargeChest && ((InventoryLargeChest)iinventory).isPartOfLargeChest(this))
364                    {
365                        ++this.numUsingPlayers;
366                    }
367                }
368            }
369        }
370
371        this.prevLidAngle = this.lidAngle;
372        f = 0.1F;
373        double d0;
374
375        if (this.numUsingPlayers > 0 && this.lidAngle == 0.0F && this.adjacentChestZNeg == null && this.adjacentChestXNeg == null)
376        {
377            double d1 = (double)this.xCoord + 0.5D;
378            d0 = (double)this.zCoord + 0.5D;
379
380            if (this.adjacentChestZPosition != null)
381            {
382                d0 += 0.5D;
383            }
384
385            if (this.adjacentChestXPos != null)
386            {
387                d1 += 0.5D;
388            }
389
390            this.worldObj.playSoundEffect(d1, (double)this.yCoord + 0.5D, d0, "random.chestopen", 0.5F, this.worldObj.rand.nextFloat() * 0.1F + 0.9F);
391        }
392
393        if (this.numUsingPlayers == 0 && this.lidAngle > 0.0F || this.numUsingPlayers > 0 && this.lidAngle < 1.0F)
394        {
395            float f1 = this.lidAngle;
396
397            if (this.numUsingPlayers > 0)
398            {
399                this.lidAngle += f;
400            }
401            else
402            {
403                this.lidAngle -= f;
404            }
405
406            if (this.lidAngle > 1.0F)
407            {
408                this.lidAngle = 1.0F;
409            }
410
411            float f2 = 0.5F;
412
413            if (this.lidAngle < f2 && f1 >= f2 && this.adjacentChestZNeg == null && this.adjacentChestXNeg == null)
414            {
415                d0 = (double)this.xCoord + 0.5D;
416                double d2 = (double)this.zCoord + 0.5D;
417
418                if (this.adjacentChestZPosition != null)
419                {
420                    d2 += 0.5D;
421                }
422
423                if (this.adjacentChestXPos != null)
424                {
425                    d0 += 0.5D;
426                }
427
428                this.worldObj.playSoundEffect(d0, (double)this.yCoord + 0.5D, d2, "random.chestclosed", 0.5F, this.worldObj.rand.nextFloat() * 0.1F + 0.9F);
429            }
430
431            if (this.lidAngle < 0.0F)
432            {
433                this.lidAngle = 0.0F;
434            }
435        }
436    }
437
438    /**
439     * Called when a client event is received with the event number and argument, see World.sendClientEvent
440     */
441    public boolean receiveClientEvent(int par1, int par2)
442    {
443        if (par1 == 1)
444        {
445            this.numUsingPlayers = par2;
446            return true;
447        }
448        else
449        {
450            return super.receiveClientEvent(par1, par2);
451        }
452    }
453
454    public void openChest()
455    {
456        if (this.numUsingPlayers < 0)
457        {
458            this.numUsingPlayers = 0;
459        }
460
461        ++this.numUsingPlayers;
462        this.worldObj.addBlockEvent(this.xCoord, this.yCoord, this.zCoord, this.getBlockType().blockID, 1, this.numUsingPlayers);
463        this.worldObj.notifyBlocksOfNeighborChange(this.xCoord, this.yCoord, this.zCoord, this.getBlockType().blockID);
464        this.worldObj.notifyBlocksOfNeighborChange(this.xCoord, this.yCoord - 1, this.zCoord, this.getBlockType().blockID);
465    }
466
467    public void closeChest()
468    {
469        if (this.getBlockType() != null && this.getBlockType() instanceof BlockChest)
470        {
471            --this.numUsingPlayers;
472            this.worldObj.addBlockEvent(this.xCoord, this.yCoord, this.zCoord, this.getBlockType().blockID, 1, this.numUsingPlayers);
473            this.worldObj.notifyBlocksOfNeighborChange(this.xCoord, this.yCoord, this.zCoord, this.getBlockType().blockID);
474            this.worldObj.notifyBlocksOfNeighborChange(this.xCoord, this.yCoord - 1, this.zCoord, this.getBlockType().blockID);
475        }
476    }
477
478    /**
479     * Returns true if automation is allowed to insert the given stack (ignoring stack size) into the given slot.
480     */
481    public boolean isStackValidForSlot(int par1, ItemStack par2ItemStack)
482    {
483        return true;
484    }
485
486    /**
487     * invalidates a tile entity
488     */
489    public void invalidate()
490    {
491        super.invalidate();
492        this.updateContainingBlockInfo();
493        this.checkForAdjacentChests();
494    }
495
496    public int func_98041_l()
497    {
498        if (this.field_94046_i == -1)
499        {
500            if (this.worldObj == null || !(this.getBlockType() instanceof BlockChest))
501            {
502                return 0;
503            }
504
505            this.field_94046_i = ((BlockChest)this.getBlockType()).field_94443_a;
506        }
507
508        return this.field_94046_i;
509    }
510}