package com.jaquadro.minecraft.storagedrawers.block.tile; import com.jaquadro.minecraft.storagedrawers.StorageDrawers; import com.jaquadro.minecraft.storagedrawers.api.security.ISecurityProvider; import com.jaquadro.minecraft.storagedrawers.api.storage.*; import com.jaquadro.minecraft.storagedrawers.api.storage.attribute.*; import com.jaquadro.minecraft.storagedrawers.block.BlockSlave; import com.jaquadro.minecraft.storagedrawers.inventory.DrawerItemHandler; import com.jaquadro.minecraft.storagedrawers.security.SecurityManager; import com.jaquadro.minecraft.storagedrawers.util.ItemMetaListRegistry; import com.mojang.authlib.GameProfile; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.NetworkManager; import net.minecraft.network.play.server.SPacketUpdateTileEntity; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.items.CapabilityItemHandler; import java.util.*; public class TileEntityController extends TileEntity implements IDrawerGroup, IPriorityGroup, ISmartGroup { private static final int PRI_VOID = 0; private static final int PRI_LOCKED = 1; private static final int PRI_NORMAL = 2; private static final int PRI_EMPTY = 3; private static final int PRI_LOCKED_EMPTY = 4; private static final int PRI_DISABLED = 5; private static class StorageRecord { public IDrawerGroup storage; public boolean mark; public int invStorageSize; public int drawerStorageSize; public int distance = Integer.MAX_VALUE; public void clear () { storage = null; mark = false; invStorageSize = 0; drawerStorageSize = 0; distance = Integer.MAX_VALUE; } } protected static class SlotRecord { public BlockPos coord; public IDrawerGroup group; public int slot; public int index; public int priority; public SlotRecord (IDrawerGroup group, BlockPos coord, int slot) { this.group = group; this.coord = coord; this.slot = slot; } } private Queue<BlockPos> searchQueue = new LinkedList<BlockPos>(); private Set<BlockPos> searchDiscovered = new HashSet<BlockPos>(); private Comparator<SlotRecord> slotRecordComparator = new Comparator<SlotRecord>() { @Override public int compare (SlotRecord o1, SlotRecord o2) { return o1.priority - o2.priority; } }; private int getSlotPriority (SlotRecord record) { IDrawerGroup group = getGroupForSlotRecord(record); if (group == null) { return PRI_DISABLED; } int drawerSlot = record.slot; IDrawer drawer = group.getDrawerIfEnabled(drawerSlot); if (drawer == null) { return PRI_DISABLED; } if (drawer.isEmpty()) { if ((drawer instanceof IItemLockable && ((IItemLockable) drawer).isItemLocked(LockAttribute.LOCK_EMPTY)) || (group instanceof IItemLockable && ((IItemLockable) group).isItemLocked(LockAttribute.LOCK_EMPTY))) { return PRI_LOCKED_EMPTY; } else return PRI_EMPTY; } if ((drawer instanceof IVoidable && ((IVoidable) drawer).isVoid()) || (group instanceof IVoidable && ((IVoidable) group).isVoid())) { return PRI_VOID; } if ((drawer instanceof IItemLockable && ((IItemLockable) drawer).isItemLocked(LockAttribute.LOCK_POPULATED)) || (group instanceof IItemLockable && ((IItemLockable) group).isItemLocked(LockAttribute.LOCK_POPULATED))) { return PRI_LOCKED; } return PRI_NORMAL; } private Map<BlockPos, StorageRecord> storage = new HashMap<BlockPos, StorageRecord>(); protected List<SlotRecord> drawerSlotList = new ArrayList<SlotRecord>(); private ItemMetaListRegistry<SlotRecord> drawerPrimaryLookup = new ItemMetaListRegistry<SlotRecord>(); protected int[] drawerSlots = new int[0]; private int range; private long lastClickTime; private UUID lastClickUUID; public TileEntityController () { range = StorageDrawers.config.getControllerRange(); } @Override public boolean shouldRefresh (World world, BlockPos pos, IBlockState oldState, IBlockState newSate) { return oldState.getBlock() != newSate.getBlock(); } public int interactPutItemsIntoInventory (EntityPlayer player) { boolean dumpInventory = getWorld().getTotalWorldTime() - lastClickTime < 10 && player.getPersistentID().equals(lastClickUUID); int count = 0; if (!dumpInventory) { ItemStack currentStack = player.inventory.getCurrentItem(); if (currentStack != null) { count = insertItems(currentStack, player.getGameProfile()); if (currentStack.stackSize == 0) player.inventory.setInventorySlotContents(player.inventory.currentItem, null); } } else { for (int i = 0, n = player.inventory.getSizeInventory(); i < n; i++) { ItemStack subStack = player.inventory.getStackInSlot(i); if (subStack != null) { count += insertItems(subStack, player.getGameProfile()); if (subStack.stackSize == 0) player.inventory.setInventorySlotContents(i, null); } } if (count > 0) StorageDrawers.proxy.updatePlayerInventory(player); } lastClickTime = getWorld().getTotalWorldTime(); lastClickUUID = player.getPersistentID(); return count; } protected int insertItems (ItemStack stack, GameProfile profile) { int itemsLeft = stack.stackSize; for (int slot : enumerateDrawersForInsertion(stack, false)) { IDrawerGroup group = getGroupForDrawerSlot(slot); if (group instanceof IProtectable) { if (!SecurityManager.hasAccess(profile, (IProtectable)group)) continue; } IDrawer drawer = getDrawer(slot); ItemStack itemProto = drawer.getStoredItemPrototype(); if (itemProto == null) break; itemsLeft = insertItemsIntoDrawer(drawer, itemsLeft); if (drawer instanceof IVoidable && ((IVoidable) drawer).isVoid()) itemsLeft = 0; if (itemsLeft == 0) break; } int count = stack.stackSize - itemsLeft; stack.stackSize = itemsLeft; return count; } protected int insertItemsIntoDrawer (IDrawer drawer, int itemCount) { int capacity = drawer.getMaxCapacity(); int storedItems = drawer.getStoredItemCount(); int storableItems = capacity - storedItems; if (drawer instanceof IFractionalDrawer) { IFractionalDrawer fracDrawer = (IFractionalDrawer)drawer; if (!fracDrawer.isSmallestUnit() && fracDrawer.getStoredItemRemainder() > 0) storableItems--; } if (storableItems == 0) return itemCount; int remainder = Math.max(itemCount - storableItems, 0); storedItems += Math.min(itemCount, storableItems); drawer.setStoredItemCount(storedItems); return remainder; } public void toggleProtection (GameProfile profile, ISecurityProvider provider) { IProtectable template = null; UUID state = null; for (StorageRecord record : storage.values()) { if (record.storage == null) continue; if (record.storage instanceof IProtectable) { IProtectable protectable = (IProtectable)record.storage; if (!SecurityManager.hasOwnership(profile, protectable)) continue; if (template == null) { template = protectable; if (template.getOwner() == null) state = profile.getId(); else { state = null; provider = null; } } protectable.setOwner(state); protectable.setSecurityProvider(provider); } } } public void toggleShroud (GameProfile profile) { Boolean template = null; boolean state = false; for (StorageRecord record : storage.values()) { if (record.storage == null) continue; if (record.storage instanceof IProtectable) { if (!SecurityManager.hasAccess(profile, (IProtectable)record.storage)) continue; } for (int i = 0, n = record.storage.getDrawerCount(); i < n; i++) { IDrawer drawer = record.storage.getDrawerIfEnabled(i); if (!(drawer instanceof IShroudable)) continue; IShroudable shroudableStorage = (IShroudable)drawer; if (template == null) { template = shroudableStorage.isShrouded(); state = !template; } shroudableStorage.setIsShrouded(state); } } } public void toggleQuantified (GameProfile profile) { Boolean template = null; boolean state = false; for (StorageRecord record : storage.values()) { if (record.storage == null) continue; if (record.storage instanceof IProtectable) { if (!SecurityManager.hasAccess(profile, (IProtectable)record.storage)) continue; } for (int i = 0, n = record.storage.getDrawerCount(); i < n; i++) { IDrawer drawer = record.storage.getDrawerIfEnabled(i); if (!(drawer instanceof IQuantifiable)) continue; IQuantifiable quantifiableStorage = (IQuantifiable)drawer; if (template == null) { template = quantifiableStorage.isShowingQuantity(); state = !template; } quantifiableStorage.setIsShowingQuantity(state); } } } public void toggleLock (EnumSet<LockAttribute> attributes, LockAttribute key, GameProfile profile) { Boolean template = null; boolean state = false; for (StorageRecord record : storage.values()) { if (record.storage == null) continue; if (record.storage instanceof IProtectable) { if (!SecurityManager.hasAccess(profile, (IProtectable)record.storage)) continue; } if (record.storage instanceof IItemLockable) { IItemLockable lockableStorage = (IItemLockable)record.storage; if (template == null) { template = lockableStorage.isItemLocked(key); state = !template; } for (LockAttribute attr : attributes) lockableStorage.setItemLocked(attr, state); } else { for (int i = 0, n = record.storage.getDrawerCount(); i < n; i++) { IDrawer drawer = record.storage.getDrawerIfEnabled(i); if (!(drawer instanceof IShroudable)) continue; IItemLockable lockableStorage = (IItemLockable)drawer; if (template == null) { template = lockableStorage.isItemLocked(key); state = !template; } for (LockAttribute attr : attributes) lockableStorage.setItemLocked(attr, state); } } } } protected void resetCache () { storage.clear(); drawerSlotList.clear(); } public boolean isValidSlave (BlockPos coord) { StorageRecord record = storage.get(coord); if (record == null || !record.mark) return false; return record.storage == null; } public void updateCache () { int preCount = drawerSlots.length; resetCache(); populateNodes(getPos()); flattenLists(); drawerSlots = sortSlotRecords(drawerSlotList); rebuildPrimaryLookup(drawerPrimaryLookup, drawerSlotList); if (preCount != drawerSlots.length && (preCount == 0 || drawerSlots.length == 0)) { if (!getWorld().isRemote) markDirty(); } } private void indexSlotRecords (List<SlotRecord> records) { for (int i = 0, n = records.size(); i < n; i++) { SlotRecord record = records.get(i); if (record != null) { record.index = i; record.priority = getSlotPriority(record); } } } private int[] sortSlotRecords (List<SlotRecord> records) { indexSlotRecords(records); List<SlotRecord> copied = new ArrayList<SlotRecord>(records); Collections.sort(copied, slotRecordComparator); int[] slotMap = new int[copied.size()]; for (int i = 0; i < slotMap.length; i++) slotMap[i] = copied.get(i).index; return slotMap; } private void rebuildPrimaryLookup (ItemMetaListRegistry<SlotRecord> lookup, List<SlotRecord> records) { lookup.clear(); for (SlotRecord record : records) { IDrawerGroup group = getGroupForSlotRecord(record); if (group == null) continue; int drawerSlot = record.slot; IDrawer drawer = group.getDrawerIfEnabled(drawerSlot); if (drawer == null) continue; if (drawer.isEmpty()) continue; ItemStack item = drawer.getStoredItemPrototype(); lookup.register(item.getItem(), item.getItemDamage(), record); } } private boolean containsNullEntries (List<SlotRecord> list) { int nullCount = 0; for (int i = 0, n = list.size(); i < n; i++) { if (list.get(i) == null) nullCount++; } return nullCount > 0; } private void flattenLists () { if (containsNullEntries(drawerSlotList)) { List<SlotRecord> newDrawerSlotList = new ArrayList<SlotRecord>(); for (int i = 0, n = drawerSlotList.size(); i < n; i++) { SlotRecord record = drawerSlotList.get(i); if (record != null) newDrawerSlotList.add(record); } drawerSlotList = newDrawerSlotList; } } private void clearRecordInfo (BlockPos coord, StorageRecord record) { record.clear(); for (int i = 0; i < drawerSlotList.size(); i++) { SlotRecord slotRecord = drawerSlotList.get(i); if (slotRecord != null && coord.equals(slotRecord.coord)) drawerSlotList.set(i, null); } } private void updateRecordInfo (BlockPos coord, StorageRecord record, TileEntity te) { if (te == null) { if (record.storage != null) clearRecordInfo(coord, record); return; } if (te instanceof TileEntityController) { if (record.storage == null && record.invStorageSize > 0) return; if (record.storage != null) clearRecordInfo(coord, record); record.storage = null; } else if (te instanceof TileEntitySlave) { if (record.storage == null && record.invStorageSize == 0) { if (((TileEntitySlave) te).getController() == this) return; } if (record.storage != null) clearRecordInfo(coord, record); record.storage = null; ((TileEntitySlave) te).bindController(getPos()); } else if (te instanceof IDrawerGroup) { IDrawerGroup group = (IDrawerGroup)te; if (record.storage == group) return; if (record.storage != null && record.storage != group) clearRecordInfo(coord, record); record.storage = group; record.drawerStorageSize = group.getDrawerCount(); for (int i = 0, n = record.drawerStorageSize; i < n; i++) drawerSlotList.add(new SlotRecord(group, coord, i)); } else { if (record.storage != null) clearRecordInfo(coord, record); } } private void populateNodes (BlockPos root) { searchQueue.clear(); searchQueue.add(root); searchDiscovered.clear(); searchDiscovered.add(root); while (!searchQueue.isEmpty()) { BlockPos coord = searchQueue.remove(); int depth = Math.max(Math.max(Math.abs(coord.getX() - root.getX()), Math.abs(coord.getY() - root.getY())), Math.abs(coord.getZ() - root.getZ())); if (depth > range) continue; Block block = getWorld().getBlockState(coord).getBlock(); if (!(block instanceof INetworked)) continue; StorageRecord record = storage.get(coord); if (record == null) { record = new StorageRecord(); storage.put(coord, record); } if (block instanceof BlockSlave) { ((BlockSlave) block).getTileEntitySafe(getWorld(), coord); } updateRecordInfo(coord, record, getWorld().getTileEntity(coord)); record.mark = true; record.distance = depth; BlockPos[] neighbors = new BlockPos[]{ coord.west(), coord.east(), coord.south(), coord.north(), coord.up(), coord.down() }; for (BlockPos n : neighbors) { if (!searchDiscovered.contains(n)) { searchQueue.add(n); searchDiscovered.add(n); } } } } protected IDrawerGroup getGroupForDrawerSlot (int drawerSlot) { if (drawerSlot < 0 || drawerSlot >= drawerSlotList.size()) return null; SlotRecord record = drawerSlotList.get(drawerSlot); if (record == null) return null; return getGroupForSlotRecord(record); } protected IDrawerGroup getGroupForSlotRecord (SlotRecord record) { IDrawerGroup group = record.group; if (group == null) return null; if (group instanceof TileEntity) { TileEntity tile = (TileEntity)group; if (tile.isInvalid() || !tile.getPos().equals(record.coord)) { record.group = null; return null; } } return group; } private int getLocalDrawerSlot (int drawerSlot) { if (drawerSlot >= drawerSlotList.size()) return 0; SlotRecord record = drawerSlotList.get(drawerSlot); if (record == null) return 0; return record.slot; } @Override public void readFromNBT (NBTTagCompound tag) { super.readFromNBT(tag); if (getWorld() != null && !getWorld().isRemote) updateCache(); } @Override public NBTTagCompound writeToNBT (NBTTagCompound tag) { super.writeToNBT(tag); return tag; } @Override public NBTTagCompound getUpdateTag () { NBTTagCompound tag = new NBTTagCompound(); writeToNBT(tag); return tag; } @Override public SPacketUpdateTileEntity getUpdatePacket () { return new SPacketUpdateTileEntity(getPos(), getBlockMetadata(), getUpdateTag()); } @Override public void onDataPacket (NetworkManager net, SPacketUpdateTileEntity pkt) { readFromNBT(pkt.getNbtCompound()); if (getWorld().isRemote) { IBlockState state = getWorld().getBlockState(getPos()); getWorld().notifyBlockUpdate(getPos(), state, state, 3); } } @Override public int getDrawerCount () { return drawerSlotList.size(); } @Override public IDrawer getDrawer (int slot) { IDrawerGroup group = getGroupForDrawerSlot(slot); if (group == null) return null; return group.getDrawer(getLocalDrawerSlot(slot)); } @Override public boolean isDrawerEnabled (int slot) { IDrawerGroup group = getGroupForDrawerSlot(slot); if (group == null) return false; return group.isDrawerEnabled(getLocalDrawerSlot(slot)); } @Override public IDrawer getDrawerIfEnabled (int slot) { IDrawerGroup group = getGroupForDrawerSlot(slot); if (group == null) return null; int localSlot = getLocalDrawerSlot(slot); return group.getDrawerIfEnabled(localSlot); } @Override public int[] getAccessibleDrawerSlots () { return drawerSlots; } @Override public void markDirty () { for (StorageRecord record : storage.values()) { IDrawerGroup group = record.storage; if (group != null) group.markDirtyIfNeeded(); } super.markDirty(); } @Override public boolean markDirtyIfNeeded () { boolean synced = false; for (StorageRecord record : storage.values()) { IDrawerGroup group = record.storage; if (group != null) synced |= group.markDirtyIfNeeded(); } if (synced) super.markDirty(); return synced; } private DrawerItemHandler itemHandler = new DrawerItemHandler(this); @Override public boolean hasCapability (Capability<?> capability, EnumFacing facing) { if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) return true; return super.hasCapability(capability, facing); } @Override public <T> T getCapability (Capability<T> capability, EnumFacing facing) { if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) return (T) itemHandler; return super.getCapability(capability, facing); } private class DrawerStackIterator implements Iterable<Integer> { private ItemStack stack; private boolean strict; private boolean insert; public DrawerStackIterator (ItemStack stack, boolean strict, boolean insert) { this.stack = stack; this.strict = strict; this.insert = insert; } @Override public Iterator<Integer> iterator () { if (this.stack == null) return new ArrayList<Integer>(0).iterator(); return new Iterator<Integer> () { List<SlotRecord> primaryRecords = drawerPrimaryLookup.getEntries(stack.getItem(), stack.getItemDamage()); Iterator<SlotRecord> iter1; int index2; Integer nextSlot = null; @Override public boolean hasNext () { if (nextSlot == null) advance(); return nextSlot != null; } @Override public Integer next () { if (nextSlot == null) advance(); Integer slot = nextSlot; nextSlot = null; return slot; } private void advance () { if (iter1 == null && primaryRecords != null && primaryRecords.size() > 0) iter1 = primaryRecords.iterator(); if (iter1 != null) { while (iter1.hasNext()) { SlotRecord candidate = iter1.next(); IDrawerGroup candidateGroup = getGroupForSlotRecord(candidate); if (candidateGroup == null) continue; IDrawer drawer = candidateGroup.getDrawer(candidate.slot); if (insert) { boolean voiding = (drawer instanceof IVoidable) ? ((IVoidable) drawer).isVoid() : false; if (!(drawer.canItemBeStored(stack) && (drawer.isEmpty() || drawer.getRemainingCapacity() > 0 || voiding))) continue; } else { if (!(drawer.canItemBeExtracted(stack) && drawer.getStoredItemCount() > 0)) continue; } int slot = drawerSlotList.indexOf(candidate); if (slot > -1) { nextSlot = slot; return; } } } for (; index2 < drawerSlots.length; index2++) { int slot = drawerSlots[index2]; IDrawer drawer = getDrawerIfEnabled(slot); if (drawer == null) continue; if (strict) { ItemStack proto = drawer.getStoredItemPrototype(); if (proto != null && !proto.isItemEqual(stack)) continue; } if (insert) { boolean voiding = (drawer instanceof IVoidable) ? ((IVoidable) drawer).isVoid() : false; if (!(drawer.canItemBeStored(stack) && (drawer.isEmpty() || drawer.getRemainingCapacity() > 0 || voiding))) continue; } else { if (!(drawer.canItemBeExtracted(stack) && drawer.getStoredItemCount() > 0)) continue; } SlotRecord record = drawerSlotList.get(slot); if (primaryRecords != null && primaryRecords.contains(record)) continue; nextSlot = slot; index2++; return; } } }; } }; public Iterable<Integer> enumerateDrawersForInsertion (ItemStack stack, boolean strict) { return new DrawerStackIterator(stack, strict, true); } public Iterable<Integer> enumerateDrawersForExtraction (ItemStack stack, boolean strict) { return new DrawerStackIterator(stack, strict, false); } }