package crazypants.enderio.conduit.liquid;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.IFluidHandler;
import cpw.mods.fml.common.gameevent.TickEvent.ServerTickEvent;
import crazypants.enderio.conduit.ConduitNetworkTickHandler;
import crazypants.enderio.conduit.ConduitNetworkTickHandler.TickListener;
import crazypants.enderio.conduit.ConduitUtil;
import crazypants.enderio.conduit.IConduit;
import crazypants.util.BlockCoord;
public class LiquidConduitNetwork extends AbstractTankConduitNetwork<LiquidConduit> {
public LiquidConduitNetwork() {
super(LiquidConduit.class);
}
private long timeAtLastApply;
private int ticksEmpty = 0;
private int maxFlowsPerTick = 10;
private int lastFlowIndex = 0;
private boolean printFlowTiming = false;
private int pushToken = 0;
private int inputVolume;
private int outputVolume;
private boolean inputLocked = false;
private final InnerTickHandler tickHandler = new InnerTickHandler();
public boolean lockNetworkForFill() {
if(inputLocked) {
return false;
}
inputLocked = true;
return true;
}
public void unlockNetworkFromFill() {
inputLocked = false;
}
@Override
public void onUpdateEntity(IConduit conduit) {
World world = conduit.getBundle().getEntity().getWorldObj();
if(world == null) {
return;
}
if(world.isRemote || liquidType == null) {
return;
}
long curTime = world.getTotalWorldTime();
if(curTime > 0 && curTime != timeAtLastApply) {
timeAtLastApply = curTime;
ConduitNetworkTickHandler.instance.addListener(tickHandler);
}
}
private void doTick() {
List<LiquidConduit> cons = getConduits();
if(cons == null || cons.isEmpty()) {
return;
}
if(isEmpty()) {
if(!fluidTypeLocked && liquidType != null) {
ticksEmpty++;
if(ticksEmpty > 40) {
setFluidType(null);
ticksEmpty = 0;
}
}
return;
}
ticksEmpty = 0;
long curTime = cons.get(0).getBundle().getEntity().getWorldObj().getTotalWorldTime();
// 1000 water, 6000 lava
if(liquidType != null && liquidType.getFluid() != null && !isEmpty()) {
int visc = Math.max(1000, liquidType.getFluid().getViscosity());
if(curTime % (visc / 500) == 0) {
long start = System.nanoTime();
if(doFlow() && printFlowTiming) {
long took = System.nanoTime() - start;
double secs = took / 1000000000.0;
System.out.println("LiquidConduitNetwork.onUpdateEntity: took " + secs + " secs, " + (secs * 1000) + " millis");
}
}
}
}
void addedFromExternal(int res) {
inputVolume += res;
}
void outputedToExternal(int filled) {
outputVolume += filled;
}
int getNextPushToken() {
return ++pushToken;
}
private boolean doFlow() {
int pushToken = getNextPushToken();
List<FlowAction> actions = new ArrayList<FlowAction>();
for (int i = 0; i < Math.min(maxFlowsPerTick, conduits.size()); i++) {
if(lastFlowIndex >= conduits.size()) {
lastFlowIndex = 0;
}
flowFrom(conduits.get(lastFlowIndex), actions, pushToken);
++lastFlowIndex;
}
for (FlowAction action : actions) {
action.apply();
}
boolean result = !actions.isEmpty();
// Flush any tanks with a tiny bit left
List<LiquidConduit> toEmpty = new ArrayList<LiquidConduit>();
for (LiquidConduit con : conduits) {
if(con != null && con.getTank().getFluidAmount() < 10) {
toEmpty.add(con);
} else {
//some of the conduits have fluid left in them so don't do the final drain yet
return result;
}
}
if(toEmpty.isEmpty()) {
return result;
}
List<LocatedFluidHandler> externals = new ArrayList<LocatedFluidHandler>();
for (AbstractTankConduit con : conduits) {
Set<ForgeDirection> extCons = con.getExternalConnections();
for (ForgeDirection dir : extCons) {
if(con.canOutputToDir(dir)) {
IFluidHandler externalTank = con.getExternalHandler(dir);
if(externalTank != null) {
externals.add(new LocatedFluidHandler(externalTank, con.getLocation().getLocation(dir), dir.getOpposite()));
}
}
}
}
if(externals.isEmpty()) {
return result;
}
for (LiquidConduit con : toEmpty) {
drainConduitToNearestExternal(con, externals);
}
return result;
}
@Override
public void setFluidTypeLocked(boolean fluidTypeLocked) {
super.setFluidTypeLocked(fluidTypeLocked);
if(!fluidTypeLocked && isEmpty()) {
setFluidType(null);
}
}
private boolean isEmpty() {
for (LiquidConduit con : conduits) {
if(con.tank.getFluidAmount() > 0) {
return false;
}
}
return true;
}
private void drainConduitToNearestExternal(LiquidConduit con, List<LocatedFluidHandler> externals) {
BlockCoord conLoc = con.getLocation();
FluidStack toDrain = con.getTank().getFluid();
if(toDrain == null) {
return;
}
int closestDistance = Integer.MAX_VALUE;
LocatedFluidHandler closestTank = null;
for (LocatedFluidHandler lh : externals) {
int distance = lh.bc.distanceSquared(conLoc);
if(distance < closestDistance) {
int couldFill = lh.tank.fill(lh.dir, toDrain.copy(), false);
if(couldFill > 0) {
closestTank = lh;
closestDistance = distance;
}
}
}
if(closestTank != null) {
int filled = closestTank.tank.fill(closestTank.dir, toDrain.copy(), true);
con.getTank().addAmount(-filled);
}
}
private void flowFrom(LiquidConduit con, List<FlowAction> actions, int pushPoken) {
ConduitTank tank = con.getTank();
int totalAmount = tank.getFluidAmount();
if(totalAmount <= 0) {
return;
}
int maxFlowVolume = 20;
// First flow all we can down, then balance the rest
if(con.getConduitConnections().contains(ForgeDirection.DOWN)) {
BlockCoord loc = con.getLocation().getLocation(ForgeDirection.DOWN);
ILiquidConduit dc = ConduitUtil.getConduit(con.getBundle().getEntity().getWorldObj(), loc.x, loc.y, loc.z, ILiquidConduit.class);
if(dc instanceof LiquidConduit) {
LiquidConduit downCon = (LiquidConduit) dc;
int filled = downCon.fill(ForgeDirection.UP, tank.getFluid().copy(), false, false, pushPoken);
int actual = filled;
actual = Math.min(actual, tank.getFluidAmount());
actual = Math.min(actual, downCon.getTank().getAvailableSpace());
tank.addAmount(-actual);
downCon.getTank().addAmount(actual);
}
}
totalAmount = tank.getFluidAmount();
if(totalAmount <= 0) {
return;
}
FluidStack available = tank.getFluid();
int totalRequested = 0;
int numRequests = 0;
// Then to external connections
for (ForgeDirection dir : con.getExternalConnections()) {
if(con.canOutputToDir(dir)) {
IFluidHandler extCon = con.getExternalHandler(dir);
if(extCon != null) {
int amount = extCon.fill(dir.getOpposite(), available.copy(), false);
if(amount > 0) {
totalRequested += amount;
numRequests++;
}
}
}
}
if(numRequests > 0) {
int amountPerRequest = Math.min(totalAmount, totalRequested) / numRequests;
amountPerRequest = Math.min(maxFlowVolume, amountPerRequest);
FluidStack requestSource = available.copy();
requestSource.amount = amountPerRequest;
for (ForgeDirection dir : con.getExternalConnections()) {
if(con.canOutputToDir(dir)) {
IFluidHandler extCon = con.getExternalHandler(dir);
if(extCon != null) {
int amount = extCon.fill(dir.getOpposite(), requestSource.copy(), true);
if(amount > 0) {
outputedToExternal(amount);
tank.addAmount(-amount);
}
}
}
}
}
totalAmount = tank.getFluidAmount();
if(totalAmount <= 0) {
return;
}
int totalCapacity = tank.getCapacity();
BlockCoord loc = con.getLocation();
Collection<ILiquidConduit> connections =
ConduitUtil.getConnectedConduits(con.getBundle().getEntity().getWorldObj(),
loc.x, loc.y, loc.z, ILiquidConduit.class);
int numTargets = 0;
for (ILiquidConduit n : connections) {
LiquidConduit neighbour = (LiquidConduit) n;
if(canFlowTo(con, neighbour)) { // can only flow within same network
totalAmount += neighbour.getTank().getFluidAmount();
totalCapacity += neighbour.getTank().getCapacity();
numTargets++;
}
}
float targetRatio = (float) totalAmount / totalCapacity;
int flowVolume = (int) Math.floor((targetRatio - tank.getFilledRatio()) * tank.getCapacity());
flowVolume = Math.min(maxFlowVolume, flowVolume);
if(Math.abs(flowVolume) < 2) {
return; // dont bother with transfers of less than a thousands of a bucket
}
for (ILiquidConduit n : connections) {
LiquidConduit neigbour = (LiquidConduit) n;
if(canFlowTo(con, neigbour)) { // can only flow within same network
flowVolume = (int) Math.floor((targetRatio - neigbour.getTank().getFilledRatio()) * neigbour.getTank().getCapacity());
if(flowVolume != 0) {
actions.add(new FlowAction(con, neigbour, flowVolume));
}
}
}
}
private boolean canFlowTo(LiquidConduit con, LiquidConduit neighbour) {
if(con == null || neighbour == null) {
return false;
}
if(neighbour.getNetwork() != this) {
return false;
}
if(neighbour.getLocation().y > con.getLocation().y) {
return false;
}
float nr = neighbour.getTank().getFilledRatio();
if(nr >= con.getTank().getFilledRatio()) {
return false;
}
return true;
}
static class FlowAction {
final LiquidConduit from;
final LiquidConduit to;
final int amount;
FlowAction(LiquidConduit fromIn, LiquidConduit toIn, int amountIn) {
if(amountIn < 0) {
to = fromIn;
from = toIn;
amount = -amountIn;
} else {
to = toIn;
from = fromIn;
amount = amountIn;
}
}
void apply() {
if(amount != 0) {
// don't take more than it has
int actual = Math.min(amount, from.getTank().getFluidAmount());
// and don't add more than it can take
actual = Math.min(actual, to.getTank().getAvailableSpace());
if(from != null && to != null) {
from.getTank().addAmount(-actual);
to.getTank().addAmount(actual);
}
}
}
}
static class LocatedFluidHandler {
final IFluidHandler tank;
final BlockCoord bc;
final ForgeDirection dir;
LocatedFluidHandler(IFluidHandler tank, BlockCoord bc, ForgeDirection dir) {
this.tank = tank;
this.bc = bc;
this.dir = dir;
}
}
private class InnerTickHandler implements TickListener {
@Override
public void tickStart(ServerTickEvent evt) {
}
@Override
public void tickEnd(ServerTickEvent evt) {
doTick();
}
}
}