package erogenousbeef.bigreactors.common.multiblock.tileentity;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.InventoryPlayer;
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.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import cofh.lib.util.helpers.BlockHelper;
import cofh.lib.util.helpers.ItemHelper;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import erogenousbeef.bigreactors.api.data.SourceProductMapping;
import erogenousbeef.bigreactors.api.registry.Reactants;
import erogenousbeef.bigreactors.client.gui.GuiReactorAccessPort;
import erogenousbeef.bigreactors.common.BRLog;
import erogenousbeef.bigreactors.common.BigReactors;
import erogenousbeef.bigreactors.common.data.StandardReactants;
import erogenousbeef.bigreactors.common.multiblock.interfaces.INeighborUpdatableEntity;
import erogenousbeef.bigreactors.gui.container.ContainerReactorAccessPort;
import erogenousbeef.bigreactors.utils.AdjacentInventoryHelper;
import erogenousbeef.core.multiblock.MultiblockControllerBase;
public class TileEntityReactorAccessPort extends TileEntityReactorPart implements IInventory, ISidedInventory, INeighborUpdatableEntity {
protected ItemStack[] _inventories;
protected boolean isInlet;
protected AdjacentInventoryHelper adjacencyHelper;
public static final int SLOT_INLET = 0;
public static final int SLOT_OUTLET = 1;
public static final int NUM_SLOTS = 2;
private static final int[] kInletExposed = {SLOT_INLET};
private static final int[] kOutletExposed = {SLOT_OUTLET};
public TileEntityReactorAccessPort() {
super();
_inventories = new ItemStack[getSizeInventory()];
isInlet = true;
}
// Return the name of the reactant to which the item in the input slot
public String getInputReactantType() {
ItemStack inputItem = getStackInSlot(SLOT_INLET);
if(inputItem == null) { return null; }
SourceProductMapping mapping = Reactants.getSolidToReactant(inputItem);
return mapping != null ? mapping.getProduct() : null;
}
// Returns the potential amount of reactant which can be produced from this port.
public int getInputReactantAmount() {
ItemStack inputItem = getStackInSlot(SLOT_INLET);
if(inputItem == null) { return 0; }
SourceProductMapping mapping = Reactants.getSolidToReactant(inputItem);
return mapping != null ? mapping.getProductAmount(inputItem.stackSize) : 0;
}
/**
* Consume items from the input slot.
* Returns the amount of reactant produced.
* @param reactantDesired The amount of reactant desired, in reactant units (mB)
* @return The amount of reactant actually produced, in reactant units (mB)
*/
public int consumeReactantItem(int reactantDesired) {
ItemStack inputItem = getStackInSlot(SLOT_INLET);
if(inputItem == null) { return 0; }
SourceProductMapping mapping = Reactants.getSolidToReactant(inputItem);
if(mapping == null) { return 0; }
int sourceItemsToConsume = Math.min(inputItem.stackSize, mapping.getSourceAmount(reactantDesired));
if(sourceItemsToConsume <= 0) { return 0; }
decrStackSize(SLOT_INLET, sourceItemsToConsume);
return mapping.getProductAmount(sourceItemsToConsume);
}
/**
* Try to emit a given amount of reactant as a solid item.
* Will either match the item type already present, or will select
* whatever type allows the most reactant to be ejected right now.
* @param reactantType Type of reactant to emit.
* @param amount
* @return
*/
public int emitReactant(String reactantType, int amount) {
if(reactantType == null || amount <= 0) { return 0; }
ItemStack outputItem = getStackInSlot(SLOT_OUTLET);
if(outputItem != null && outputItem.stackSize >= getInventoryStackLimit()) {
// Already full?
return 0;
}
// If we have an output item, try to produce more of it, given its mapping
if(outputItem != null) {
// Find matching mapping
SourceProductMapping mapping = Reactants.getSolidToReactant(outputItem);
if(mapping == null || !reactantType.equals(mapping.getProduct())) {
// Items are incompatible!
return 0;
}
// We're using the original source item >> reactant mapping here
// This means that source == item, and product == reactant
int amtToProduce = mapping.getSourceAmount(amount);
amtToProduce = Math.min(amtToProduce, getInventoryStackLimit() - outputItem.stackSize);
if(amtToProduce <= 0) { return 0; }
// Do we actually produce any reactant at this reduced amount?
int reactantToConsume = mapping.getProductAmount(amtToProduce);
if(reactantToConsume <= 0) { return 0; }
outputItem.stackSize += amtToProduce;
onItemsReceived();
return reactantToConsume;
}
// Ok, we have no items. We need to figure out candidate mappings.
// Below here, we're using the reactant >> source mappings.
// This means that source == reactant, and product == item.
SourceProductMapping bestMapping = null;
List<SourceProductMapping> mappings = Reactants.getReactantToSolids(reactantType);
if(mappings != null) {
int bestReactantAmount = 0;
for(SourceProductMapping mapping: mappings) {
// How much product can we produce?
int potentialProducts = mapping.getProductAmount(amount);
// And how much reactant will that consume?
int potentialReactant = mapping.getSourceAmount(potentialProducts);
if(bestMapping == null || bestReactantAmount < potentialReactant) {
bestMapping = mapping;
bestReactantAmount = potentialReactant;
}
}
}
if(bestMapping == null) {
BRLog.warning("There are no mapped item types for reactant %s. Using cyanite instead.", reactantType);
bestMapping = StandardReactants.cyaniteMapping;
}
int itemsToProduce = Math.min(bestMapping.getProductAmount(amount), getInventoryStackLimit());
if(itemsToProduce <= 0) {
// Can't produce even one ingot? Ok then.
return 0;
}
// And clamp again in case we could produce more than 64 items
int reactantConsumed = bestMapping.getSourceAmount(itemsToProduce);
itemsToProduce = bestMapping.getProductAmount(reactantConsumed);
ItemStack newItem = ItemHelper.getOre(bestMapping.getProduct());
if(newItem == null) {
BRLog.warning("Could not find item for oredict entry %s, using cyanite instead.", bestMapping.getSource());
newItem = BigReactors.ingotGeneric.getItemStackForType("ingotCyanite");
}
else {
newItem = newItem.copy(); // Don't stomp the oredict
}
newItem.stackSize = itemsToProduce;
setInventorySlotContents(SLOT_OUTLET, newItem);
onItemsReceived();
return reactantConsumed;
}
// Multiblock overrides
@Override
public void onMachineAssembled(MultiblockControllerBase controller) {
super.onMachineAssembled(controller);
adjacencyHelper = new AdjacentInventoryHelper(this.getOutwardsDir());
checkForAdjacentInventories();
}
@Override
public void onMachineBroken() {
super.onMachineBroken();
adjacencyHelper = null;
}
// TileEntity overrides
@Override
public void readFromNBT(NBTTagCompound tag) {
super.readFromNBT(tag);
_inventories = new ItemStack[getSizeInventory()];
if(tag.hasKey("Items")) {
NBTTagList tagList = tag.getTagList("Items", 10);
for(int i = 0; i < tagList.tagCount(); i++) {
NBTTagCompound itemTag = (NBTTagCompound)tagList.getCompoundTagAt(i);
int slot = itemTag.getByte("Slot") & 0xff;
if(slot >= 0 && slot <= _inventories.length) {
ItemStack itemStack = new ItemStack((Block)null,0,0);
itemStack.readFromNBT(itemTag);
_inventories[slot] = itemStack;
}
}
}
if(tag.hasKey("isInlet")) {
this.isInlet = tag.getBoolean("isInlet");
}
}
@Override
public void writeToNBT(NBTTagCompound tag) {
super.writeToNBT(tag);
NBTTagList tagList = new NBTTagList();
for(int i = 0; i < _inventories.length; i++) {
if((_inventories[i]) != null) {
NBTTagCompound itemTag = new NBTTagCompound();
itemTag.setByte("Slot", (byte)i);
_inventories[i].writeToNBT(itemTag);
tagList.appendTag(itemTag);
}
}
if(tagList.tagCount() > 0) {
tag.setTag("Items", tagList);
}
tag.setBoolean("isInlet", isInlet);
}
// MultiblockTileEntityBase
@Override
protected void encodeDescriptionPacket(NBTTagCompound packetData) {
super.encodeDescriptionPacket(packetData);
packetData.setBoolean("inlet", isInlet);
}
@Override
protected void decodeDescriptionPacket(NBTTagCompound packetData) {
super.decodeDescriptionPacket(packetData);
if(packetData.hasKey("inlet")) {
setInlet(packetData.getBoolean("inlet"));
}
}
// IInventory
@Override
public int getSizeInventory() {
return NUM_SLOTS;
}
@Override
public ItemStack getStackInSlot(int slot) {
return _inventories[slot];
}
@Override
public ItemStack decrStackSize(int slot, int amount) {
if(_inventories[slot] != null)
{
if(_inventories[slot].stackSize <= amount)
{
ItemStack itemstack = _inventories[slot];
_inventories[slot] = null;
markDirty();
return itemstack;
}
ItemStack newStack = _inventories[slot].splitStack(amount);
if(_inventories[slot].stackSize == 0)
{
_inventories[slot] = null;
}
markDirty();
return newStack;
}
else
{
return null;
}
}
@Override
public ItemStack getStackInSlotOnClosing(int slot) {
return null;
}
@Override
public void setInventorySlotContents(int slot, ItemStack itemstack) {
_inventories[slot] = itemstack;
if(itemstack != null && itemstack.stackSize > getInventoryStackLimit())
{
itemstack.stackSize = getInventoryStackLimit();
}
markDirty();
}
@Override
public String getInventoryName() {
return "Access Port";
}
@Override
public boolean hasCustomInventoryName() {
return false;
}
@Override
public int getInventoryStackLimit() {
return 64;
}
@Override
public boolean isUseableByPlayer(EntityPlayer entityplayer) {
if(worldObj.getTileEntity(xCoord, yCoord, zCoord) != this)
{
return false;
}
return entityplayer.getDistanceSq((double)xCoord + 0.5D, (double)yCoord + 0.5D, (double)zCoord + 0.5D) <= 64D;
}
@Override
public void openInventory() {
}
@Override
public void closeInventory() {
}
@Override
public boolean isItemValidForSlot(int slot, ItemStack itemstack) {
if(itemstack == null) { return true; }
if(slot == SLOT_INLET) {
return Reactants.isFuel(itemstack);
}
else if(slot == SLOT_OUTLET) {
return Reactants.isWaste(itemstack);
}
else {
return false;
}
}
// ISidedInventory
@Override
public int[] getAccessibleSlotsFromSide(int side) {
if(isInlet()) {
return kInletExposed;
}
else {
return kOutletExposed;
}
}
@Override
public boolean canInsertItem(int slot, ItemStack itemstack, int side) {
return isItemValidForSlot(slot, itemstack);
}
@Override
public boolean canExtractItem(int slot, ItemStack itemstack, int side) {
return isItemValidForSlot(slot, itemstack);
}
// IMultiblockGuiHandler
@Override
public Object getContainer(InventoryPlayer inventoryPlayer) {
return new ContainerReactorAccessPort(this, inventoryPlayer);
}
@SideOnly(Side.CLIENT)
@Override
public Object getGuiElement(InventoryPlayer inventoryPlayer) {
return new GuiReactorAccessPort(new ContainerReactorAccessPort(this, inventoryPlayer), this);
}
/**
* Called when stuff has been placed in the access port
*/
public void onItemsReceived() {
distributeItems();
markChunkDirty();
}
public boolean isInlet() { return this.isInlet; }
public void setInlet(boolean shouldBeInlet) {
if(isInlet == shouldBeInlet) { return; }
isInlet = shouldBeInlet;
worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
if(!worldObj.isRemote) {
distributeItems();
markChunkDirty();
}
notifyNeighborsOfTileChange();
}
protected void distributeItems() {
if(worldObj.isRemote) { return; }
if(adjacencyHelper == null) { return; }
if(this.isInlet()) { return; }
_inventories[SLOT_OUTLET] = adjacencyHelper.distribute(_inventories[SLOT_OUTLET]);
markChunkDirty();
}
protected void checkForAdjacentInventories() {
ForgeDirection outDir = getOutwardsDir();
if(adjacencyHelper == null && outDir != ForgeDirection.UNKNOWN) {
adjacencyHelper = new AdjacentInventoryHelper(outDir);
}
if(adjacencyHelper != null && outDir != ForgeDirection.UNKNOWN) {
TileEntity te = worldObj.getTileEntity(xCoord + outDir.offsetX, yCoord + outDir.offsetY, zCoord + outDir.offsetZ);
if(adjacencyHelper.set(te)) {
distributeItems();
}
}
}
protected void markChunkDirty() {
worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this);
}
// INeighborUpdateableEntity
@Override
public void onNeighborBlockChange(World world, int x, int y, int z,
Block neighborBlock) {
checkForAdjacentInventories();
}
@Override
public void onNeighborTileChange(IBlockAccess world, int x, int y, int z,
int neighborX, int neighborY, int neighborZ) {
int side = BlockHelper.determineAdjacentSide(this, neighborX, neighborY, neighborZ);
if(side == getOutwardsDir().ordinal()) {
checkForAdjacentInventories();
}
}
}