com.teambrmodding.neotech.common.tiles.AbstractMachine.java Source code

Java tutorial

Introduction

Here is the source code for com.teambrmodding.neotech.common.tiles.AbstractMachine.java

Source

package com.teambrmodding.neotech.common.tiles;

import com.teambr.bookshelf.common.container.IInventoryCallback;
import com.teambr.bookshelf.common.tiles.EnergyHandler;
import com.teambr.bookshelf.common.tiles.InventoryHandler;
import com.teambr.bookshelf.util.ClientUtils;
import com.teambrmodding.neotech.collections.EnumInputOutputMode;
import com.teambrmodding.neotech.common.tiles.traits.IUpgradeItem;
import com.teambrmodding.neotech.managers.CapabilityLoadManager;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.ItemStackHelper;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.NonNullList;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTank;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.ItemHandlerHelper;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import static com.teambrmodding.neotech.common.tiles.traits.IUpgradeItem.ENUM_UPGRADE_CATEGORY.*;

/**
 * This file was created for NeoTech
 *
 * NeoTech is licensed under the
 * Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License:
 * http://creativecommons.org/licenses/by-nc-sa/4.0/
 *
 * @author Paul Davis - pauljoda
 * @since 2/11/2017
 */
public abstract class AbstractMachine extends EnergyHandler implements IItemHandlerModifiable, IFluidHandler {

    /*******************************************************************************************************************
     * AbstractMachine Variables                                                                                       *
     *******************************************************************************************************************/

    // Current Redstone Mode
    public int redstone = 0;
    // Working variable
    public boolean working = false;

    // NBT Tags
    public static final String REDSTONE_NBT = "RedstoneMode";
    public static final String UPDATE_ENERGY_NBT = "UpdateEnergy";
    public static final String SIDE_MODE_NBT = "Side: ";

    // Syncing Variables
    public static final int REDSTONE_FIELD_ID = 0;
    public static final int IO_FIELD_ID = 1;
    public static final int UPDATE_CLIENT_ID = 2;

    /*******************************************************************************************************************
     * Input Output Variables                                                                                          *
     *******************************************************************************************************************/

    // Holds valid modes for this instance
    public List<EnumInputOutputMode> validModes = new ArrayList<>();
    // Holds the current mode for each side
    public HashMap<EnumFacing, EnumInputOutputMode> sideModes = new HashMap<>();

    /*******************************************************************************************************************
     * Inventory Variables                                                                                             *
     *******************************************************************************************************************/

    // A list to hold all callback objects
    private List<IInventoryCallback> callBacks = new ArrayList<>();
    // List of Inventory contents
    public NonNullList<ItemStack> inventoryContents = NonNullList.withSize(getInventorySize(), ItemStack.EMPTY);

    // NBT Tags
    protected static final String ITEMS_NBT_TAG = "Inventory";

    // Side Handlers
    private IItemHandler handlerTop = new AbstractMachineSidedWrapper(this, EnumFacing.UP);
    private IItemHandler handlerDown = new AbstractMachineSidedWrapper(this, EnumFacing.DOWN);
    private IItemHandler handlerNorth = new AbstractMachineSidedWrapper(this, EnumFacing.NORTH);
    private IItemHandler handlerSouth = new AbstractMachineSidedWrapper(this, EnumFacing.SOUTH);
    private IItemHandler handlerEast = new AbstractMachineSidedWrapper(this, EnumFacing.EAST);
    private IItemHandler handlerWest = new AbstractMachineSidedWrapper(this, EnumFacing.WEST);

    /*******************************************************************************************************************
     * FluidHandler Variables                                                                                          *
     *******************************************************************************************************************/

    // NBT Tags
    protected static final String SIZE_FLUID_NBT_TAG = "SizeFluids";
    protected static final String TANK_ID_NBT_TAG = "TankID";
    protected static final String TANKS_NBT_TAG = "Tanks";

    // Tanks
    public FluidTank[] tanks;

    /*******************************************************************************************************************
     * Upgradeable Variables                                                                                           *
     *******************************************************************************************************************/

    public static final int UPGRADE_INVENTORY_SIZE = 6;

    public InventoryHandler upgradeInventory = new InventoryHandler() {
        @Override
        protected int getInventorySize() {
            return UPGRADE_INVENTORY_SIZE;
        }

        @Override
        protected boolean isItemValidForSlot(int index, ItemStack stack) {
            return !hasUpgradeAlready(stack);
        }

        @Override
        public void setVariable(int id, double value) {
        }

        @Override
        public Double getVariable(int id) {
            return null;
        }
    };

    // NBT Tags
    public static final String UPGRADE_INVENTORY_NBT = "UpgradeInventory";

    /*******************************************************************************************************************
     * Constructor                                                                                                     *
     *******************************************************************************************************************/

    /**
     * Main constructor, load things needed here
     */
    public AbstractMachine() {
        super();
        // Input Output
        setupValidModes();
        resetIO();

        // FluidHandler
        setupTanks();

        // Upgradeable
        // Add callback for our upgrade inventory
        upgradeInventory.addCallback((inventory, slotNumber) -> {
            if (inventory.getStackInSlot(slotNumber) == null)
                resetValues();
            upgradeInventoryChanged(slotNumber);
        });
    }

    /*******************************************************************************************************************
     * Abstract Methods                                                                                                *
     *******************************************************************************************************************/

    /**
     * Used to get what particles to spawn. This will be called when the tile is active
     */
    public abstract void spawnActiveParticles(double xPos, double yPos, double zPos);

