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}