package mods.ocminecart.common.component;
import li.cil.oc.api.API;
import li.cil.oc.api.machine.Arguments;
import li.cil.oc.api.machine.Callback;
import li.cil.oc.api.machine.Context;
import li.cil.oc.api.network.*;
import mods.ocminecart.Settings;
import mods.ocminecart.common.minecart.ComputerCart;
import mods.ocminecart.common.util.InventoryUtil;
import mods.ocminecart.common.util.ItemUtil;
import mods.ocminecart.common.util.TankUtil;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.IFluidHandler;
import net.minecraftforge.fluids.IFluidTank;
import java.util.ArrayList;
public class ComputerCartController implements ManagedEnvironment{
private Node node;
private ComputerCart cart;
public ComputerCartController(ComputerCart cart){
this.cart = cart;
node = API.network.newNode(this, Visibility.Neighbors).withComponent("computercart").create();
}
@Override
public Node node() {
return node;
}
@Override
public void onConnect(Node node) {
}
@Override
public void onDisconnect(Node node) {
}
@Override
public void onMessage(Message message) {}
@Override
public void load(NBTTagCompound nbt) {
if(nbt.hasKey("node")) ((Component)this.node).load(nbt.getCompoundTag("node"));
}
@Override
public void save(NBTTagCompound nbt) {
NBTTagCompound node = new NBTTagCompound();
((Component)this.node).save(node);
nbt.setTag("node", node);
}
@Override
public boolean canUpdate() {
return false;
}
@Override
public void update() {}
/*--------Component-Functions-Cart--------*/
@Callback(doc="function(set:boolean):boolen, string -- Enable/Disable the brake. String for errors")
public Object[] setBrake(Context context, Arguments arguments){
boolean state = arguments.checkBoolean(0);
if(this.cart.getSpeed() > this.cart.getMaxCartSpeedOnRail() && state){
return new Object[]{this.cart.getBrakeState(), "too fast"};
}
this.cart.setBrakeState(state);
return new Object[]{state, null};
}
@Callback(direct = true,doc="function():boolean -- Get the status of the brake.")
public Object[] getBrake(Context context, Arguments arguments){
return new Object[]{this.cart.getBrakeState()};
}
@Callback(direct = true,doc="function():number -- Get engine speed")
public Object[] getEngineSpeed(Context context, Arguments arguments){
return new Object[]{this.cart.getEngineState()};
}
@Callback(doc="function():number -- Get current speed of the cart. -1 if there is no rail")
public Object[] getCartSpeed(Context context, Arguments arguments){
double speed = -1;
if(this.cart.onRail()){
speed = this.cart.getSpeed();
}
return new Object[]{speed};
}
@Callback(doc="function(speed:number):number -- Set the engine speed.")
public Object[] setEngineSpeed(Context context, Arguments arguments){
double speed = Math.max(Math.min(arguments.checkDouble(0), this.cart.getMaxCartSpeedOnRail()), 0);
this.cart.setEngineState(speed);
return new Object[]{speed};
}
@Callback(doc="function():number -- Get the maximal cart speed")
public Object[] getMaxSpeed(Context context, Arguments arguments){
return new Object[]{this.cart.getMaxCartSpeedOnRail()};
}
@Callback(doc="function(color:number):number -- Set light color")
public Object[] setLightColor(Context context, Arguments arguments){
int color = arguments.checkInteger(0);
this.cart.setLightColor(color);
return new Object[]{color};
}
@Callback(direct = true,doc="function():number -- Get light color")
public Object[] getLightColor(Context context, Arguments arguments){
return new Object[]{this.cart.getLightColor()};
}
@Callback(doc="function() -- Rotate the cart")
public Object[] rotate(Context context, Arguments arguments){
float yaw = this.cart.rotationYaw + 180F;
if(yaw>180) yaw-=360F;
else if(yaw<-180) yaw+=360F;
this.cart.rotationYaw = yaw;
//OCMinecart.logger.info("Rotate: "+this.cart.rotationYaw+" + "+cart.facing().toString());
return new Object[]{};
}
@Callback(direct = true,doc="function():boolean -- Check if the cart is on a rail")
public Object[] onRail(Context context, Arguments arguments){
return new Object[]{this.cart.onRail()};
}
@Callback(direct = true,doc="function():boolean -- Check if the cart is connected to a network rail")
public Object[] hasNetworkRail(Context context, Arguments arguments){
return new Object[]{this.cart.hasNetRail()};
}
@Callback(direct = true,doc="function():boolean -- Check if the cart is locked on a track")
public Object[] isLocked(Context context, Arguments arguments){
return new Object[]{this.cart.isLocked()};
}
/*--------Component-Functions-Inventory--------*/
@Callback(doc = "function():number -- The size of this device's internal inventory.")
public Object[] inventorySize(Context context, Arguments arguments){
return new Object[]{this.cart.getInventorySpace()};
}
@Callback(doc = "function([slot:number]):number -- Get the currently selected slot; set the selected slot if specified.")
public Object[] select(Context context, Arguments args){
int slot = args.optInteger(0, 0);
if(slot > 0 && slot <= this.cart.maininv.getMaxSizeInventory()){
this.cart.setSelectedSlot(slot-1);
}
else if(args.count() > 0){
throw new IllegalArgumentException("invalid slot");
}
return new Object[]{this.cart.selectedSlot()+1};
}
@Callback(direct = true, doc = "function([slot:number]):number -- Get the number of items in the specified slot, otherwise in the selected slot.")
public Object[] count(Context context, Arguments args){
int slot = args.optInteger(0, -1);
int num = 0;
slot = (args.count() > 0) ? slot-1 : this.cart.selectedSlot();
if(slot >= 0 && slot < this.cart.getInventorySpace()){
if(this.cart.mainInventory().getStackInSlot(slot)!=null){
num = this.cart.mainInventory().getStackInSlot(slot).stackSize;
}
}
else{
if(args.count() < 1)
return new Object[]{ 0 , "no slot selected"};
throw new IllegalArgumentException("invalid slot");
}
return new Object[]{num};
}
@Callback(direct = true, doc = "function([slot:number]):number -- Get the remaining space in the specified slot, otherwise in the selected slot.")
public Object[] space(Context context, Arguments args){
int slot = args.optInteger(0, -1);
int num = 0;
slot = (args.count() > 0) ? slot-1 : this.cart.selectedSlot();
if(slot > 0 && slot <= this.cart.getInventorySpace()){
ItemStack stack = this.cart.mainInventory().getStackInSlot(slot-1);
if(stack!=null){
int maxStack = Math.min(this.cart.mainInventory().getInventoryStackLimit(), stack.getMaxStackSize());
num = maxStack-stack.stackSize;
}
else{
num = this.cart.mainInventory().getInventoryStackLimit();
}
}
else{
if(args.count() < 1)
return new Object[]{ 0 , "no slot selected"};
throw new IllegalArgumentException("invalid slot");
}
return new Object[]{num};
}
@Callback(doc = "function(otherSlot:number):boolean -- Compare the contents of the selected slot to the contents of the specified slot.")
public Object[] compareTo(Context context, Arguments args){
int slotA = args.checkInteger(0) - 1;
int slotB = this.cart.selectedSlot();
boolean result;
if(slotB>=0 && slotB<this.cart.mainInventory().getSizeInventory())
return new Object[]{ false , "no slot selected"};
if(slotA>=0 && slotA<this.cart.mainInventory().getSizeInventory()){
ItemStack stackA = this.cart.mainInventory().getStackInSlot(slotA);
ItemStack stackB = this.cart.mainInventory().getStackInSlot(slotB);
result = (stackA==null && stackB==null) ||
(stackA!=null && stackB!=null && stackA.isItemEqual(stackB));
return new Object[]{result};
}
throw new IllegalArgumentException("invalid slot");
}
@Callback(doc = "function(toSlot:number[, amount:number]):boolean -- Move up to the specified amount of items from the selected slot into the specified slot.")
public Object[] transferTo(Context context, Arguments args){
int tslot = args.checkInteger(0) - 1;
int number = args.optInteger(1, this.cart.mainInventory().getInventoryStackLimit());
if(!(tslot>=0 && tslot<this.cart.mainInventory().getSizeInventory()))
throw new IllegalArgumentException("invalid slot");
if(!(this.cart.selectedSlot()>=0 && this.cart.selectedSlot()<this.cart.mainInventory().getSizeInventory()))
return new Object[]{ false , "no slot selected"};
ItemStack stackT = this.cart.mainInventory().getStackInSlot(tslot);
ItemStack stackS = this.cart.mainInventory().getStackInSlot(this.cart.selectedSlot());
if(!(stackT==null || (stackS!=null && stackT!=null && stackT.isItemEqual(stackS))) || stackS == null)
return new Object[]{false};
int items = 0;
int maxStack = Math.min(this.cart.mainInventory().getInventoryStackLimit(), stackS.getMaxStackSize());
items = maxStack - ((stackT!=null) ? stackT.stackSize : 0);
items = Math.min(items, number);
if(items<=0) return new Object[]{false};
ItemStack dif = this.cart.mainInventory().decrStackSize(this.cart.selectedSlot(), items);
if(stackT!=null){
stackT.stackSize+=dif.stackSize;
}
else{
this.cart.mainInventory().setInventorySlotContents(tslot, dif);
}
return new Object[]{true};
}
/*--------Component-Functions-Tank--------*/
@Callback(doc = "function():number -- The number of tanks installed in the device.")
public Object[] tankCount(Context context, Arguments args){
return new Object[]{ this.cart.tankcount() };
}
@Callback(doc = "function([index:number]):number -- Select a tank and/or get the number of the currently selected tank.")
public Object[] selectTank(Context context, Arguments args){
int index = args.optInteger(0, 0);
if(index > 0 && index <=this.cart.tankcount())
this.cart.setSelectedTank(index);
else if(args.count() > 0)
throw new IllegalArgumentException("invalid tank index");
return new Object[]{this.cart.selectedTank()};
}
@Callback(direct = true, doc = "function([index:number]):number -- Get the fluid amount in the specified or selected tank.")
public Object[] tankLevel(Context context, Arguments args){
int index = args.optInteger(0, 0);
index = (args.count()>0) ? index : this.cart.selectedTank();
if(!(index>0 && index<=this.cart.tankcount())){
if(args.count()<1)
return new Object[]{ false ,"no tank selected" };
throw new IllegalArgumentException("invalid tank index");
}
return new Object[]{ this.cart.getTank(index).getFluidAmount() };
}
@Callback(direct = true, doc = "function([index:number]):number -- Get the remaining fluid capacity in the specified or selected tank.")
public Object[] tankSpace(Context context, Arguments args){
int index = args.optInteger(0, 0);
index = (args.count()>0) ? index : this.cart.selectedTank();
if(!(index>0 && index<=this.cart.tankcount())){
if(args.count()<1)
return new Object[]{ false ,"no tank selected" };
throw new IllegalArgumentException("invalid tank index");
}
IFluidTank tank = this.cart.getTank(index);
return new Object[]{ tank.getCapacity() - tank.getFluidAmount() };
}
@Callback(doc = "function(index:number):boolean -- Compares the fluids in the selected and the specified tank. Returns true if equal.")
public Object[] compareFluidTo(Context context, Arguments args){
int tankA = args.checkInteger(0);
int tankB = this.cart.selectedTank();
if(!(tankA>0 && tankA<=this.cart.tankcount()))
throw new IllegalArgumentException("invalid tank index");
if(!(tankB>0 && tankB<=this.cart.tankcount()))
return new Object[]{ false ,"no tank selected" };
FluidStack stackA = this.cart.getTank(tankA).getFluid();
FluidStack stackB = this.cart.getTank(tankB).getFluid();
boolean res = (stackA==null && stackB==null);
if(!res && stackA!=null && stackB!=null)
res = stackA.isFluidEqual(stackB);
return new Object[]{ res };
}
@Callback(doc = "function(index:number[, count:number=1000]):boolean -- Move the specified amount of fluid from the selected tank into the specified tank.")
public Object[] transferFluidTo(Context context, Arguments args){
int tankA = args.checkInteger(0);
int tankB = this.cart.selectedTank();
int count = args.optInteger(1, 1000);
if(!(tankA>0 && tankA<=this.cart.tankcount()))
throw new IllegalArgumentException("invalid tank index");
if(!(tankB>0 && tankB<=this.cart.tankcount()))
return new Object[]{ false ,"no tank selected" };
IFluidTank tankT = this.cart.getTank(tankA);
IFluidTank tankS = this.cart.getTank(tankB);
if(tankS.getFluid()==null || (tankT!=null && tankS.getFluid().isFluidEqual(tankT.getFluid())))
return new Object[]{ false };
FluidStack sim = tankS.drain(count, false); //Simulate the transfer to get the max. moveable amount.
int move = tankT.fill(sim, false);
if(move<=0) return new Object[]{ false };
FluidStack mv = tankS.drain(move, true);
int over = tankT.fill(mv, true);
over-=mv.amount;
if(over>0){ //Just in case we drained too much.
FluidStack ret = mv.copy();
ret.amount = over;
tankS.fill(ret, true);
}
return new Object[]{ true };
}
//--------World-Inventory----------//
@Callback(doc = "function(side:number[, count:number=64]):boolean -- Drops items from the selected slot towards the specified side.")
public Object[] drop(Context context, Arguments args){
int side = args.checkInteger(0);
int amount = args.optInteger(1,64);
if(side<0 || side > 5) throw new IllegalArgumentException("invalid side");
int sslot = this.cart.selectedSlot();
if(!(sslot>=0 && sslot<this.cart.mainInventory().getSizeInventory()))
return new Object[]{ false , "no slot selected"};
if(amount<1) return new Object[]{ false };
ForgeDirection dir = this.cart.toGlobal(ForgeDirection.getOrientation(side));
int x = (int)Math.floor(this.cart.xPosition())+dir.offsetX;
int y = (int)Math.floor(this.cart.yPosition())+dir.offsetY;
int z = (int)Math.floor(this.cart.zPosition())+dir.offsetZ;
ItemStack dstack = this.cart.mainInventory().getStackInSlot(sslot);
if(dstack == null) return new Object[]{ false };
if(!(this.cart.world().getTileEntity(x, y, z) instanceof IInventory)){
ArrayList<ItemStack> drop = new ArrayList<ItemStack>();
int mov = Math.min(dstack.stackSize, amount);
ItemStack dif = dstack.splitStack(mov);
if(dstack.stackSize < 1)
this.cart.mainInventory().setInventorySlotContents(sslot, null);
drop.add(dif);
ItemUtil.dropItemList(drop, this.cart.world(), x+0.5D, y+0.5D, z+0.5D, false);
context.pause(Settings.OC_DropDelay);
return new Object[]{ true };
}
else{
int moved = InventoryUtil.dropItemInventoryWorld(dstack.copy(), this.cart.world(), x, y, z, dir.getOpposite(), amount);
if(moved < 1) return new Object[]{ false, "inventory full" };
if(dstack.stackSize > moved) dstack.stackSize-=moved;
else this.cart.mainInventory().setInventorySlotContents(sslot, null);
context.pause(Settings.OC_DropDelay);
return new Object[]{ true };
}
}
@Callback(doc = "function(side:number[, count:number=64]):boolean -- Suck up items from the specified side.")
public Object[] suck(Context context, Arguments args){
int side = args.checkInteger(0);
int amount = args.optInteger(1,64);
if(side<0 || side > 5) throw new IllegalArgumentException("invalid side");
int sslot = this.cart.selectedSlot();
if(!(sslot>=0 && sslot<this.cart.mainInventory().getSizeInventory()))
return new Object[]{ false ,"no slot selected"};
if(amount<1) return new Object[]{ false };
ForgeDirection dir = this.cart.toGlobal(ForgeDirection.getOrientation(side));
int x = (int)Math.floor(this.cart.xPosition())+dir.offsetX;
int y = (int)Math.floor(this.cart.yPosition())+dir.offsetY;
int z = (int)Math.floor(this.cart.zPosition())+dir.offsetZ;
if(!(this.cart.world().getTileEntity(x, y, z) instanceof IInventory)){
int moved = 0;
int[] acc = InventoryUtil.getAccessible(this.cart.mainInventory(), ForgeDirection.UNKNOWN);
acc = InventoryUtil.prioritizeAccessible(acc, this.cart.selectedSlot());
if(!ItemUtil.hasDroppedItems(this.cart.world(), x, y, z))
return new Object[]{ false };
for(int i=0;i<acc.length && moved<1;i+=1){
ItemStack filter = this.cart.mainInventory().getStackInSlot(acc[i]);
if(filter != null) acc = InventoryUtil.sortAccessible(this.cart.mainInventory(), acc, filter);
acc = InventoryUtil.prioritizeAccessible(acc, this.cart.selectedSlot());
int movable = InventoryUtil.spaceforItem(filter, this.cart.mainInventory(), acc);
movable = Math.min(movable, Math.min((filter == null) ? 64 :filter.getMaxStackSize(), amount));
ItemStack stack = ItemUtil.suckItems(this.cart.world(), x, y, z, filter, movable);
if(stack!=null) moved = stack.stackSize;
if(moved > 0) InventoryUtil.putInventory(stack, this.cart.mainInventory(), moved, ForgeDirection.UNKNOWN, acc);
}
if(moved>0) context.pause(Settings.OC_SuckDelay);
return new Object[]{ (moved > 0) };
}
else{
int[] mslots = InventoryUtil.getAccessible(this.cart.mainInventory(), ForgeDirection.UNKNOWN);
int moved = InventoryUtil.suckItemInventoryWorld(this.cart.mainInventory(), mslots, this.cart.selectedSlot(), this.cart.worldObj,
x, y, z, dir.getOpposite(), amount);
if(moved > 0){
context.pause(Settings.OC_SuckDelay);
return new Object[]{ true };
}
return new Object[]{ false };
}
}
//-------World-Tank-------//
@Callback(doc = "function(side:number):boolean -- Compare the fluid in the selected tank with the fluid on the specified side. Returns true if equal.")
public Object[] compareFluid(Context context, Arguments args){
int side = args.checkInteger(0);
if(side<0 || side > 5) throw new IllegalArgumentException("invalid side");
int stank = this.cart.selectedTank();
if(!(stank>0 && stank<=this.cart.tankcount()))
return new Object[]{ false , "no tank selected"};
ForgeDirection dir = this.cart.toGlobal(ForgeDirection.getOrientation(side));
int x = (int)Math.floor(this.cart.xPosition())+dir.offsetX;
int y = (int)Math.floor(this.cart.yPosition())+dir.offsetY;
int z = (int)Math.floor(this.cart.zPosition())+dir.offsetZ;
IFluidHandler t1 = TankUtil.getFluidHandler(this.cart.world(), x, y, z);
FluidStack st = this.cart.getTank(stank).getFluid();
if(t1 == null) return new Object[]{ false };
return new Object[]{ TankUtil.hasFluid(t1, st, dir.getOpposite()) };
}
@Callback(doc = "function(side:number[, amount:number=1000]):boolean, number or string -- Drains the specified amount of fluid from the specified side. Returns the amount drained, or an error message.")
public Object[] drain(Context context, Arguments args){
int side = args.checkInteger(0);
if(side<0 || side > 5) throw new IllegalArgumentException("invalid side");
int amount = args.optInteger(1, 1000);
int stank = this.cart.selectedTank();
if(!(stank>0 && stank<=this.cart.tankcount()))
return new Object[]{ false , "no tank selected"};
ForgeDirection dir = this.cart.toGlobal(ForgeDirection.getOrientation(side));
int x = (int)Math.floor(this.cart.xPosition())+dir.offsetX;
int y = (int)Math.floor(this.cart.yPosition())+dir.offsetY;
int z = (int)Math.floor(this.cart.zPosition())+dir.offsetZ;
IFluidTank t1 = this.cart.getTank(stank);
IFluidHandler t2 = TankUtil.getFluidHandler(this.cart.world(), x, y, z);
if(t2 == null ) return new Object[]{ false , "no tank found"};
FluidStack filter = t1.getFluid();
FluidStack drain = null;
if(filter == null)
drain = t2.drain(dir.getOpposite(), amount, false);
else
drain = t2.drain(dir.getOpposite(), new FluidStack(filter,amount), false);
if(drain == null) return new Object[]{ false, "incompatible or no fluid" };
int moved = t1.fill(drain, false);
if(moved < 1) return new Object[]{ false, "tank full" };
t1.fill(t2.drain(dir.getOpposite(), new FluidStack(drain, moved), true), true);
return new Object[]{ true, moved};
}
@Callback(doc = "function(side:number[, amount:number=1000]):boolean, number or string -- Eject the specified amount of fluid to the specified side. Returns the amount ejected or an error message.")
public Object[] fill(Context context, Arguments args){
int side = args.checkInteger(0);
if(side<0 || side > 5) throw new IllegalArgumentException("invalid side");
int amount = args.optInteger(1, 1000);
int stank = this.cart.selectedTank();
if(!(stank>0 && stank<=this.cart.tankcount()))
return new Object[]{ false , "no tank selected"};
ForgeDirection dir = this.cart.toGlobal(ForgeDirection.getOrientation(side));
int x = (int)Math.floor(this.cart.xPosition())+dir.offsetX;
int y = (int)Math.floor(this.cart.yPosition())+dir.offsetY;
int z = (int)Math.floor(this.cart.zPosition())+dir.offsetZ;
IFluidTank t1 = this.cart.getTank(stank);
IFluidHandler t2 = TankUtil.getFluidHandler(this.cart.world(), x, y, z);
if(t2 == null ) return new Object[]{ false , "no tank found"};
FluidStack drain = t1.drain(amount, false);
if(drain == null)
return new Object[]{ false, "tank is empty" };
if(TankUtil.getSpaceForFluid(t2, drain, dir.getOpposite()) < 1)
return new Object[]{ false, "incompatible or no fluid" };
int moved = t2.fill(dir.getOpposite(),drain, false);
if(moved < 1) return new Object[]{ false, "no space" };
t2.fill(dir.getOpposite(),t1.drain(moved, true), true);
return new Object[]{ true, moved };
}
}