    /**
     * Used to get what slots are allowed to be output
     *
     * @return The slots to output from
     */
    public abstract int[] getOutputSlots(EnumInputOutputMode mode);

    /**
     * Used to get what slots are allowed to be input
     *
     * @return The slots to input from
     */
    public abstract int[] getInputSlots(EnumInputOutputMode mode);

    /**
     * Used to output the redstone single from this structure
     *
     * Use a range from 0 - 16.
     *
     * 0 Usually means that there is nothing in the tile, so take that for lowest level. Like the generator has no energy while
     * 16 is usually the flip side of that. Output 16 when it is totally full and not less
     *
     * @return int range 0 - 16
     */
    public abstract int getRedstoneOutput();

    /**
     * Used to actually do the processes needed. For processors this should be cooking items and generators should
     * generate RF. This is called every tick allowed, provided redstone mode requirements are met
     */
    protected abstract void doWork();

    /**
     * Use this to set all variables back to the default values, usually means the operation failed
     */
    public abstract void reset();

    /**
     * The initial size of the inventory
     */
    public abstract int getInventorySize();

    /**
     * Used to define if an item is valid for a slot
     *
     * @param index The slot id
     * @param stack The stack to check
     * @return True if you can put this there
     */
    public abstract boolean isItemValidForSlot(int index, ItemStack stack);

    /**
     * Get the slots for the given face
     * @param face The face
     * @return What slots can be accessed
     */
    public abstract int[] getSlotsForFace(EnumFacing face);

    /**
     * Can insert the item into the inventory
     * @param slot The slot
     * @param itemStackIn The stack to insert
     * @param dir The dir
     * @return True if can insert
     */
    public abstract boolean canInsertItem(int slot, ItemStack itemStackIn, EnumFacing dir);

    /**
     * Can this extract the item
     * @param slot The slot
     * @param stack The stack
     * @param dir The dir
     * @return True if can extract
     */
    public abstract boolean canExtractItem(int slot, ItemStack stack, EnumFacing dir);

    /**
     * Add all modes you want, in order, here
     */
    public abstract void addValidModes();

    /**
     * This will try to take things from other inventories and put it into ours
     */
    public abstract void tryInput();

    /**
     * This will try to take things from our inventory and try to place them in others
     */
    public abstract void tryOutput();

    /**
     * Used to get the description to display on the tab
     * @return The long string with the description
     */
    @Nullable
    @SideOnly(Side.CLIENT)
    public abstract String getDescription();

    /**
     * Return the container for this tile
     *
     * @param id Id, probably not needed but could be used for multiple guis
     * @param player The player that is opening the gui
     * @param worldObj The worldObj
     * @param x X Pos
     * @param y Y Pos
     * @param z Z Pos
     * @return The container to open
     */
    public abstract Object getServerGuiElement(int id, EntityPlayer player, World worldObj, int x, int y, int z);

    /**
     * Return the gui for this tile
     *
     * @param id Id, probably not needed but could be used for multiple guis
     * @param player The player that is opening the gui
     * @param worldObj The worldObj
     * @param x X Pos
     * @param y Y Pos
     * @param z Z Pos
     * @return The gui to open
     */
    public abstract Object getClientGuiElement(int id, EntityPlayer player, World worldObj, int x, int y, int z);

    /*******************************************************************************************************************
     * TileEntity Methods                                                                                              *
     *******************************************************************************************************************/

    // Make sure we don't keep rendering back and forth
    private int updateCooldown = 60;

    @Override
    protected void onClientTick() {
        if (getSupposedEnergy() != energyStorage.getMaxStored())
            sendValueToClient(UPDATE_CLIENT_ID, 0);

        // Mark for render update if needed
        updateCooldown -= 1;
        if (updateCooldown < 0 && working != isActive()) {
            markForUpdate(3);
            working = isActive();
            updateCooldown = 60;
        }
    }

    private int timeTicker = 0;

