package crazypants.enderio.machine; import java.util.EnumMap; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.Set; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.IInventory; import net.minecraft.inventory.ISidedInventory; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; import com.enderio.core.common.util.BlockCoord; import com.enderio.core.common.util.InventoryWrapper; import com.enderio.core.common.util.ItemUtil; import cpw.mods.fml.client.FMLClientHandler; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import crazypants.enderio.EnderIO; import crazypants.enderio.TileEntityEio; import crazypants.enderio.api.redstone.IRedstoneConnectable; import crazypants.enderio.config.Config; public abstract class AbstractMachineEntity extends TileEntityEio implements ISidedInventory, IMachine, IRedstoneModeControlable, IRedstoneConnectable, IIoConfigurable { public short facing; // Client sync monitoring protected int ticksSinceSync = -1; protected boolean forceClientUpdate = true; protected boolean lastActive; protected int ticksSinceActiveChanged = 0; protected ItemStack[] inventory; protected final SlotDefinition slotDefinition; protected RedstoneControlMode redstoneControlMode; protected boolean redstoneCheckPassed; private boolean redstoneStateDirty = true; protected Map<ForgeDirection, IoMode> faceModes; private final int[] allSlots; protected boolean notifyNeighbours = false; @SideOnly(Side.CLIENT) private MachineSound sound; private final ResourceLocation soundRes; public boolean isDirty = false; public static ResourceLocation getSoundFor(String sound) { return sound == null ? null : new ResourceLocation(EnderIO.DOMAIN + ":" + sound); } public AbstractMachineEntity(SlotDefinition slotDefinition) { this.slotDefinition = slotDefinition; facing = 3; inventory = new ItemStack[slotDefinition.getNumSlots()]; redstoneControlMode = RedstoneControlMode.IGNORE; soundRes = getSoundFor(getSoundName()); allSlots = new int[slotDefinition.getNumSlots()]; for (int i = 0; i < allSlots.length; i++) { allSlots[i] = i; } } @Override public IoMode toggleIoModeForFace(ForgeDirection faceHit) { IoMode curMode = getIoMode(faceHit); IoMode mode = curMode.next(); while (!supportsMode(faceHit, mode)) { mode = mode.next(); } setIoMode(faceHit, mode); return mode; } @Override public boolean supportsMode(ForgeDirection faceHit, IoMode mode) { return true; } @Override public void setIoMode(ForgeDirection faceHit, IoMode mode) { if(mode == IoMode.NONE && faceModes == null) { return; } if(faceModes == null) { faceModes = new EnumMap<ForgeDirection, IoMode>(ForgeDirection.class); } faceModes.put(faceHit, mode); forceClientUpdate = true; notifyNeighbours = true; updateBlock(); } @Override public void clearAllIoModes() { if(faceModes != null) { faceModes = null; forceClientUpdate = true; notifyNeighbours = true; updateBlock(); } } @Override public IoMode getIoMode(ForgeDirection face) { if(faceModes == null) { return IoMode.NONE; } IoMode res = faceModes.get(face); if(res == null) { return IoMode.NONE; } return res; } public SlotDefinition getSlotDefinition() { return slotDefinition; } public boolean isValidUpgrade(ItemStack itemstack) { for (int i = slotDefinition.getMinUpgradeSlot(); i <= slotDefinition.getMaxUpgradeSlot(); i++) { if(isItemValidForSlot(i, itemstack)) { return true; } } return false; } public boolean isValidInput(ItemStack itemstack) { for (int i = slotDefinition.getMinInputSlot(); i <= slotDefinition.getMaxInputSlot(); i++) { if(isItemValidForSlot(i, itemstack)) { return true; } } return false; } public boolean isValidOutput(ItemStack itemstack) { for (int i = slotDefinition.getMinOutputSlot(); i <= slotDefinition.getMaxOutputSlot(); i++) { if(isItemValidForSlot(i, itemstack)) { return true; } } return false; } @Override public final boolean isItemValidForSlot(int i, ItemStack itemstack) { if(slotDefinition.isUpgradeSlot(i)) { return itemstack != null && itemstack.getItem() == EnderIO.itemBasicCapacitor && itemstack.getItemDamage() > 0; } return isMachineItemValidForSlot(i, itemstack); } protected abstract boolean isMachineItemValidForSlot(int i, ItemStack itemstack); @Override public RedstoneControlMode getRedstoneControlMode() { return redstoneControlMode; } @Override public void setRedstoneControlMode(RedstoneControlMode redstoneControlMode) { this.redstoneControlMode = redstoneControlMode; redstoneStateDirty = true; updateBlock(); } public short getFacing() { return facing; } public ForgeDirection getFacingDir() { return ForgeDirection.getOrientation(facing); } public void setFacing(short facing) { this.facing = facing; } public abstract boolean isActive(); public String getSoundName() { return null; } public boolean hasSound() { return getSoundName() != null; } public float getVolume() { return Config.machineSoundVolume; } public float getPitch() { return 1.0f; } protected boolean shouldPlaySound() { return isActive() && !isInvalid(); } @SideOnly(Side.CLIENT) private void updateSound() { if(Config.machineSoundsEnabled && hasSound()) { if(shouldPlaySound()) { if(sound == null) { sound = new MachineSound(soundRes, xCoord + 0.5f, yCoord + 0.5f, zCoord + 0.5f, getVolume(), getPitch()); FMLClientHandler.instance().getClient().getSoundHandler().playSound(sound); } } else if(sound != null) { sound.endPlaying(); sound = null; } } } // --- Process Loop // -------------------------------------------------------------------------- @Override public void doUpdate() { if(worldObj.isRemote) { updateEntityClient(); return; } // else is server, do all logic only on the server boolean requiresClientSync = forceClientUpdate; boolean prevRedCheck = redstoneCheckPassed; if(redstoneStateDirty) { redstoneCheckPassed = RedstoneControlMode.isConditionMet(redstoneControlMode, this); redstoneStateDirty = false; } if(shouldDoWorkThisTick(5)) { requiresClientSync |= doSideIo(); } requiresClientSync |= prevRedCheck != redstoneCheckPassed; requiresClientSync |= processTasks(redstoneCheckPassed); if(requiresClientSync) { // this will cause 'getPacketDescription()' to be called and its result // will be sent to the PacketHandler on the other end of // client/server connection worldObj.markBlockForUpdate(xCoord, yCoord, zCoord); // And this will make sure our current tile entity state is saved markDirty(); } if(notifyNeighbours) { worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord, getBlockType()); notifyNeighbours = false; } } protected void updateEntityClient() { // check if the block on the client needs to update its texture if(isActive() != lastActive) { ticksSinceActiveChanged++; if(ticksSinceActiveChanged > 20 || isActive()) { ticksSinceActiveChanged = 0; lastActive = isActive(); forceClientUpdate = true; } } if(hasSound()) { updateSound(); } if(forceClientUpdate) { if (worldObj.rand.nextInt(1024) <= (isDirty ? 256 : 0)) { isDirty = !isDirty; } worldObj.markBlockForUpdate(xCoord, yCoord, zCoord); forceClientUpdate = false; } } protected boolean doSideIo() { if(faceModes == null) { return false; } boolean res = false; Set<Entry<ForgeDirection, IoMode>> ents = faceModes.entrySet(); for (Entry<ForgeDirection, IoMode> ent : ents) { IoMode mode = ent.getValue(); if(mode.pulls()) { res = res | doPull(ent.getKey()); } if(mode.pushes()) { res = res | doPush(ent.getKey()); } } return res; } protected boolean doPush(ForgeDirection dir) { if(slotDefinition.getNumOutputSlots() <= 0) { return false; } if(!shouldDoWorkThisTick(20)) { return false; } BlockCoord loc = getLocation().getLocation(dir); TileEntity te = worldObj.getTileEntity(loc.x, loc.y, loc.z); return doPush(dir, te, slotDefinition.minOutputSlot, slotDefinition.maxOutputSlot); } protected boolean doPush(ForgeDirection dir, TileEntity te, int minSlot, int maxSlot) { if(te == null) { return false; } for (int i = minSlot; i <= maxSlot; i++) { ItemStack item = inventory[i]; if(item != null) { int num = ItemUtil.doInsertItem(te, item, dir.getOpposite()); if(num > 0) { item.stackSize -= num; if(item.stackSize <= 0) { item = null; } inventory[i] = item; markDirty(); } } } return false; } protected boolean doPull(ForgeDirection dir) { if(slotDefinition.getNumInputSlots() <= 0) { return false; } if(!shouldDoWorkThisTick(20)) { return false; } boolean hasSpace = false; for (int slot = slotDefinition.minInputSlot; slot <= slotDefinition.maxInputSlot && !hasSpace; slot++) { hasSpace = inventory[slot] == null ? true : inventory[slot].stackSize < Math.min(inventory[slot].getMaxStackSize(), getInventoryStackLimit(slot)); } if(!hasSpace) { return false; } BlockCoord loc = getLocation().getLocation(dir); TileEntity te = worldObj.getTileEntity(loc.x, loc.y, loc.z); if(te == null) { return false; } if(!(te instanceof IInventory)) { return false; } ISidedInventory target; if(te instanceof ISidedInventory) { target = (ISidedInventory) te; } else { target = new InventoryWrapper((IInventory) te); } int[] targetSlots = target.getAccessibleSlotsFromSide(dir.getOpposite().ordinal()); if(targetSlots == null) { return false; } for (int inputSlot = slotDefinition.minInputSlot; inputSlot <= slotDefinition.maxInputSlot; inputSlot++) { if(doPull(inputSlot, target, targetSlots, dir)) { return false; } } return false; } protected boolean doPull(int inputSlot, ISidedInventory target, int[] targetSlots, ForgeDirection side) { for (int i = 0; i < targetSlots.length; i++) { int tSlot = targetSlots[i]; ItemStack targetStack = target.getStackInSlot(tSlot); if(targetStack != null && target.canExtractItem(i, targetStack, side.getOpposite().ordinal())) { int res = ItemUtil.doInsertItem(this, targetStack, side); if(res > 0) { targetStack = targetStack.copy(); targetStack.stackSize -= res; if(targetStack.stackSize <= 0) { targetStack = null; } target.setInventorySlotContents(tSlot, targetStack); return true; } } } return false; } protected abstract boolean processTasks(boolean redstoneCheckPassed); // ---- Tile Entity // ------------------------------------------------------------------------------ @Override public void invalidate() { super.invalidate(); if(worldObj.isRemote) { updateSound(); } } @Override public void readCustomNBT(NBTTagCompound nbtRoot) { setFacing(nbtRoot.getShort("facing")); redstoneCheckPassed = nbtRoot.getBoolean("redstoneCheckPassed"); forceClientUpdate = nbtRoot.getBoolean("forceClientUpdate"); readCommon(nbtRoot); } /** * Read state common to both block and item */ public void readCommon(NBTTagCompound nbtRoot) { // read in the inventories contents inventory = new ItemStack[slotDefinition.getNumSlots()]; NBTTagList itemList = (NBTTagList) nbtRoot.getTag("Items"); if(itemList != null) { for (int i = 0; i < itemList.tagCount(); i++) { NBTTagCompound itemStack = itemList.getCompoundTagAt(i); byte slot = itemStack.getByte("Slot"); if(slot >= 0 && slot < inventory.length) { inventory[slot] = ItemStack.loadItemStackFromNBT(itemStack); } } } int rsContr = nbtRoot.getInteger("redstoneControlMode"); if(rsContr < 0 || rsContr >= RedstoneControlMode.values().length) { rsContr = 0; } redstoneControlMode = RedstoneControlMode.values()[rsContr]; if(nbtRoot.hasKey("hasFaces")) { for (ForgeDirection dir : ForgeDirection.VALID_DIRECTIONS) { if(nbtRoot.hasKey("face" + dir.ordinal())) { setIoMode(dir, IoMode.values()[nbtRoot.getShort("face" + dir.ordinal())]); } } } } public void readFromItemStack(ItemStack stack) { if(stack == null || stack.stackTagCompound == null) { return; } NBTTagCompound root = stack.stackTagCompound; if(!root.hasKey("eio.abstractMachine")) { return; } readCommon(root); } @Override public void writeCustomNBT(NBTTagCompound nbtRoot) { nbtRoot.setShort("facing", facing); nbtRoot.setBoolean("redstoneCheckPassed", redstoneCheckPassed); nbtRoot.setBoolean("forceClientUpdate", forceClientUpdate); forceClientUpdate = false; writeCommon(nbtRoot); } /** * Write state common to both block and item */ public void writeCommon(NBTTagCompound nbtRoot) { // write inventory list NBTTagList itemList = new NBTTagList(); for (int i = 0; i < inventory.length; i++) { if(inventory[i] != null) { NBTTagCompound itemStackNBT = new NBTTagCompound(); itemStackNBT.setByte("Slot", (byte) i); inventory[i].writeToNBT(itemStackNBT); itemList.appendTag(itemStackNBT); } } nbtRoot.setTag("Items", itemList); nbtRoot.setInteger("redstoneControlMode", redstoneControlMode.ordinal()); //face modes if(faceModes != null) { nbtRoot.setByte("hasFaces", (byte) 1); for (Entry<ForgeDirection, IoMode> e : faceModes.entrySet()) { nbtRoot.setShort("face" + e.getKey().ordinal(), (short) e.getValue().ordinal()); } } } public void writeToItemStack(ItemStack stack) { if(stack == null) { return; } if(stack.stackTagCompound == null) { stack.stackTagCompound = new NBTTagCompound(); } NBTTagCompound root = stack.stackTagCompound; root.setBoolean("eio.abstractMachine", true); writeCommon(root); String name; if(stack.hasDisplayName()) { name = stack.getDisplayName(); } else { name = EnderIO.lang.localizeExact(stack.getUnlocalizedName() + ".name"); } name += " " + EnderIO.lang.localize("machine.tooltip.configured"); stack.setStackDisplayName(name); } // ---- Inventory // ------------------------------------------------------------------------------ @Override public boolean isUseableByPlayer(EntityPlayer player) { return canPlayerAccess(player); } @Override public int getSizeInventory() { return slotDefinition.getNumSlots(); } public int getInventoryStackLimit(int slot) { return getInventoryStackLimit(); } @Override public int getInventoryStackLimit() { return 64; } @Override public ItemStack getStackInSlot(int slot) { if(slot < 0 || slot >= inventory.length) { return null; } return inventory[slot]; } @Override public ItemStack decrStackSize(int fromSlot, int amount) { ItemStack fromStack = inventory[fromSlot]; if(fromStack == null) { return null; } if(fromStack.stackSize <= amount) { inventory[fromSlot] = null; return fromStack; } ItemStack result = new ItemStack(fromStack.getItem(), amount, fromStack.getItemDamage()); if(fromStack.stackTagCompound != null) { result.stackTagCompound = (NBTTagCompound) fromStack.stackTagCompound.copy(); } fromStack.stackSize -= amount; return result; } @Override public void setInventorySlotContents(int slot, ItemStack contents) { if(contents == null) { inventory[slot] = contents; } else { inventory[slot] = contents.copy(); } if(contents != null && contents.stackSize > getInventoryStackLimit(slot)) { contents.stackSize = getInventoryStackLimit(slot); } } @Override public ItemStack getStackInSlotOnClosing(int i) { return null; } @Override public void openInventory() { } @Override public void closeInventory() { } @Override public String getInventoryName() { return getMachineName(); } @Override public boolean hasCustomInventoryName() { return false; } @Override public int[] getAccessibleSlotsFromSide(int var1) { if(isSideDisabled(var1)) { return new int[0]; } return allSlots; } @Override public boolean canInsertItem(int slot, ItemStack itemstack, int side) { if(isSideDisabled(side) || !slotDefinition.isInputSlot(slot)) { return false; } ItemStack existing = inventory[slot]; if(existing != null) { // no point in checking the recipes if an item is already in the slot // worst case we get more of the wrong item - but that doesn't change anything return existing.isStackable() && existing.isItemEqual(itemstack); } // no need to call isItemValidForSlot as upgrade slots are not input slots return isMachineItemValidForSlot(slot, itemstack); } @Override public boolean canExtractItem(int slot, ItemStack itemstack, int side) { if(isSideDisabled(side)) { return false; } if(!slotDefinition.isOutputSlot(slot)) { return false; } return canExtractItem(slot, itemstack); } protected boolean canExtractItem(int slot, ItemStack itemstack) { if(inventory[slot] == null || inventory[slot].stackSize < itemstack.stackSize) { return false; } return itemstack.getItem() == inventory[slot].getItem(); } public boolean isSideDisabled(int var1) { ForgeDirection dir = ForgeDirection.getOrientation(var1); IoMode mode = getIoMode(dir); if(mode == IoMode.DISABLED) { return true; } return false; } public void onNeighborBlockChange(Block blockId) { redstoneStateDirty = true; } /* IRedstoneConnectable */ @Override public boolean shouldRedstoneConduitConnect(World world, int x, int y, int z, ForgeDirection from) { return true; } }