package pneumaticCraft.common.tileentity;
import ic2.api.item.IC2Items;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.network.Packet;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.FluidContainerRegistry;
import net.minecraftforge.fluids.FluidContainerRegistry.FluidContainerData;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTankInfo;
import net.minecraftforge.fluids.IFluidContainerItem;
import net.minecraftforge.fluids.IFluidHandler;
import pneumaticCraft.api.IHeatExchangerLogic;
import pneumaticCraft.api.tileentity.IHeatExchanger;
import pneumaticCraft.api.tileentity.IPneumaticMachine;
import pneumaticCraft.common.inventory.SyncedField;
import pneumaticCraft.common.item.ItemMachineUpgrade;
import pneumaticCraft.common.item.Itemss;
import pneumaticCraft.common.network.DescPacketHandler;
import pneumaticCraft.common.network.DescSynced;
import pneumaticCraft.common.network.IDescSynced;
import pneumaticCraft.common.network.NetworkHandler;
import pneumaticCraft.common.network.NetworkUtils;
import pneumaticCraft.common.network.PacketDescription;
import pneumaticCraft.common.thirdparty.computercraft.ILuaMethod;
import pneumaticCraft.common.thirdparty.computercraft.LuaMethod;
import pneumaticCraft.common.util.PneumaticCraftUtils;
import pneumaticCraft.common.util.TileEntityCache;
import pneumaticCraft.lib.ModIds;
import pneumaticCraft.lib.PneumaticValues;
import cpw.mods.fml.common.Optional;
import cpw.mods.fml.common.network.NetworkRegistry.TargetPoint;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
@Optional.InterfaceList({@Optional.Interface(iface = "dan200.computercraft.api.peripheral.IPeripheral", modid = ModIds.COMPUTERCRAFT)})
public class TileEntityBase extends TileEntity implements IGUIButtonSensitive, IDescSynced, IPeripheral{
/**
* True only the first time updateEntity invokes in a session.
*/
protected boolean firstRun = true;
private int[] upgradeSlots;
private boolean descriptionPacketScheduled;
private List<SyncedField> descriptionFields;
protected int poweredRedstone; //The redstone strength currently applied to the block.
private TileEntityCache[] tileCache;
protected List<ILuaMethod> luaMethods = new ArrayList<ILuaMethod>();
public TileEntityBase(){
addLuaMethods();
}
public TileEntityBase(int... upgradeSlots){
this();
this.upgradeSlots = upgradeSlots;
}
@Override
public Packet getDescriptionPacket(){
return DescPacketHandler.getPacket(new PacketDescription(this));
}
protected double getPacketDistance(){
return 64;
}
@Override
public List<SyncedField> getDescriptionFields(){
if(descriptionFields == null) {
descriptionFields = NetworkUtils.getSyncedFields(this, DescSynced.class);
for(SyncedField field : descriptionFields) {
field.update();
}
}
return descriptionFields;
}
/**
* Sends the description packet to every client within PACKET_UPDATE_DISTANCE blocks, and in the same dimension.
*/
public void sendDescriptionPacket(){
//PacketDispatcher.sendPacketToAllAround(xCoord, yCoord, zCoord, TileEntityConstants.PACKET_UPDATE_DISTANCE, worldObj.provider.dimensionId, getDescriptionPacket());
worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
}
/**
* A way to safely mark a block for an update from another thread (like the CC Lua thread).
*/
protected void scheduleDescriptionPacket(){
descriptionPacketScheduled = true;
}
public void sendDescPacket(double maxPacketDistance){
NetworkHandler.sendToAllAround(new PacketDescription(this), new TargetPoint(worldObj.provider.dimensionId, xCoord, yCoord, zCoord, maxPacketDistance));
}
@Override
public void updateEntity(){
if(firstRun && !worldObj.isRemote) {
//firstRun = false;
onFirstServerUpdate();
onNeighborTileUpdate();
onNeighborBlockUpdate();
}
firstRun = false;
if(!worldObj.isRemote) {
if(this instanceof IHeatExchanger) {
((IHeatExchanger)this).getHeatExchangerLogic(ForgeDirection.UNKNOWN).update();
}
if(descriptionFields == null) descriptionPacketScheduled = true;
for(SyncedField field : getDescriptionFields()) {
if(field.update()) {
descriptionPacketScheduled = true;
}
}
if(descriptionPacketScheduled) {
descriptionPacketScheduled = false;
sendDescriptionPacket();
}
}
}
protected void onFirstServerUpdate(){
initializeIfHeatExchanger();
}
protected void updateNeighbours(){
worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord, getBlockType());
}
public void onBlockRotated(){}
protected void rerenderChunk(){
worldObj.markBlockRangeForRenderUpdate(xCoord, yCoord, zCoord, xCoord, yCoord, zCoord);
}
protected boolean shouldRerenderChunkOnDescUpdate(){
return false;
}
/**
* Encoded into the description packet. Also is included in the world save.
* Used as last resort, using @DescSynced is preferred.
* @param tag
*/
@Override
public void writeToPacket(NBTTagCompound tag){}
/**
* Encoded into the description packet. Also is included in the world save.
* Used as last resort, using @DescSynced is preferred.
* @param tag
*/
@Override
public void readFromPacket(NBTTagCompound tag){}
@Override
public void writeToNBT(NBTTagCompound tag){
super.writeToNBT(tag);
writeToPacket(tag);
if(this instanceof IHeatExchanger) {
((IHeatExchanger)this).getHeatExchangerLogic(ForgeDirection.UNKNOWN).writeToNBT(tag);
}
}
@Override
public void readFromNBT(NBTTagCompound tag){
super.readFromNBT(tag);
readFromPacket(tag);
if(this instanceof IHeatExchanger) {
((IHeatExchanger)this).getHeatExchangerLogic(ForgeDirection.UNKNOWN).readFromNBT(tag);
}
}
@Override
public void validate(){
super.validate();
scheduleDescriptionPacket();
}
@Override
public void onDescUpdate(){
if(shouldRerenderChunkOnDescUpdate()) rerenderChunk();
}
/**
* Called when a key is synced in the container.
*/
public void onGuiUpdate(){}
public ForgeDirection getRotation(){
return ForgeDirection.getOrientation(getBlockMetadata());
}
public int getUpgrades(int upgradeDamage){
return getUpgrades(upgradeDamage, this instanceof IPneumaticMachine ? ((IPneumaticMachine)this).getAirHandler().getUpgradeSlots() : getUpgradeSlots());
}
protected int getUpgrades(int upgradeDamage, int... upgradeSlots){
int upgrades = 0;
IInventory inv = null;
if(this instanceof IInventory) inv = (IInventory)this;
if(inv == null && this instanceof TileEntityPneumaticBase && ((TileEntityPneumaticBase)this).parentTile instanceof IInventory) inv = (IInventory)((TileEntityPneumaticBase)this).parentTile;
if(inv != null) {
for(int i : upgradeSlots) {
if(inv.getStackInSlot(i) != null && inv.getStackInSlot(i).getItem() == Itemss.machineUpgrade && inv.getStackInSlot(i).getItemDamage() == upgradeDamage) {
upgrades += inv.getStackInSlot(i).stackSize;
}
}
}
return upgrades;
}
protected float getSpeedMultiplierFromUpgrades(){
return getSpeedMultiplierFromUpgrades(getUpgradeSlots());
}
protected float getSpeedUsageMultiplierFromUpgrades(){
return getSpeedUsageMultiplierFromUpgrades(getUpgradeSlots());
}
public float getSpeedMultiplierFromUpgrades(int[] upgradeSlots){
return (float)Math.pow(PneumaticValues.SPEED_UPGRADE_MULTIPLIER, Math.min(10, getUpgrades(ItemMachineUpgrade.UPGRADE_SPEED_DAMAGE, upgradeSlots)));
}
protected float getSpeedUsageMultiplierFromUpgrades(int[] upgradeSlots){
return (float)Math.pow(PneumaticValues.SPEED_UPGRADE_USAGE_MULTIPLIER, Math.min(10, getUpgrades(ItemMachineUpgrade.UPGRADE_SPEED_DAMAGE, upgradeSlots)));
}
@Optional.Method(modid = ModIds.INDUSTRIALCRAFT)
protected int getIC2Upgrades(String ic2ItemKey, int[] upgradeSlots){
ItemStack itemStack = IC2Items.getItem(ic2ItemKey);
if(itemStack == null) return 0;
int upgrades = 0;
if(this instanceof IInventory) {// this always should be true.
IInventory inv = (IInventory)this;
for(int i : upgradeSlots) {
if(inv.getStackInSlot(i) != null && inv.getStackInSlot(i).getItem() == itemStack.getItem()) {
upgrades += inv.getStackInSlot(i).stackSize;
}
}
}
return upgrades;
}
@Override
public void handleGUIButtonPress(int guiID, EntityPlayer player){}
public boolean isGuiUseableByPlayer(EntityPlayer par1EntityPlayer){
return worldObj.getTileEntity(xCoord, yCoord, zCoord) != this ? false : par1EntityPlayer.getDistanceSq(xCoord + 0.5D, yCoord + 0.5D, zCoord + 0.5D) <= 64.0D;
}
public void setUpgradeSlots(int... upgradeSlots){
this.upgradeSlots = upgradeSlots;
}
public int[] getUpgradeSlots(){
return upgradeSlots;
}
public static void writeInventoryToNBT(NBTTagCompound tag, ItemStack[] stacks){
writeInventoryToNBT(tag, stacks, "Items");
}
public static void writeInventoryToNBT(NBTTagCompound tag, IInventory inventory, String tagName){
ItemStack[] stacks = new ItemStack[inventory.getSizeInventory()];
for(int i = 0; i < stacks.length; i++) {
stacks[i] = inventory.getStackInSlot(i);
}
writeInventoryToNBT(tag, stacks, tagName);
}
public static void writeInventoryToNBT(NBTTagCompound tag, ItemStack[] stacks, String tagName){
NBTTagList tagList = new NBTTagList();
for(int i = 0; i < stacks.length; i++) {
if(stacks[i] != null) {
NBTTagCompound itemTag = new NBTTagCompound();
stacks[i].writeToNBT(itemTag);
itemTag.setByte("Slot", (byte)i);
tagList.appendTag(itemTag);
}
}
tag.setTag(tagName, tagList);
}
public static void readInventoryFromNBT(NBTTagCompound tag, ItemStack[] stacks){
readInventoryFromNBT(tag, stacks, "Items");
}
public static void readInventoryFromNBT(NBTTagCompound tag, IInventory inventory, String tagName){
ItemStack[] stacks = new ItemStack[inventory.getSizeInventory()];
readInventoryFromNBT(tag, stacks, tagName);
for(int i = 0; i < stacks.length; i++) {
inventory.setInventorySlotContents(i, stacks[i]);
}
}
public static void readInventoryFromNBT(NBTTagCompound tag, ItemStack[] stacks, String tagName){
for(int i = 0; i < stacks.length; i++) {
stacks[i] = null;
}
NBTTagList tagList = tag.getTagList(tagName, 10);
for(int i = 0; i < tagList.tagCount(); i++) {
NBTTagCompound itemTag = tagList.getCompoundTagAt(i);
int slot = itemTag.getByte("Slot");
if(slot >= 0 && slot < stacks.length) {
stacks[slot] = ItemStack.loadItemStackFromNBT(itemTag);
}
}
}
public void onNeighborTileUpdate(){
initializeIfHeatExchanger();
for(TileEntityCache cache : getTileCache()) {
cache.update();
}
}
public TileEntityCache[] getTileCache(){
if(tileCache == null) tileCache = TileEntityCache.getDefaultCache(worldObj, xCoord, yCoord, zCoord);
return tileCache;
}
public void onNeighborBlockUpdate(){
poweredRedstone = PneumaticCraftUtils.getRedstoneLevel(worldObj, xCoord, yCoord, zCoord);
initializeIfHeatExchanger();
}
public boolean redstoneAllows(){
if(worldObj.isRemote) onNeighborBlockUpdate();
switch(((IRedstoneControl)this).getRedstoneMode()){
case 0:
return true;
case 1:
return poweredRedstone > 0;
case 2:
return poweredRedstone == 0;
}
return false;
}
protected void initializeIfHeatExchanger(){
if(this instanceof IHeatExchanger) {
initializeHeatExchanger(((IHeatExchanger)this).getHeatExchangerLogic(ForgeDirection.UNKNOWN), getConnectedHeatExchangerSides());
}
}
protected void initializeHeatExchanger(IHeatExchangerLogic heatExchanger, ForgeDirection... connectedSides){
heatExchanger.initializeAsHull(worldObj, xCoord, yCoord, zCoord, connectedSides);
}
/**
* Gets the valid sides for heat exchanging to be allowed. returning an empty array will allow any side.
* @return
*/
protected ForgeDirection[] getConnectedHeatExchangerSides(){
return new ForgeDirection[0];
}
public void autoExportLiquid(){
FluidStack extractedStack = ((IFluidHandler)this).drain(ForgeDirection.UNKNOWN, Integer.MAX_VALUE, false);
if(extractedStack != null) {
for(ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
TileEntity te = getTileCache()[d.ordinal()].getTileEntity();
if(te instanceof IFluidHandler) {
if(((IFluidHandler)te).canFill(d.getOpposite(), extractedStack.getFluid())) {
int filledAmount = ((IFluidHandler)te).fill(d.getOpposite(), extractedStack, true);
((IFluidHandler)this).drain(ForgeDirection.UNKNOWN, filledAmount, true);
extractedStack.amount -= filledAmount;
if(extractedStack.amount <= 0) break;
}
}
}
}
}
@Override
public Type getSyncType(){
return Type.TILE_ENTITY;
}
@Override
public int getX(){
return xCoord;
}
@Override
public int getY(){
return yCoord;
}
@Override
public int getZ(){
return zCoord;
}
protected void processFluidItem(int inputSlot, int outputSlot){
IInventory inv = (IInventory)this;
IFluidHandler fluidHandler = (IFluidHandler)this;
FluidTankInfo tankInfo = fluidHandler.getTankInfo(ForgeDirection.UNKNOWN)[0];
if(inv.getStackInSlot(inputSlot) != null) {
ItemStack fluidContainer = inv.getStackInSlot(inputSlot);
if(tankInfo.fluid == null || tankInfo.fluid.isFluidEqual(fluidContainer)) {
FluidStack fluid = FluidContainerRegistry.getFluidForFilledItem(fluidContainer);
int amount = FluidContainerRegistry.BUCKET_VOLUME;
if(fluid == null) {
if(fluidContainer.getItem() instanceof IFluidContainerItem) {
IFluidContainerItem containerItem = (IFluidContainerItem)fluidContainer.getItem();
fluid = containerItem.getFluid(fluidContainer);
if(fluid != null && fluidHandler.canFill(ForgeDirection.UNKNOWN, fluid.getFluid())) {
amount = fluid != null ? fluid.amount : 0;
int availableSpace = tankInfo.capacity - (tankInfo.fluid != null ? tankInfo.fluid.amount : 0);
if(availableSpace >= amount) {
ItemStack singleFuelItem = fluidContainer.copy();
singleFuelItem.stackSize = 1;
FluidStack drainedStack = containerItem.drain(singleFuelItem, availableSpace, true);
if(fluidContainer.stackSize == 1 || inv.getStackInSlot(outputSlot) == null || canStack(singleFuelItem, inv.getStackInSlot(outputSlot))) {
fluidHandler.fill(ForgeDirection.UNKNOWN, drainedStack, true);
if(fluidContainer.stackSize == 1) {
inv.setInventorySlotContents(inputSlot, singleFuelItem);
} else {
inv.getStackInSlot(inputSlot).stackSize--;
if(inv.getStackInSlot(outputSlot) == null) {
inv.setInventorySlotContents(outputSlot, singleFuelItem);
} else {
inv.getStackInSlot(outputSlot).stackSize++;
}
}
}
}
}
}
} else if(fluidHandler.canFill(ForgeDirection.UNKNOWN, fluid.getFluid())) {
if(tankInfo.capacity - (tankInfo.fluid != null ? tankInfo.fluid.amount : 0) >= amount) {
ItemStack returnedItem = null;
FluidContainerData[] allFluidData = FluidContainerRegistry.getRegisteredFluidContainerData();
for(FluidContainerData fluidData : allFluidData) {
if(fluidData.filledContainer.isItemEqual(fluidContainer)) {
returnedItem = fluidData.emptyContainer;
break;
}
}
if(returnedItem == null || inv.getStackInSlot(outputSlot) == null || canStack(returnedItem, inv.getStackInSlot(outputSlot))) {
if(returnedItem != null) {
if(inv.getStackInSlot(outputSlot) == null) {
inv.setInventorySlotContents(outputSlot, returnedItem.copy());
} else {
inv.getStackInSlot(outputSlot).stackSize += returnedItem.stackSize;
}
}
fluidHandler.fill(ForgeDirection.UNKNOWN, new FluidStack(fluid.getFluid(), amount, fluid.tag), true);
inv.getStackInSlot(inputSlot).stackSize--;
if(inv.getStackInSlot(inputSlot).stackSize <= 0) inv.setInventorySlotContents(inputSlot, null);
}
}
}
}
if(fluidContainer.getItem() instanceof IFluidContainerItem) {
if(((IFluidContainerItem)fluidContainer.getItem()).getFluid(fluidContainer) == null && (inv.getStackInSlot(outputSlot) == null || canStack(fluidContainer, inv.getStackInSlot(outputSlot)))) {
if(inv.getStackInSlot(outputSlot) == null) {
inv.setInventorySlotContents(outputSlot, fluidContainer);
} else {
inv.getStackInSlot(outputSlot).stackSize += fluidContainer.stackSize;
}
inv.setInventorySlotContents(inputSlot, null);
}
}
}
}
private boolean canStack(ItemStack stack1, ItemStack stack2){
return stack1.isItemEqual(stack2) && ItemStack.areItemStackTagsEqual(stack1, stack2) && stack1.stackSize + stack2.stackSize <= stack1.getMaxStackSize();
}
/*
* COMPUTERCRAFT API
*/
protected void addLuaMethods(){
if(this instanceof IHeatExchanger) {
final IHeatExchanger exchanger = (IHeatExchanger)this;
luaMethods.add(new LuaMethod("getTemperature"){
@Override
public Object[] call(Object[] args) throws Exception{
if(args.length == 0) {
return new Object[]{exchanger.getHeatExchangerLogic(ForgeDirection.UNKNOWN).getTemperature()};
} else if(args.length == 1) {
IHeatExchangerLogic logic = exchanger.getHeatExchangerLogic(getDirForString((String)args[0]));
return new Object[]{logic != null ? logic.getTemperature() : 0};
} else {
throw new IllegalArgumentException("getTemperature method requires 0 or 1 argument (direction: up, down, east, west, north, south!");
}
}
});
}
}
@Override
public String getType(){
return getBlockType().getUnlocalizedName().substring(5);
}
@Override
public String[] getMethodNames(){
String[] methodNames = new String[luaMethods.size()];
for(int i = 0; i < methodNames.length; i++) {
methodNames[i] = luaMethods.get(i).getMethodName();
}
return methodNames;
}
public List<ILuaMethod> getLuaMethods(){
return luaMethods;
}
@Override
@Optional.Method(modid = ModIds.COMPUTERCRAFT)
public Object[] callMethod(IComputerAccess computer, ILuaContext context, int method, Object[] arguments) throws LuaException, InterruptedException{
try {
return luaMethods.get(method).call(arguments);
} catch(Exception e) {
throw new LuaException(e.getMessage());
}
}
@Override
@Optional.Method(modid = ModIds.COMPUTERCRAFT)
public void attach(IComputerAccess computer){}
@Override
@Optional.Method(modid = ModIds.COMPUTERCRAFT)
public void detach(IComputerAccess computer){}
@Override
@Optional.Method(modid = ModIds.COMPUTERCRAFT)
public boolean equals(IPeripheral other){
if(other == null) {
return false;
}
if(this == other) {
return true;
}
if(other instanceof TileEntity) {
TileEntity tother = (TileEntity)other;
return tother.getWorldObj().equals(worldObj) && tother.xCoord == xCoord && tother.yCoord == yCoord && tother.zCoord == zCoord;
}
return false;
}
}