    @Override
    protected void onServerTick() {
        super.onServerTick();
        // Make sure our energy storage is correct
        if (getSupposedEnergy() != energyStorage.getMaxStored())
            changeEnergy(energyStorage.getEnergyStored());

        // If redstone mode is not matched, break our of update
        if (hasUpgradeByID(IUpgradeItem.REDSTONE_CIRCUIT)) {
            if (redstone == -1 && isPowered())
                return;
            else if (redstone == 1 && !isPowered())
                return;
        }

        // We want to try automatic IO if we are able to
        if (shouldHandleIO() && timeTicker <= 0 && hasUpgradeByID(IUpgradeItem.NETWORK_CARD)) {
            timeTicker = 20 - getModifierForCategory(IUpgradeItem.ENUM_UPGRADE_CATEGORY.CPU);
            tryInput();
            tryOutput();
        }
        timeTicker -= 1;

        // Do the work
        doWork();
    }

    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound compound) {
        super.writeToNBT(compound);

        // Upgrades
        NBTTagCompound upgradeTagCompound = new NBTTagCompound();
        ItemStackHelper.saveAllItems(upgradeTagCompound, upgradeInventory.inventoryContents);
        compound.setTag(UPGRADE_INVENTORY_NBT, upgradeTagCompound);

        // Inventory
        NBTTagCompound inventory = new NBTTagCompound();
        ItemStackHelper.saveAllItems(inventory, inventoryContents);
        compound.setTag(ITEMS_NBT_TAG, inventory);

        // FluidHandler
        if (isFluidHandler()) {
            int id = 0;
            compound.setInteger(SIZE_FLUID_NBT_TAG, tanks.length);
            NBTTagList tagListFluid = new NBTTagList();
            for (FluidTank tank : tanks) {
                if (tank != null) {
                    NBTTagCompound tankCompound = new NBTTagCompound();
                    tankCompound.setByte(TANK_ID_NBT_TAG, (byte) id);
                    id += 1;
                    tank.writeToNBT(tankCompound);
                    tagListFluid.appendTag(tankCompound);
                }
            }
            compound.setTag(TANKS_NBT_TAG, tagListFluid);
        }

        // Input Output
        for (EnumFacing side : EnumFacing.values())
            compound.setInteger(SIDE_MODE_NBT + side.ordinal(), sideModes.get(side).getIntValue());

        // Abstract Machine
        compound.setInteger(REDSTONE_NBT, redstone);
        if (updateClient && world != null) {
            compound.setBoolean(UPDATE_ENERGY_NBT, true);
            updateClient = false;
        }

        return compound;
    }

    @Override
    public void readFromNBT(NBTTagCompound compound) {
        super.readFromNBT(compound);

        // Upgrades
        NBTTagCompound upgradeTag = compound.getCompoundTag(UPGRADE_INVENTORY_NBT);
        ItemStackHelper.loadAllItems(upgradeTag, upgradeInventory.inventoryContents);

        // Inventory
        NBTTagCompound inventoryTag = compound.getCompoundTag(ITEMS_NBT_TAG);
        ItemStackHelper.loadAllItems(inventoryTag, inventoryContents);

        // FluidHandler
        if (isFluidHandler()) {
            NBTTagList tagListFluid = compound.getTagList(TANKS_NBT_TAG, 10);
            int size = compound.getInteger(SIZE_FLUID_NBT_TAG);
            if (size != tanks.length && compound.hasKey(SIZE_FLUID_NBT_TAG))
                tanks = new FluidTank[size];
            for (int x = 0; x < tagListFluid.tagCount(); x++) {
                NBTTagCompound tankCompound = tagListFluid.getCompoundTagAt(x);
                byte position = tankCompound.getByte(TANK_ID_NBT_TAG);
                if (position < tanks.length)
                    tanks[position].readFromNBT(tankCompound);
            }
        }

        // Input Output
        for (EnumFacing side : EnumFacing.values())
            sideModes.put(side,
                    EnumInputOutputMode.getModeFromInt(compound.getInteger(SIDE_MODE_NBT + side.ordinal())));

        // Abstract Machine
        if (compound.hasKey(UPDATE_ENERGY_NBT) && world != null)
            changeEnergy(compound.getInteger("Energy"));
        redstone = compound.getInteger(REDSTONE_NBT);
    }

    @Override
    public boolean hasCapability(@Nonnull Capability<?> capability, EnumFacing facing) {
        return (!isDisabled(facing) || getModeForSide(facing) == EnumInputOutputMode.DEFAULT)
                && ((capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY
                        && getItemHandlerCapability(facing) != null)
                        || (isFluidHandler() && capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY
                                && getFluidHandlerCapability(facing) != null)
                        || super.hasCapability(capability, facing));
    }

    @Override
    public <T> T getCapability(@Nonnull Capability<T> capability, EnumFacing facing) {
        if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY
                && getItemHandlerCapability(facing) != null) {
            return (T) getItemHandlerCapability(facing);
        }
        if (isFluidHandler() && capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY
                && getFluidHandlerCapability(facing) != null) {
            return (T) getFluidHandlerCapability(facing);
        }
        return super.getCapability(capability, facing);
    }

    /**
     * Used to expose the item handler capability, in child classes, manage input and output based on this
     * @param dir The direction
     * @return The item handler
     */
    @Nullable
    protected IItemHandler getItemHandlerCapability(EnumFacing dir) {
        switch (dir) {
        case UP:
            return handlerTop;
        case DOWN:
            return handlerDown;
        case NORTH:
            return handlerNorth;
        case SOUTH:
            return handlerSouth;
        case EAST:
            return handlerEast;
        case WEST:
            return handlerWest;
        default:
            return this;
        }
    }

    /**
     * Used to get the fluid handler for exposing
     * @param dir The direction
     * @return The fluid handler
     */
    @Nullable
    protected IFluidHandler getFluidHandlerCapability(EnumFacing dir) {
        return this;
    }

    /**
     * Used to check if this tile is active or not
     *
     * @return True if active state
     */
    public boolean isActive() {
        // If redstone mode is not matched, break our of update
        if (hasUpgradeByID(IUpgradeItem.REDSTONE_CIRCUIT)) {
            if (redstone == -1 && isPowered())
                return false;
            else if (redstone == 1 && !isPowered())
                return false;
        }
        return true;
    }

    /*******************************************************************************************************************
     * Energy Methods                                                                                                  *
     *******************************************************************************************************************/

    // Tag to let us know if we need to push update
    private boolean updateClient = false;

    /**
     * Used to change the energy to a new storage with a different size
     *
     * @param initial How much was in the old storage
     */
    public void changeEnergy(int initial) {
        energyStorage.setMaxStored(getSupposedEnergy());
        energyStorage.setMaxInsert(getSupposedEnergy());
        energyStorage.setMaxExtract(getSupposedEnergy());
        updateClient = true;
        energyStorage.setCurrentStored(initial);
        if (energyStorage.getCurrentStored() > energyStorage.getMaxEnergyStored())
            energyStorage.setCurrentStored(energyStorage.getMaxEnergyStored());
        markForUpdate(3);
    }

    /**
     * Used to determine how much energy should be in this tile
     *
     * @return How much energy should be available
     */
    public int getSupposedEnergy() {
        return getDefaultEnergyStorageSize() * (getModifierForCategory(PSU));
    }

    /*******************************************************************************************************************
     * Input Output Methods                                                                                            *
     *******************************************************************************************************************/

    /**
     * Used to specify if this tile should handle IO, and render in GUI
     *
     * @return False to prevent
     */
    public boolean shouldHandleIO() {
        return true;
    }

    /**
     * Used to manually disable the IO rendering on tile, true by default
     *
     * @return False to prevent rendering
     */
    public boolean shouldRenderInputOutputOnTile() {
        return shouldHandleIO();
    }

    /**
     * Used to set the default modes for this, calls the helper function to the child
     */
    public void setupValidModes() {
        // Set to default as the first
        validModes.add(EnumInputOutputMode.DEFAULT);

        // Add in specific modes
        addValidModes();

        // Add disabled to end
        validModes.add(EnumInputOutputMode.DISABLED);
    }

    /**
     * Resets everything to default mode
     */
    public void resetIO() {
        for (EnumFacing dir : EnumFacing.values())
            sideModes.put(dir, validModes.get(0));
    }

    /**
     * Toggles the mode to the next available mode in the list
     *
     * @param dir The face to toggle
     */
    public void toggleMode(EnumFacing dir) {
        // Loop to find the next value
        EnumInputOutputMode nextMode = null;
        boolean selectNext = false;

        for (EnumInputOutputMode mode : validModes) {
            if (selectNext) {
                nextMode = mode;
                break;
            }
            if (mode == sideModes.get(dir))
                selectNext = true;
        }

        // Need to loop back around
        if (nextMode == null)
            nextMode = validModes.get(0);

        // Update Collection
        sideModes.put(dir, nextMode);
    }

    /**
     * Used to check if the side is set to a mode that allows output
     * @param dir The face to output from
     * @param isPrimary Is this a primary output or false for secondary
     * @return True if you can move
     */
    public boolean canOutputFromSide(EnumFacing dir, boolean isPrimary) {
        if (dir == null)
            return true;
        if (isDisabled(dir))
            return false;

        if (isPrimary)
            return sideModes.get(dir) == EnumInputOutputMode.ALL_MODES
                    || sideModes.get(dir) == EnumInputOutputMode.OUTPUT_ALL
                    || sideModes.get(dir) == EnumInputOutputMode.OUTPUT_PRIMARY;
        else
            return sideModes.get(dir) == EnumInputOutputMode.ALL_MODES
                    || sideModes.get(dir) == EnumInputOutputMode.OUTPUT_ALL
                    || sideModes.get(dir) == EnumInputOutputMode.OUTPUT_SECONDARY;
    }

    /**
     * Used to check if the side is set to a mode that allows input
     * @param dir The face to input from
     * @param isPrimary Is this a primary input or false for secondary
     * @return True if you can move
     */
    public boolean canInputFromSide(EnumFacing dir, boolean isPrimary) {
        if (dir == null)
            return true;
        if (isDisabled(dir))
            return false;

        if (isPrimary)
            return sideModes.get(dir) == EnumInputOutputMode.ALL_MODES
                    || sideModes.get(dir) == EnumInputOutputMode.INPUT_ALL
                    || sideModes.get(dir) == EnumInputOutputMode.INPUT_PRIMARY;
        else
            return sideModes.get(dir) == EnumInputOutputMode.ALL_MODES
                    || sideModes.get(dir) == EnumInputOutputMode.INPUT_ALL
                    || sideModes.get(dir) == EnumInputOutputMode.INPUT_SECONDARY;
    }

    /**
     * Used to check if the side has been set to disabled
     * @param dir The face
     * @return True if disabled
     */
    public boolean isDisabled(EnumFacing dir) {
        return dir != null && sideModes.get(dir) == EnumInputOutputMode.DISABLED;
    }

    /**
     * Used to get the mode for a specific side, probably should use this as there are helper methods but
     * some instances may need this
     * @param dir The face
     * @return The mode for the side
     */
    public EnumInputOutputMode getModeForSide(EnumFacing dir) {
        return sideModes.get(dir);
    }

    /**
     * Get the string used to find the texture for this mode
     */
    public String getDisplayIconForSide(EnumFacing dir) {
        switch (getModeForSide(dir)) {
        case INPUT_ALL:
            return "neotech:blocks/inputface";
        case INPUT_PRIMARY:
            return "neotech:blocks/inputfaceprimary";
        case INPUT_SECONDARY:
            return "neotech:blocks/inputfacesecondary";
        case OUTPUT_ALL:
            return "neotech:blocks/outputface";
        case OUTPUT_PRIMARY:
            return "neotech:blocks/outputfaceprimary";
        case OUTPUT_SECONDARY:
            return "neotech:blocks/outputfacesecondary";
        case ALL_MODES:
            return "neotech:blocks/inputoutputface";
        case DISABLED:
            return "neotech:blocks/disabled";
        default:
            return null;
        }
    }

    /*******************************************************************************************************************
     * FluidHandler Methods                                                                                            *
     *******************************************************************************************************************/

    /**
     * Method to define if this tile is a fluid handler
     * @return False by default, override in child classes to enable fluid handling
     */
    public boolean isFluidHandler() {
        return false;
    }

    /**
     * Used to set up the tanks needed. You can insert any number of tanks
     *
     * MUST OVERRIDE IN CHILD CLASSES IF isFluidHandler RETURNS TRUE
     */
    protected void setupTanks() {
    }

    /**
     * Which tanks can input
     *
     * MUST OVERRIDE IN CHILD CLASSES IF isFluidHandler RETURNS TRUE
     *
     * @return An array with the indexes of the input tanks
     */
    protected int[] getInputTanks() {
        return new int[0];
    }

    /**
     * Which tanks can output
     *
     * MUST OVERRIDE IN CHILD CLASSES IF isFluidHandler RETURNS TRUE
     *
     * @return An array with the indexes of the output tanks
     */
    protected int[] getOutputTanks() {
        return new int[0];
    }

    /**
     * Called when something happens to the tank, you should mark the block for update here if a tile
     */
    public void onTankChanged(FluidTank tank) {
        markForUpdate(3);
    }

    /**
     * Used to convert a number of buckets into MB
     *
     * @param buckets How many buckets
     * @return The amount of buckets in MB
     */
    public int bucketsToMB(int buckets) {
        return Fluid.BUCKET_VOLUME * buckets;
    }

    /**
     * Returns true if the given fluid can be inserted
     *
     * More formally, this should return true if fluid is able to enter
     */
    protected boolean canFill(Fluid fluid) {
        for (Integer x : getInputTanks()) {
            if (x < tanks.length)
                if ((tanks[x].getFluid() == null || tanks[x].getFluid().getFluid() == null)
                        || (tanks[x].getFluid() != null && tanks[x].getFluid().getFluid() == fluid))
                    return true;
        }
        return false;
    }

    /**
     * Returns true if the given fluid can be extracted
     *
     * More formally, this should return true if fluid is able to leave
     */
    protected boolean canDrain(Fluid fluid) {
        for (Integer x : getOutputTanks()) {
            if (x < tanks.length)
                if (tanks[x].getFluid() != null && tanks[x].getFluid().getFluid() != null)
                    return true;
        }
        return false;
    }

    /**
     * Returns an array of objects which represent the internal tanks.
     * These objects cannot be used to manipulate the internal tanks.
     *
     * @return Properties for the relevant internal tanks.
     */
    @Override
    public IFluidTankProperties[] getTankProperties() {
        IFluidTankProperties[] properties = new IFluidTankProperties[tanks.length];
        for (int x = 0; x < tanks.length; x++) {
            final FluidTank tank = tanks[x];
            properties[x] = new IFluidTankProperties() {
                @Nullable
                @Override
                public FluidStack getContents() {
                    return tank.getFluid();
                }

                @Override
                public int getCapacity() {
                    return tank.getCapacity();
                }

                @Override
                public boolean canFill() {
                    return tank.canFill();
                }

                @Override
                public boolean canDrain() {
                    return tank.canDrain();
                }

                @Override
                public boolean canFillFluidType(FluidStack fluidStack) {
                    return tank.canFillFluidType(fluidStack);
                }

                @Override
                public boolean canDrainFluidType(FluidStack fluidStack) {
                    return tank.canDrainFluidType(fluidStack);
                }
            };
        }
        return properties;
    }

    /**
     * Fills fluid into internal tanks, distribution is left entirely to the IFluidHandler.
     *
     * @param resource FluidStack representing the Fluid and maximum amount of fluid to be filled.
     * @param doFill   If false, fill will only be simulated.
     * @return Amount of resource that was (or would have been, if simulated) filled.
     */
    @Override
    public int fill(FluidStack resource, boolean doFill) {
        if (resource != null && resource.getFluid() != null && canFill(resource.getFluid())) {
            for (Integer x : getInputTanks()) {
                if (x < tanks.length) {
                    if (tanks[x].fill(resource, false) > 0) {
                        int actual = tanks[x].fill(resource, doFill);
                        if (doFill)
                            onTankChanged(tanks[x]);
                        return actual;
                    }
                }
            }
        }
        return 0;
    }

    /**
     * Drains fluid out of internal tanks, distribution is left entirely to the IFluidHandler.
     * <p/>
     * This method is not Fluid-sensitive.
     *
     * @param maxDrain Maximum amount of fluid to drain.
     * @param doDrain  If false, drain will only be simulated.
     * @return FluidStack representing the Fluid and amount that was (or would have been, if
     * simulated) drained.
     */
    @Nullable
    @Override
    public FluidStack drain(int maxDrain, boolean doDrain) {
        FluidStack fluidStack = null;
        for (Integer x : getOutputTanks()) {
            if (x < tanks.length) {
                fluidStack = tanks[x].drain(maxDrain, false);
                if (fluidStack != null) {
                    tanks[x].drain(maxDrain, doDrain);
                    if (doDrain)
                        onTankChanged(tanks[x]);
                    return fluidStack;
                }
            }
        }
        return fluidStack;
    }

    /**
     * Drains fluid out of internal tanks, distribution is left entirely to the IFluidHandler.
     *
     * @param resource FluidStack representing the Fluid and maximum amount of fluid to be drained.
     * @param doDrain  If false, drain will only be simulated.
     * @return FluidStack representing the Fluid and amount that was (or would have been, if
     * simulated) drained.
     */
    @Nullable
    @Override
    public FluidStack drain(FluidStack resource, boolean doDrain) {
        return drain(resource.amount, doDrain);
    }

    /*******************************************************************************************************************
     * InventoryHandler Methods                                                                                        *
     *******************************************************************************************************************/

    /**
     * Add a callback to this inventory
     * @param iInventoryCallback The callback you wish to add
     * @return This object, to enable chaining
     */
    public AbstractMachine addCallback(IInventoryCallback iInventoryCallback) {
        callBacks.add(iInventoryCallback);
        return this;
    }

    /**
     * Called when the inventory has a change
     *
     * @param slot The slot that changed
     */
    protected void onInventoryChanged(int slot) {
        callBacks.forEach((IInventoryCallback callback) -> {
            callback.onInventoryChanged(this, slot);
        });
    }

    /**
     * Used to copy from an existing inventory
     *
     * @param inventory The inventory to copy from
     */
    public void copyFrom(IItemHandler inventory) {
        for (int i = 0; i < inventory.getSlots(); i++) {
            if (i < inventoryContents.size()) {
                ItemStack stack = inventory.getStackInSlot(i);
                if (!stack.isEmpty())
                    inventoryContents.set(i, stack.copy());
                else
                    inventoryContents.set(i, ItemStack.EMPTY);
            }
        }
    }

    /**
     * Makes sure this slot is within our range
     * @param slot Which slot
     */
    protected boolean isValidSlot(int slot) {
        return slot > 0 || slot <= inventoryContents.size();
    }

    /**
     * Overrides the stack in the given slot. This method is used by the
     * standard Forge helper methods and classes. It is not intended for
     * general use by other mods, and the handler may throw an error if it
     * is called unexpectedly.
     *
     * @param slot  Slot to modify
     * @param stack ItemStack to set slot to (may be null)
     * @throws RuntimeException if the handler is called in a way that the handler
     * was not expecting.
     **/
    @Override
    public void setStackInSlot(int slot, ItemStack stack) {
        if (!isValidSlot(slot))
            return;
        if (ItemStack.areItemStacksEqual(this.inventoryContents.get(slot), stack))
            return;
        this.inventoryContents.set(slot, stack);
        onInventoryChanged(slot);
    }

    /**
     * Returns the number of slots available
     *
     * @return The number of slots available
     **/
    @Override
    public int getSlots() {
        return inventoryContents.size();
    }

    /**
     * Returns the ItemStack in a given slot.
     *
     * The result's stack size may be greater than the itemstacks max size.
     *
     * If the result is null, then the slot is empty.
     * If the result is not null but the stack size is zero, then it represents
     * an empty slot that will only accept* a specific itemstack.
     *
     * <p/>
     * IMPORTANT: This ItemStack MUST NOT be modified. This method is not for
     * altering an inventories contents. Any implementers who are able to detect
     * modification through this method should throw an exception.
     * <p/>
     * SERIOUSLY: DO NOT MODIFY THE RETURNED ITEMSTACK
     *
     * @param slot Slot to query
     * @return ItemStack in given slot. May not be null.
     **/
    @Override
    @Nonnull
    public ItemStack getStackInSlot(int slot) {
        if (!isValidSlot(slot))
            return ItemStack.EMPTY;
        return inventoryContents.get(slot);
    }

    /**
     * Inserts an ItemStack into the given slot and return the remainder.
     * The ItemStack should not be modified in this function!
     * Note: This behaviour is subtly different from IFluidHandlers.fill()
     *
     * @param slot     Slot to insert into.
     * @param stack    ItemStack to insert.
     * @param simulate If true, the insertion is only simulated
     * @return The remaining ItemStack that was not inserted (if the entire stack is accepted, then return null).
     *         May be the same as the input ItemStack if unchanged, otherwise a new ItemStack.
     **/
    @Nonnull
    @Override
    public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) {
        if (stack.isEmpty() || !isItemValidForSlot(slot, stack))
            return ItemStack.EMPTY;

        if (!isValidSlot(slot))
            return ItemStack.EMPTY;

        ItemStack existing = this.inventoryContents.get(slot);

        int limit = getSlotLimit(slot);

        if (!existing.isEmpty()) {
            if (!ItemHandlerHelper.canItemStacksStack(stack, existing))
                return stack;

            limit -= existing.getCount();
        }

        if (limit <= 0)
            return stack;

        boolean reachedLimit = stack.getCount() > limit;

        if (!simulate) {
            if (existing.isEmpty()) {
                this.inventoryContents.set(slot,
                        reachedLimit ? ItemHandlerHelper.copyStackWithSize(stack, limit) : stack);
            } else {
                existing.grow(reachedLimit ? limit : stack.getCount());
            }
            onInventoryChanged(slot);
        }
        return reachedLimit ? ItemHandlerHelper.copyStackWithSize(stack, stack.getCount() - limit)
                : ItemStack.EMPTY;
    }

    /**
     * Extracts an ItemStack from the given slot. The returned value must be null
     * if nothing is extracted, otherwise it's stack size must not be greater than amount or the
     * itemstacks getMaxStackSize().
     *
     * @param slot     Slot to extract from.
     * @param amount   Amount to extract (may be greater than the current stacks max limit)
     * @param simulate If true, the extraction is only simulated
     * @return ItemStack extracted from the slot, must be null, if nothing can be extracted
     **/
    @Nonnull
    @Override
    public ItemStack extractItem(int slot, int amount, boolean simulate) {
        if (amount == 0)
            return ItemStack.EMPTY;

        if (!isValidSlot(slot))
            return ItemStack.EMPTY;
        ItemStack existing = this.inventoryContents.get(slot);

        if (existing.isEmpty())
            return ItemStack.EMPTY;

        int toExtract = Math.min(amount, existing.getMaxStackSize());

        if (existing.getCount() <= toExtract) {
            if (!simulate) {
                this.inventoryContents.set(slot, ItemStack.EMPTY);
                onInventoryChanged(slot);
            }
            return existing;
        } else {
            if (!simulate) {
                this.inventoryContents.set(slot,
                        ItemHandlerHelper.copyStackWithSize(existing, existing.getCount() - toExtract));
                onInventoryChanged(slot);
            }

            return ItemHandlerHelper.copyStackWithSize(existing, toExtract);
        }
    }

    /**
     * Retrieves the maximum stack size allowed to exist in the given slot.
     *
     * @param slot Slot to query.
     * @return The maximum stack size allowed in the slot.
     */
    @Override
    public int getSlotLimit(int slot) {
        return 64;
    }

    /*******************************************************************************************************************
     * Upgradable Methods                                                                                              *
     *******************************************************************************************************************/

    /**
     * Called when the upgrade inventory changes, reset to default values for any upgrades
     */
    public void resetValues() {
        if (!hasUpgradeByID(IUpgradeItem.NETWORK_CARD))
            resetIO();
        if (!hasUpgradeByID(IUpgradeItem.REDSTONE_CIRCUIT))
            redstone = 0;
    }

    /**
     * Called when upgrade inventory is changed
     */
    public void upgradeInventoryChanged(int slot) {
    }

    /**
     * Return the list of upgrades by their id that are allowed in this machine
     * @return A list of valid upgrades
     */
    public List<String> getAcceptableUpgrades() {
        return new ArrayList<>();
    }

    /**
     * Checks the inventory for existing cases of a tiered upgrade, also checks for existing by ID
     * @param stack The stack to check
     * @return True if we have this already
     */
    public boolean hasUpgradeAlready(ItemStack stack) {
        // Make sure this is acceptable to insert
        if (stack.hasCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null)) {
            // Cast to our data object
            IUpgradeItem upgradeItem = stack.getCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null);

            // Check if valid
            if (!getAcceptableUpgrades().contains(upgradeItem.getID()))
                return true; // Well, doesn't have but can't have anyway so fail

            // Check against slots
            for (int x = 0; x < upgradeInventory.getSlots(); x++) {
                // Grab item in slot
                ItemStack slottedItem = upgradeInventory.getStackInSlot(x);
                // If we have an item here, and for type cast security it is valid
                if (slottedItem.hasCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null)) {
                    // Cast to our data object
                    IUpgradeItem slottedUpgrade = slottedItem
                            .getCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null);
                    // Check for matching category, only on tiered objects, otherwise match by ID
                    switch (slottedUpgrade.getCategory()) {
                    case CPU:
                        if (upgradeItem.getCategory() == CPU)
                            return slottedItem.getCount() == slottedItem.getMaxStackSize();
                        break;
                    case HDD:
                        if (upgradeItem.getCategory() == HDD)
                            return slottedItem.getCount() == slottedItem.getMaxStackSize();
                        break;
                    case MEMORY:
                        if (upgradeItem.getCategory() == MEMORY)
                            return slottedItem.getCount() == slottedItem.getMaxStackSize();
                        break;
                    case PSU:
                        if (upgradeItem.getCategory() == PSU)
                            return slottedItem.getCount() == slottedItem.getMaxStackSize();
                        break;
                    case MISC:
                        break;
                    default:
                    case NONE:
                        if (upgradeItem.getID().equalsIgnoreCase(slottedUpgrade.getID()))
                            return slottedItem.getCount() == slottedItem.getMaxStackSize();
                        break;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Used to get the upgrade count by id
     * @param id The id of the upgrade
     * @return How many are present, 0 for none found
     */
    public int getUpgradeCountByID(String id) {
        // Cycle Inventory
        for (int x = 0; x < upgradeInventory.getSlots(); x++) {
            // Grab item in slot
            ItemStack slottedItem = upgradeInventory.getStackInSlot(x);
            // If we have an item here, and for type cast security it is valid
            if (slottedItem.hasCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null)) {
                // Cast to our data object
                IUpgradeItem slottedUpgrade = slottedItem
                        .getCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null);
                // If we find what we need, return the stack size
                if (slottedUpgrade.getID().equalsIgnoreCase(id))
                    return slottedItem.getCount();
            }
        }
        return 0;
    }

    /**
     * Used to check if this upgrade exists by category
     * @param id The category
     * @return If the upgrade is present
     */
    public boolean hasUpgradeByID(String id) {
        // Cycle Inventory
        for (int x = 0; x < upgradeInventory.getSlots(); x++) {
            // Grab item in slot
            ItemStack slottedItem = upgradeInventory.getStackInSlot(x);
            // If we have an item here, and for type cast security it is valid
            if (slottedItem.hasCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null)) {
                // Cast to our data object
                IUpgradeItem slottedUpgrade = slottedItem
                        .getCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null);
                // If we find what we need, return the stack size
                if (slottedUpgrade.getID().equalsIgnoreCase(id))
                    return true;
            }
        }
        return false;
    }

    /**
     * Used to get the upgrade count by category
     * @param category The category of the upgrade
     * @return How many are present, 0 for none found
     */
    public int getUpgradeCountByCategory(IUpgradeItem.ENUM_UPGRADE_CATEGORY category) {
        // Cycle Inventory
        for (int x = 0; x < upgradeInventory.getSlots(); x++) {
            // Grab item in slot
            ItemStack slottedItem = upgradeInventory.getStackInSlot(x);
            // If we have an item here, and for type cast security it is valid
            if (slottedItem.hasCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null)) {
                // Cast to our data object
                IUpgradeItem slottedUpgrade = slottedItem
                        .getCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null);
                // If we find what we need, return the stack size
                if (slottedUpgrade.getCategory() == category)
                    return slottedItem.getCount();
            }
        }
        return 0;
    }

    /**
     * Used to check if this upgrade exists by category
     * @param category The category
     * @return Shouldn't really be using this, categories are made for numbered upgrades
     */
    public boolean hasUpgradeByCategory(IUpgradeItem.ENUM_UPGRADE_CATEGORY category) {
        // Cycle Inventory
        for (int x = 0; x < upgradeInventory.getSlots(); x++) {
            // Grab item in slot
            ItemStack slottedItem = upgradeInventory.getStackInSlot(x);
            // If we have an item here, and for type cast security it is valid
            if (slottedItem.hasCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null)) {
                // Cast to our data object
                IUpgradeItem slottedUpgrade = slottedItem
                        .getCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null);
                // If we find what we need, return the stack size
                if (slottedUpgrade.getCategory() == category)
                    return true;
            }
        }
        return false;
    }

    /**
     * Get modifier for a category
     * @param category The category
     * @return The modifier to apply
     */
    public int getModifierForCategory(IUpgradeItem.ENUM_UPGRADE_CATEGORY category) {
        // Cycle Inventory
        for (int x = 0; x < upgradeInventory.getSlots(); x++) {
            // Grab item in slot
            ItemStack slottedItem = upgradeInventory.getStackInSlot(x);
            // If we have an item here, and for type cast security it is valid
            if (slottedItem.hasCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null)) {
                // Cast to our data object
                IUpgradeItem slottedUpgrade = slottedItem
                        .getCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null);
                // If we find what we need, return the stack size
                if (slottedUpgrade.getCategory() == category)
                    return slottedUpgrade.getMultiplier(slottedItem);
            }
        }
        return 1;
    }

    /**
     * Used to get the upgrade count by id
     * @param id The id of the upgrade
     * @return How many are present, 0 for none found
     */
    public int getModifierByID(String id) {
        // Cycle Inventory
        for (int x = 0; x < upgradeInventory.getSlots(); x++) {
            // Grab item in slot
            ItemStack slottedItem = upgradeInventory.getStackInSlot(x);
            // If we have an item here, and for type cast security it is valid
            if (slottedItem.hasCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null)) {
                // Cast to our data object
                IUpgradeItem slottedUpgrade = slottedItem
                        .getCapability(CapabilityLoadManager.UPGRADE_ITEM_CAPABILITY, null);
                // If we find what we need, return the stack size
                if (slottedUpgrade.getID().equalsIgnoreCase(id))
                    return slottedUpgrade.getMultiplier(slottedItem);
            }
        }
        return 1;
    }

    /**
     * Used mainly by GUI to check for changes
     * @param inventory The inventory to compare against
     * @return True if something changed
     */
    public boolean hasChangedFromLast(InventoryHandler inventory) {
        for (int x = 0; x < upgradeInventory.getSlots(); x++)
            if (!ItemStack.areItemStacksEqual(upgradeInventory.getStackInSlot(x), inventory.getStackInSlot(x)))
                return true;
        return false;
    }

    /*******************************************************************************************************************
     * Syncable Methods                                                                                                *
     *******************************************************************************************************************/

    /**
     * Used to set the variable for this tile, the Syncable will use this when you send a value to the server
     *
     * @param id The ID of the variable to send
     * @param value The new value to set to (you can use this however you want, eg using the ordinal of EnumFacing)
     */
    @Override
    public void setVariable(int id, double value) {
        switch (id) {
        case REDSTONE_FIELD_ID:
            redstone = (int) value;
            break;
        case IO_FIELD_ID:
            toggleMode(EnumFacing.getFront((int) value));
            break;
        case UPDATE_CLIENT_ID:
            updateClient = true;
            break;
        default:
            super.setVariable(id, value);
        }
    }

    /**
     * Used to get the variable
     *
     * @param id The variable ID
     * @return The value of the variable
     */
    @Override
    public Double getVariable(int id) {
        return super.getVariable(id);
    }

    /**
     * Moves the current redstone mode in either direction
     *
     * @param mod The direction to move. This will move it in that direction as many as provided, usually 1
     *            Positive : To the right
     *            Negative : To the left
     */
    public void moveRedstoneMode(int mod) {
        redstone += mod;
        if (redstone < -1)
            redstone = 1;
        else if (redstone > 1)
            redstone = -1;
    }

    /**
     * Get's the display name for the current mode
     *
     * @return The translated name to display
     */
    public String getRedstoneModeName() {
        switch (redstone) {
        case -1:
            return ClientUtils.translate("neotech.text.low");
        case 0:
            return ClientUtils.translate("neotech.text.disabled");
        case 1:
            return ClientUtils.translate("neotech.text.high");
        default:
            return ClientUtils.translate("neotech.text.error");
        }
    }

    /**
     * Set the mode manually
     *
     * @param newMode The new mode to set to
     */
    public void setRedstoneMode(int newMode) {
        redstone = newMode;
    }

    /**
     * Checks if this block is recieving redstone
     *
     * @return True if has power
     */
    public boolean isPowered() {
        return world.isBlockIndirectlyGettingPowered(pos) > 0 || world.isBlockPowered(pos);
    }
}