001package net.minecraft.tileentity;
002
003import java.util.List;
004import net.minecraft.block.Block;
005import net.minecraft.block.BlockChest;
006import net.minecraft.block.BlockHopper;
007import net.minecraft.command.IEntitySelector;
008import net.minecraft.entity.Entity;
009import net.minecraft.entity.item.EntityItem;
010import net.minecraft.entity.player.EntityPlayer;
011import net.minecraft.inventory.IInventory;
012import net.minecraft.inventory.ISidedInventory;
013import net.minecraft.item.ItemStack;
014import net.minecraft.nbt.NBTTagCompound;
015import net.minecraft.nbt.NBTTagList;
016import net.minecraft.util.AxisAlignedBB;
017import net.minecraft.util.Facing;
018import net.minecraft.util.MathHelper;
019import net.minecraft.world.World;
020
021public class TileEntityHopper extends TileEntity implements Hopper
022{
023    private ItemStack[] hopperItemStacks = new ItemStack[5];
024
025    /** The name that is displayed if the hopper was renamed */
026    private String inventoryName;
027    private int transferCooldown = -1;
028
029    /**
030     * Reads a tile entity from NBT.
031     */
032    public void readFromNBT(NBTTagCompound par1NBTTagCompound)
033    {
034        super.readFromNBT(par1NBTTagCompound);
035        NBTTagList nbttaglist = par1NBTTagCompound.getTagList("Items");
036        this.hopperItemStacks = new ItemStack[this.getSizeInventory()];
037
038        if (par1NBTTagCompound.hasKey("CustomName"))
039        {
040            this.inventoryName = par1NBTTagCompound.getString("CustomName");
041        }
042
043        this.transferCooldown = par1NBTTagCompound.getInteger("TransferCooldown");
044
045        for (int i = 0; i < nbttaglist.tagCount(); ++i)
046        {
047            NBTTagCompound nbttagcompound1 = (NBTTagCompound)nbttaglist.tagAt(i);
048            byte b0 = nbttagcompound1.getByte("Slot");
049
050            if (b0 >= 0 && b0 < this.hopperItemStacks.length)
051            {
052                this.hopperItemStacks[b0] = ItemStack.loadItemStackFromNBT(nbttagcompound1);
053            }
054        }
055    }
056
057    /**
058     * Writes a tile entity to NBT.
059     */
060    public void writeToNBT(NBTTagCompound par1NBTTagCompound)
061    {
062        super.writeToNBT(par1NBTTagCompound);
063        NBTTagList nbttaglist = new NBTTagList();
064
065        for (int i = 0; i < this.hopperItemStacks.length; ++i)
066        {
067            if (this.hopperItemStacks[i] != null)
068            {
069                NBTTagCompound nbttagcompound1 = new NBTTagCompound();
070                nbttagcompound1.setByte("Slot", (byte)i);
071                this.hopperItemStacks[i].writeToNBT(nbttagcompound1);
072                nbttaglist.appendTag(nbttagcompound1);
073            }
074        }
075
076        par1NBTTagCompound.setTag("Items", nbttaglist);
077        par1NBTTagCompound.setInteger("TransferCooldown", this.transferCooldown);
078
079        if (this.isInvNameLocalized())
080        {
081            par1NBTTagCompound.setString("CustomName", this.inventoryName);
082        }
083    }
084
085    /**
086     * Called when an the contents of an Inventory change, usually
087     */
088    public void onInventoryChanged()
089    {
090        super.onInventoryChanged();
091    }
092
093    /**
094     * Returns the number of slots in the inventory.
095     */
096    public int getSizeInventory()
097    {
098        return this.hopperItemStacks.length;
099    }
100
101    /**
102     * Returns the stack in slot i
103     */
104    public ItemStack getStackInSlot(int par1)
105    {
106        return this.hopperItemStacks[par1];
107    }
108
109    /**
110     * Removes from an inventory slot (first arg) up to a specified number (second arg) of items and returns them in a
111     * new stack.
112     */
113    public ItemStack decrStackSize(int par1, int par2)
114    {
115        if (this.hopperItemStacks[par1] != null)
116        {
117            ItemStack itemstack;
118
119            if (this.hopperItemStacks[par1].stackSize <= par2)
120            {
121                itemstack = this.hopperItemStacks[par1];
122                this.hopperItemStacks[par1] = null;
123                return itemstack;
124            }
125            else
126            {
127                itemstack = this.hopperItemStacks[par1].splitStack(par2);
128
129                if (this.hopperItemStacks[par1].stackSize == 0)
130                {
131                    this.hopperItemStacks[par1] = null;
132                }
133
134                return itemstack;
135            }
136        }
137        else
138        {
139            return null;
140        }
141    }
142
143    /**
144     * When some containers are closed they call this on each slot, then drop whatever it returns as an EntityItem -
145     * like when you close a workbench GUI.
146     */
147    public ItemStack getStackInSlotOnClosing(int par1)
148    {
149        if (this.hopperItemStacks[par1] != null)
150        {
151            ItemStack itemstack = this.hopperItemStacks[par1];
152            this.hopperItemStacks[par1] = null;
153            return itemstack;
154        }
155        else
156        {
157            return null;
158        }
159    }
160
161    /**
162     * Sets the given item stack to the specified slot in the inventory (can be crafting or armor sections).
163     */
164    public void setInventorySlotContents(int par1, ItemStack par2ItemStack)
165    {
166        this.hopperItemStacks[par1] = par2ItemStack;
167
168        if (par2ItemStack != null && par2ItemStack.stackSize > this.getInventoryStackLimit())
169        {
170            par2ItemStack.stackSize = this.getInventoryStackLimit();
171        }
172    }
173
174    /**
175     * Returns the name of the inventory.
176     */
177    public String getInvName()
178    {
179        return this.isInvNameLocalized() ? this.inventoryName : "container.hopper";
180    }
181
182    /**
183     * If this returns false, the inventory name will be used as an unlocalized name, and translated into the player's
184     * language. Otherwise it will be used directly.
185     */
186    public boolean isInvNameLocalized()
187    {
188        return this.inventoryName != null && this.inventoryName.length() > 0;
189    }
190
191    public void func_96115_a(String par1Str)
192    {
193        this.inventoryName = par1Str;
194    }
195
196    /**
197     * Returns the maximum stack size for a inventory slot. Seems to always be 64, possibly will be extended. *Isn't
198     * this more of a set than a get?*
199     */
200    public int getInventoryStackLimit()
201    {
202        return 64;
203    }
204
205    /**
206     * Do not make give this method the name canInteractWith because it clashes with Container
207     */
208    public boolean isUseableByPlayer(EntityPlayer par1EntityPlayer)
209    {
210        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;
211    }
212
213    public void openChest() {}
214
215    public void closeChest() {}
216
217    /**
218     * Returns true if automation is allowed to insert the given stack (ignoring stack size) into the given slot.
219     */
220    public boolean isStackValidForSlot(int par1, ItemStack par2ItemStack)
221    {
222        return true;
223    }
224
225    /**
226     * Allows the entity to update its state. Overridden in most subclasses, e.g. the mob spawner uses this to count
227     * ticks and creates a new spawn inside its implementation.
228     */
229    public void updateEntity()
230    {
231        if (this.worldObj != null && !this.worldObj.isRemote)
232        {
233            --this.transferCooldown;
234
235            if (!this.func_98047_l())
236            {
237                this.func_98046_c(0);
238                this.func_98045_j();
239            }
240        }
241    }
242
243    public boolean func_98045_j()
244    {
245        if (this.worldObj != null && !this.worldObj.isRemote)
246        {
247            if (!this.func_98047_l() && BlockHopper.func_94452_d(this.getBlockMetadata()))
248            {
249                boolean flag = this.insertItemToInventory() | suckItemsIntoHopper(this);
250
251                if (flag)
252                {
253                    this.func_98046_c(8);
254                    this.onInventoryChanged();
255                    return true;
256                }
257            }
258
259            return false;
260        }
261        else
262        {
263            return false;
264        }
265    }
266
267    /**
268     * Inserts one item from the hopper into the inventory the hopper is pointing at.
269     */
270    private boolean insertItemToInventory()
271    {
272        IInventory iinventory = this.getOutputInventory();
273
274        if (iinventory == null)
275        {
276            return false;
277        }
278        else
279        {
280            for (int i = 0; i < this.getSizeInventory(); ++i)
281            {
282                if (this.getStackInSlot(i) != null)
283                {
284                    ItemStack itemstack = this.getStackInSlot(i).copy();
285                    ItemStack itemstack1 = insertStack(iinventory, this.decrStackSize(i, 1), Facing.oppositeSide[BlockHopper.getDirectionFromMetadata(this.getBlockMetadata())]);
286
287                    if (itemstack1 == null || itemstack1.stackSize == 0)
288                    {
289                        iinventory.onInventoryChanged();
290                        return true;
291                    }
292
293                    this.setInventorySlotContents(i, itemstack);
294                }
295            }
296
297            return false;
298        }
299    }
300
301    /**
302     * Sucks one item into the given hopper from an inventory or EntityItem above it.
303     */
304    public static boolean suckItemsIntoHopper(Hopper par0Hopper)
305    {
306        IInventory iinventory = func_96118_b(par0Hopper);
307
308        if (iinventory != null)
309        {
310            byte b0 = 0;
311
312            if (iinventory instanceof ISidedInventory && b0 > -1)
313            {
314                ISidedInventory isidedinventory = (ISidedInventory)iinventory;
315                int[] aint = isidedinventory.getSizeInventorySide(b0);
316
317                for (int i = 0; i < aint.length; ++i)
318                {
319                    if (func_102012_a(par0Hopper, iinventory, aint[i], b0))
320                    {
321                        return true;
322                    }
323                }
324            }
325            else
326            {
327                int j = iinventory.getSizeInventory();
328
329                for (int k = 0; k < j; ++k)
330                {
331                    if (func_102012_a(par0Hopper, iinventory, k, b0))
332                    {
333                        return true;
334                    }
335                }
336            }
337        }
338        else
339        {
340            EntityItem entityitem = func_96119_a(par0Hopper.getWorldObj(), par0Hopper.getXPos(), par0Hopper.getYPos() + 1.0D, par0Hopper.getZPos());
341
342            if (entityitem != null)
343            {
344                return func_96114_a(par0Hopper, entityitem);
345            }
346        }
347
348        return false;
349    }
350
351    private static boolean func_102012_a(Hopper par0Hopper, IInventory par1IInventory, int par2, int par3)
352    {
353        ItemStack itemstack = par1IInventory.getStackInSlot(par2);
354
355        if (itemstack != null && func_102013_b(par1IInventory, itemstack, par2, par3))
356        {
357            ItemStack itemstack1 = itemstack.copy();
358            ItemStack itemstack2 = insertStack(par0Hopper, par1IInventory.decrStackSize(par2, 1), -1);
359
360            if (itemstack2 == null || itemstack2.stackSize == 0)
361            {
362                par1IInventory.onInventoryChanged();
363                return true;
364            }
365
366            par1IInventory.setInventorySlotContents(par2, itemstack1);
367        }
368
369        return false;
370    }
371
372    public static boolean func_96114_a(IInventory par0IInventory, EntityItem par1EntityItem)
373    {
374        boolean flag = false;
375
376        if (par1EntityItem == null)
377        {
378            return false;
379        }
380        else
381        {
382            ItemStack itemstack = par1EntityItem.getEntityItem().copy();
383            ItemStack itemstack1 = insertStack(par0IInventory, itemstack, -1);
384
385            if (itemstack1 != null && itemstack1.stackSize != 0)
386            {
387                par1EntityItem.setEntityItemStack(itemstack1);
388            }
389            else
390            {
391                flag = true;
392                par1EntityItem.setDead();
393            }
394
395            return flag;
396        }
397    }
398
399    /**
400     * Inserts a stack into an inventory. Args: Inventory, stack, side. Returns leftover items.
401     */
402    public static ItemStack insertStack(IInventory par1IInventory, ItemStack par2ItemStack, int par3)
403    {
404        if (par1IInventory instanceof ISidedInventory && par3 > -1)
405        {
406            ISidedInventory isidedinventory = (ISidedInventory)par1IInventory;
407            int[] aint = isidedinventory.getSizeInventorySide(par3);
408
409            for (int j = 0; j < aint.length && par2ItemStack != null && par2ItemStack.stackSize > 0; ++j)
410            {
411                par2ItemStack = func_102014_c(par1IInventory, par2ItemStack, aint[j], par3);
412            }
413        }
414        else
415        {
416            int k = par1IInventory.getSizeInventory();
417
418            for (int l = 0; l < k && par2ItemStack != null && par2ItemStack.stackSize > 0; ++l)
419            {
420                par2ItemStack = func_102014_c(par1IInventory, par2ItemStack, l, par3);
421            }
422        }
423
424        if (par2ItemStack != null && par2ItemStack.stackSize == 0)
425        {
426            par2ItemStack = null;
427        }
428
429        return par2ItemStack;
430    }
431
432    private static boolean func_102015_a(IInventory par0IInventory, ItemStack par1ItemStack, int par2, int par3)
433    {
434        return !par0IInventory.isStackValidForSlot(par2, par1ItemStack) ? false : !(par0IInventory instanceof ISidedInventory) || ((ISidedInventory)par0IInventory).func_102007_a(par2, par1ItemStack, par3);
435    }
436
437    private static boolean func_102013_b(IInventory par0IInventory, ItemStack par1ItemStack, int par2, int par3)
438    {
439        return !(par0IInventory instanceof ISidedInventory) || ((ISidedInventory)par0IInventory).func_102008_b(par2, par1ItemStack, par3);
440    }
441
442    private static ItemStack func_102014_c(IInventory par0IInventory, ItemStack par1ItemStack, int par2, int par3)
443    {
444        ItemStack itemstack1 = par0IInventory.getStackInSlot(par2);
445
446        if (func_102015_a(par0IInventory, par1ItemStack, par2, par3))
447        {
448            boolean flag = false;
449
450            if (itemstack1 == null)
451            {
452                par0IInventory.setInventorySlotContents(par2, par1ItemStack);
453                par1ItemStack = null;
454                flag = true;
455            }
456            else if (func_94114_a(itemstack1, par1ItemStack))
457            {
458                int k = par1ItemStack.getMaxStackSize() - itemstack1.stackSize;
459                int l = Math.min(par1ItemStack.stackSize, k);
460                par1ItemStack.stackSize -= l;
461                itemstack1.stackSize += l;
462                flag = l > 0;
463            }
464
465            if (flag)
466            {
467                if (par0IInventory instanceof TileEntityHopper)
468                {
469                    ((TileEntityHopper)par0IInventory).func_98046_c(8);
470                }
471
472                par0IInventory.onInventoryChanged();
473            }
474        }
475
476        return par1ItemStack;
477    }
478
479    /**
480     * Gets the inventory the hopper is pointing at.
481     */
482    private IInventory getOutputInventory()
483    {
484        int i = BlockHopper.getDirectionFromMetadata(this.getBlockMetadata());
485        return getInventoryAtLocation(this.getWorldObj(), (double)(this.xCoord + Facing.offsetsXForSide[i]), (double)(this.yCoord + Facing.offsetsYForSide[i]), (double)(this.zCoord + Facing.offsetsZForSide[i]));
486    }
487
488    public static IInventory func_96118_b(Hopper par0Hopper)
489    {
490        return getInventoryAtLocation(par0Hopper.getWorldObj(), par0Hopper.getXPos(), par0Hopper.getYPos() + 1.0D, par0Hopper.getZPos());
491    }
492
493    public static EntityItem func_96119_a(World par0World, double par1, double par3, double par5)
494    {
495        List list = par0World.selectEntitiesWithinAABB(EntityItem.class, AxisAlignedBB.getAABBPool().getAABB(par1, par3, par5, par1 + 1.0D, par3 + 1.0D, par5 + 1.0D), IEntitySelector.selectAnything);
496        return list.size() > 0 ? (EntityItem)list.get(0) : null;
497    }
498
499    /**
500     * Gets an inventory at the given location to extract items into or take items from. Can find either a tile entity
501     * or regular entity implementing IInventory.
502     */
503    public static IInventory getInventoryAtLocation(World par0World, double par1, double par3, double par5)
504    {
505        IInventory iinventory = null;
506        int i = MathHelper.floor_double(par1);
507        int j = MathHelper.floor_double(par3);
508        int k = MathHelper.floor_double(par5);
509        TileEntity tileentity = par0World.getBlockTileEntity(i, j, k);
510
511        if (tileentity != null && tileentity instanceof IInventory)
512        {
513            iinventory = (IInventory)tileentity;
514
515            if (iinventory instanceof TileEntityChest)
516            {
517                int l = par0World.getBlockId(i, j, k);
518                Block block = Block.blocksList[l];
519
520                if (block instanceof BlockChest)
521                {
522                    iinventory = ((BlockChest)block).getInventory(par0World, i, j, k);
523                }
524            }
525        }
526
527        if (iinventory == null)
528        {
529            List list = par0World.getEntitiesWithinAABBExcludingEntity((Entity)null, AxisAlignedBB.getAABBPool().getAABB(par1, par3, par5, par1 + 1.0D, par3 + 1.0D, par5 + 1.0D), IEntitySelector.selectInventories);
530
531            if (list != null && list.size() > 0)
532            {
533                iinventory = (IInventory)list.get(par0World.rand.nextInt(list.size()));
534            }
535        }
536
537        return iinventory;
538    }
539
540    private static boolean func_94114_a(ItemStack par1ItemStack, ItemStack par2ItemStack)
541    {
542        return par1ItemStack.itemID != par2ItemStack.itemID ? false : (par1ItemStack.getItemDamage() != par2ItemStack.getItemDamage() ? false : (par1ItemStack.stackSize > par1ItemStack.getMaxStackSize() ? false : ItemStack.areItemStackTagsEqual(par1ItemStack, par2ItemStack)));
543    }
544
545    /**
546     * Gets the world X position for this hopper entity.
547     */
548    public double getXPos()
549    {
550        return (double)this.xCoord;
551    }
552
553    /**
554     * Gets the world Y position for this hopper entity.
555     */
556    public double getYPos()
557    {
558        return (double)this.yCoord;
559    }
560
561    /**
562     * Gets the world Z position for this hopper entity.
563     */
564    public double getZPos()
565    {
566        return (double)this.zCoord;
567    }
568
569    public void func_98046_c(int par1)
570    {
571        this.transferCooldown = par1;
572    }
573
574    public boolean func_98047_l()
575    {
576        return this.transferCooldown > 0;
577    }
578}