package micdoodle8.mods.galacticraft.core.energy.grid;
import cofh.api.energy.IEnergyReceiver;
import ic2.api.energy.tile.IEnergySink;
import mekanism.api.energy.IStrictEnergyAcceptor;
import micdoodle8.mods.galacticraft.api.transmission.grid.IElectricityNetwork;
import micdoodle8.mods.galacticraft.api.transmission.tile.IConductor;
import micdoodle8.mods.galacticraft.api.transmission.tile.IElectrical;
import micdoodle8.mods.galacticraft.api.vector.BlockVec3;
import micdoodle8.mods.galacticraft.core.energy.EnergyConfigHandler;
import micdoodle8.mods.galacticraft.core.energy.EnergyUtil;
import micdoodle8.mods.galacticraft.core.tick.TickHandlerServer;
import micdoodle8.mods.galacticraft.core.util.ConfigManagerCore;
import micdoodle8.mods.galacticraft.core.util.GCCoreUtil;
import micdoodle8.mods.galacticraft.core.util.GCLog;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.FMLLog;
import java.util.*;
//import buildcraft.api.power.PowerHandler.Type;
/**
* A universal network that works with multiple energy systems.
*
* @author radfast, micdoodle8, Calclavia, Aidancbrady
*/
public class EnergyNetwork implements IElectricityNetwork
{
private boolean isMekLoaded = EnergyConfigHandler.isMekanismLoaded() && !EnergyConfigHandler.disableMekanismOutput;
private boolean isRF1Loaded = EnergyConfigHandler.isRFAPIv1Loaded() && !EnergyConfigHandler.disableRFOutput;
private boolean isRF2Loaded = EnergyConfigHandler.isRFAPIv2Loaded() && !EnergyConfigHandler.disableRFOutput;
private boolean isIC2Loaded = EnergyConfigHandler.isIndustrialCraft2Loaded() && !EnergyConfigHandler.disableIC2Output;
/* Re-written by radfast for better performance
*
* Imagine a 30 producer, 80 acceptor network...
*
* Before: it would have called the inner loop in produce() 2400 times each tick. Not good!
*
* After: the inner loop runs 80 times - part of it is in doTickStartCalc() at/near the tick start, and part of it is in doProduce() at the end of the tick
*/
public static int tickCount = 0;
private int tickDone = -1;
private float totalRequested = 0F;
private float totalStorageExcess = 0F;
private float totalEnergy = 0F;
private float totalSent = 0F;
private boolean doneScheduled = false;
private boolean spamstop = false;
private boolean loopPrevention = false;
public int networkTierGC = 1;
private int producersTierGC = 1;
/*
* connectedAcceptors is all the acceptors connected to this network
* connectedDirections is the directions of those connections (from the point of view of the acceptor tile)
* Note: each position in those two linked lists matches
* so, an acceptor connected on two sides will be in connectedAcceptors twice
*/
private List<Object> connectedAcceptors = new LinkedList<Object>();
private List<EnumFacing> connectedDirections = new LinkedList<EnumFacing>();
/*
* availableAcceptors is the acceptors which can receive energy (this tick)
* availableconnectedDirections is a map of those acceptors and the directions they will receive from (from the point of view of the acceptor tile)
* Note: each acceptor will only be included once in these collections
* (there is no point trying to put power into a machine twice from two different sides)
*/
private Set<Object> availableAcceptors = new HashSet<Object>();
private Map<Object, EnumFacing> availableconnectedDirections = new HashMap<Object, EnumFacing>();
private Map<Object, Float> energyRequests = new HashMap<Object, Float>();
private List<TileEntity> ignoreAcceptors = new LinkedList<TileEntity>();
private final Set<IConductor> conductors = new HashSet<IConductor>();
//This is an energy per tick which exceeds what any normal machine will request, so the requester must be an energy storage - for example, a battery or an energy cube
private final static float ENERGY_STORAGE_LEVEL = 200F;
@Override
public Set<IConductor> getTransmitters()
{
return this.conductors;
}
/**
* Get the total energy request in this network
*
* @param ignoreTiles Tiles to ignore in the request calculations (NOTE: only used in initial (internal) check.
* @return Amount of energy requested in this network
*/
@Override
public float getRequest(TileEntity... ignoreTiles)
{
if (EnergyNetwork.tickCount != this.tickDone)
{
//Start the new tick - initialise everything
this.ignoreAcceptors.clear();
this.ignoreAcceptors.addAll(Arrays.asList(ignoreTiles));
this.doTickStartCalc();
}
return this.totalRequested - this.totalEnergy - this.totalSent;
}
/**
* Produce energy into the network
*
* @param energy Amount of energy to send into the network
* @param doReceive Whether to put energy into the network (true) or just simulate (false)
* @param ignoreTiles TileEntities to ignore for energy transfers.
* @return Amount of energy REMAINING from the passed energy parameter
*/
@Override
public float produce(float energy, boolean doReceive, int producerTier, TileEntity... ignoreTiles)
{
if (this.loopPrevention)
{
return energy;
}
if (energy > 0F)
{
if (EnergyNetwork.tickCount != this.tickDone)
{
this.tickDone = EnergyNetwork.tickCount;
//Start the new tick - initialise everything
this.ignoreAcceptors.clear();
this.ignoreAcceptors.addAll(Arrays.asList(ignoreTiles));
this.producersTierGC = 1;
this.doTickStartCalc();
}
else
{
this.ignoreAcceptors.addAll(Arrays.asList(ignoreTiles));
}
if (!this.doneScheduled && this.totalRequested > 0.0F)
{
TickHandlerServer.scheduleNetworkTick(this);
this.doneScheduled = true;
}
//On a regular mid-tick produce(), just figure out how much is totalEnergy this tick and return the used amount
//This will return 0 if totalRequested is 0 - for example a network with no acceptors
float totalEnergyLast = this.totalEnergy;
//Add the energy for distribution by this grid later this tick
//Note: totalEnergy cannot exceed totalRequested
if (doReceive)
{
this.totalEnergy += Math.min(energy, this.totalRequested - totalEnergyLast);
//The field producersTierGC will be the *highest* of any producers putting energy into the network this tick
if (producerTier > this.producersTierGC)
{
this.producersTierGC = producerTier;
}
}
if (this.totalRequested >= totalEnergyLast + energy)
{
return 0F; //All the electricity will be used
}
if (totalEnergyLast >= this.totalRequested)
{
return energy; //None of the electricity will be used
}
return totalEnergyLast + energy - this.totalRequested; //Some of the electricity will be used
}
return energy;
}
/**
* Called on server tick end, from the Galacticraft Core tick handler.
*/
public void tickEnd()
{
this.doneScheduled = false;
this.loopPrevention = true;
//Finish the last tick if there was some to send and something to receive it
if (this.totalEnergy > 0F)
{
//Call doTickStartCalc a second time in case anything has updated meanwhile
this.doTickStartCalc();
if (this.totalRequested > 0F)
{
this.totalSent = this.doProduce();
if (this.totalSent < this.totalEnergy)
{
//Any spare energy left is retained for the next tick
this.totalEnergy -= this.totalSent;
}
else
{
this.totalEnergy = 0F;
}
}
else
{
this.totalEnergy = 0F;
}
}
else
{
this.totalEnergy = 0F;
}
this.loopPrevention = false;
}
/**
* Refreshes all tiles in network, and updates requested energy
*/
private void doTickStartCalc()
{
this.tickDone = EnergyNetwork.tickCount;
this.totalSent = 0F;
this.refreshAcceptors();
if (!EnergyUtil.initialisedIC2Methods)
{
EnergyUtil.initialiseIC2Methods();
}
if (this.conductors.size() == 0)
{
return;
}
this.loopPrevention = true;
this.availableAcceptors.clear();
this.availableconnectedDirections.clear();
this.energyRequests.clear();
this.totalRequested = 0.0F;
this.totalStorageExcess = 0F;
if (!this.connectedAcceptors.isEmpty())
{
float e;
final Iterator<EnumFacing> acceptorDirection = this.connectedDirections.iterator();
for (Object acceptor : this.connectedAcceptors)
{
//This tries all sides of the acceptor which are connected (see refreshAcceptors())
EnumFacing sideFrom = acceptorDirection.next();
//But the grid will only put energy into the acceptor from one side - once it's in availableAcceptors
if (!this.ignoreAcceptors.contains(acceptor) && !this.availableAcceptors.contains(acceptor))
{
e = 0.0F;
if (acceptor instanceof IElectrical)
{
e = ((IElectrical) acceptor).getRequest(sideFrom);
}
else if (isMekLoaded && acceptor instanceof IStrictEnergyAcceptor)
{
e = (float) ((((IStrictEnergyAcceptor) acceptor).getMaxEnergy() - ((IStrictEnergyAcceptor) acceptor).getEnergy()) / EnergyConfigHandler.TO_MEKANISM_RATIO);
}
else if (isIC2Loaded && acceptor instanceof IEnergySink)
{
double result = 0;
try
{
result = (Double) EnergyUtil.demandedEnergyIC2.invoke(acceptor);
}
catch (Exception ex)
{
if (ConfigManagerCore.enableDebug)
{
ex.printStackTrace();
}
}
//Cap IC2 power transfer at 128EU/t for standard Alu wire, 256EU/t for heavy Alu wire
result = Math.min(result, this.networkTierGC * 128D);
e = (float) result / EnergyConfigHandler.TO_IC2_RATIO;
}
else if (isRF2Loaded && acceptor instanceof IEnergyReceiver)
{
e = ((IEnergyReceiver) acceptor).receiveEnergy(sideFrom, Integer.MAX_VALUE, true) / EnergyConfigHandler.TO_RF_RATIO;
}
if (e > 0.0F)
{
this.availableAcceptors.add(acceptor);
this.availableconnectedDirections.put(acceptor, sideFrom);
this.energyRequests.put(acceptor, e);
this.totalRequested += e;
if (e > EnergyNetwork.ENERGY_STORAGE_LEVEL)
{
this.totalStorageExcess += e - EnergyNetwork.ENERGY_STORAGE_LEVEL;
}
}
}
}
}
this.loopPrevention = false;
}
/**
* Complete the energy transfer. Called internally on server tick end.
*
* @return Amount of energy SENT to all acceptors
*/
private float doProduce()
{
float sent = 0.0F;
if (!this.availableAcceptors.isEmpty())
{
float energyNeeded = this.totalRequested;
float energyAvailable = this.totalEnergy;
float reducor = 1.0F;
float energyStorageReducor = 1.0F;
if (energyNeeded > energyAvailable)
{
//If not enough energy, try reducing what goes into energy storage (if any)
energyNeeded -= this.totalStorageExcess;
//If there's still not enough, put the minimum into energy storage (if any) and, anyhow, reduce everything proportionately
if (energyNeeded > energyAvailable)
{
energyStorageReducor = 0F;
reducor = energyAvailable / energyNeeded;
}
else
{
//Energyavailable exceeds the total needed but only if storage does not fill all in one go - this is a common situation
energyStorageReducor = (energyAvailable - energyNeeded) / this.totalStorageExcess;
}
}
float currentSending;
float sentToAcceptor;
int tierProduced = Math.min(this.producersTierGC, this.networkTierGC);
Object debugTE = null;
try
{
for (Object tileEntity : this.availableAcceptors)
{
debugTE = tileEntity;
//Exit the loop if there is no energy left at all (should normally not happen, should be some even for the last acceptor)
if (sent >= energyAvailable)
{
break;
}
//The base case is to give each acceptor what it is requesting
currentSending = this.energyRequests.get(tileEntity);
//If it's an energy store, we may need to damp it down if energyStorageReducor is less than 1
if (currentSending > EnergyNetwork.ENERGY_STORAGE_LEVEL)
{
currentSending = EnergyNetwork.ENERGY_STORAGE_LEVEL + (currentSending - EnergyNetwork.ENERGY_STORAGE_LEVEL) * energyStorageReducor;
}
//Reduce everything proportionately if there is not enough energy for all needs
currentSending *= reducor;
if (currentSending > energyAvailable - sent)
{
currentSending = energyAvailable - sent;
}
EnumFacing sideFrom = this.availableconnectedDirections.get(tileEntity);
if (tileEntity instanceof IElectrical)
{
sentToAcceptor = ((IElectrical) tileEntity).receiveElectricity(sideFrom, currentSending, tierProduced, true);
}
else if (isMekLoaded && tileEntity instanceof IStrictEnergyAcceptor)
{
sentToAcceptor = (float) ((IStrictEnergyAcceptor) tileEntity).transferEnergyToAcceptor(sideFrom, currentSending * EnergyConfigHandler.TO_MEKANISM_RATIO) / EnergyConfigHandler.TO_MEKANISM_RATIO;
}
else if (isIC2Loaded && tileEntity instanceof IEnergySink)
{
double energySendingIC2 = currentSending * EnergyConfigHandler.TO_IC2_RATIO;
if (energySendingIC2 >= 1D)
{
double result = 0;
try
{
if (EnergyUtil.voltageParameterIC2)
{
result = (Double) EnergyUtil.injectEnergyIC2.invoke(tileEntity, sideFrom.getOpposite(), energySendingIC2, 120D);
}
else
{
result = (Double) EnergyUtil.injectEnergyIC2.invoke(tileEntity, sideFrom.getOpposite(), energySendingIC2);
}
}
catch (Exception ex)
{
if (ConfigManagerCore.enableDebug)
{
ex.printStackTrace();
}
}
sentToAcceptor = currentSending - (float) result / EnergyConfigHandler.TO_IC2_RATIO;
if (sentToAcceptor < 0F)
{
sentToAcceptor = 0F;
}
}
else
{
sentToAcceptor = 0F;
}
}
else if (isRF2Loaded && tileEntity instanceof IEnergyReceiver)
{
final int currentSendinginRF = (currentSending >= Integer.MAX_VALUE / EnergyConfigHandler.TO_RF_RATIO) ? Integer.MAX_VALUE : (int) (currentSending * EnergyConfigHandler.TO_RF_RATIO);
sentToAcceptor = ((IEnergyReceiver) tileEntity).receiveEnergy(sideFrom, currentSendinginRF, false) / EnergyConfigHandler.TO_RF_RATIO;
}
else
{
sentToAcceptor = 0F;
}
if (sentToAcceptor / currentSending > 1.002F && sentToAcceptor > 0.01F)
{
if (!this.spamstop)
{
FMLLog.info("Energy network: acceptor took too much energy, offered " + currentSending + ", took " + sentToAcceptor + ". " + tileEntity.toString());
this.spamstop = true;
}
sentToAcceptor = currentSending;
}
sent += sentToAcceptor;
}
}
catch (Exception e)
{
GCLog.severe("DEBUG Energy network loop issue, please report this");
if (debugTE instanceof TileEntity)
{
GCLog.severe("Problem was likely caused by tile in dim " + GCCoreUtil.getDimensionID(((TileEntity)debugTE).getWorld()) + " at " + ((TileEntity)debugTE).getPos() + " Type:" + debugTE.getClass().getSimpleName());
}
}
}
if (EnergyNetwork.tickCount % 200 == 0)
{
this.spamstop = false;
}
float returnvalue = sent;
if (returnvalue > this.totalEnergy)
{
returnvalue = this.totalEnergy;
}
if (returnvalue < 0F)
{
returnvalue = 0F;
}
return returnvalue;
}
/**
* Refresh validity of each conductor in the network
*/
public void refreshWithChecks()
{
int tierfound = Integer.MAX_VALUE;
Iterator<IConductor> it = this.conductors.iterator();
while (it.hasNext())
{
IConductor conductor = it.next();
if (conductor == null)
{
it.remove();
continue;
}
TileEntity tile = (TileEntity) conductor;
World world = tile.getWorld();
//Remove any conductors in unloaded chunks
if (tile.isInvalid() || world == null || !world.isBlockLoaded(tile.getPos()))
{
it.remove();
continue;
}
if (conductor != world.getTileEntity(tile.getPos()))
{
it.remove();
continue;
}
if (conductor.getTierGC() < tierfound)
{
tierfound = conductor.getTierGC();
}
if (conductor.getNetwork() != this)
{
conductor.setNetwork(this);
conductor.onNetworkChanged();
}
}
//This will set the network tier to 2 if all the conductors are tier 2
if (tierfound == Integer.MAX_VALUE)
{
tierfound = 1;
}
this.networkTierGC = tierfound;
}
@Override
public void refresh()
{
int tierfound = Integer.MAX_VALUE;
Iterator<IConductor> it = this.conductors.iterator();
while (it.hasNext())
{
IConductor conductor = it.next();
if (conductor == null)
{
it.remove();
continue;
}
TileEntity tile = (TileEntity) conductor;
World world = tile.getWorld();
//Remove any conductors in unloaded chunks
if (tile.isInvalid() || world == null)
{
it.remove();
continue;
}
if (conductor.getTierGC() < tierfound)
{
tierfound = conductor.getTierGC();
}
if (conductor.getNetwork() != this)
{
conductor.setNetwork(this);
conductor.onNetworkChanged();
}
}
//This will set the network tier to 2 if all the conductors are tier 2, etc
if (tierfound == Integer.MAX_VALUE)
{
tierfound = 1;
}
this.networkTierGC = tierfound;
}
/**
* Refresh all energy acceptors in the network
*/
private void refreshAcceptors()
{
this.connectedAcceptors.clear();
this.connectedDirections.clear();
this.refreshWithChecks();
try
{
LinkedList<IConductor> conductorsCopy = new LinkedList();
conductorsCopy.addAll(this.conductors);
//This prevents concurrent modifications if something in the loop causes chunk loading
//(Chunk loading can change the network if new conductors are found)
for (IConductor conductor : conductorsCopy)
{
EnergyUtil.setAdjacentPowerConnections((TileEntity) conductor, this.connectedAcceptors, this.connectedDirections);
}
}
catch (Exception e)
{
FMLLog.severe("GC Aluminium Wire: Error when testing whether another mod's tileEntity can accept energy.");
e.printStackTrace();
}
}
/**
* Combine this network with another electricitynetwork
*
* @param network Network to merge with
* @return The final, joined network
*/
@Override
public IElectricityNetwork merge(IElectricityNetwork network)
{
if (network != null && network != this)
{
Set<IConductor> thisNetwork = this.conductors;
Set<IConductor> thatNetwork = network.getTransmitters();
if (thisNetwork.size() >= thatNetwork.size())
{
thisNetwork.addAll(thatNetwork);
this.refresh();
if (network instanceof EnergyNetwork)
{
((EnergyNetwork) network).destroy();
}
return this;
}
else
{
thatNetwork.addAll(thisNetwork);
network.refresh();
this.destroy();
return network;
}
}
return this;
}
private void destroy()
{
this.conductors.clear();
this.connectedAcceptors.clear();
this.availableAcceptors.clear();
this.totalEnergy = 0F;
this.totalRequested = 0F;
TickHandlerServer.removeNetworkTick(this);
}
@Override
public void split(IConductor splitPoint)
{
if (splitPoint instanceof TileEntity)
{
this.getTransmitters().remove(splitPoint);
splitPoint.setNetwork(null);
//If the size of the residual network is 1, it should simply be preserved
if (this.getTransmitters().size() > 1)
{
World world = ((TileEntity) splitPoint).getWorld();
if (this.getTransmitters().size() > 0)
{
TileEntity[] nextToSplit = new TileEntity[6];
boolean[] toDo = { true, true, true, true, true, true };
TileEntity tileEntity;
int xCoord = ((TileEntity) splitPoint).getPos().getX();
int yCoord = ((TileEntity) splitPoint).getPos().getY();
int zCoord = ((TileEntity) splitPoint).getPos().getZ();
for (int j = 0; j < 6; j++)
{
switch (j)
{
case 0:
tileEntity = world.getTileEntity(((TileEntity) splitPoint).getPos().down());
break;
case 1:
tileEntity = world.getTileEntity(((TileEntity) splitPoint).getPos().up());
break;
case 2:
tileEntity = world.getTileEntity(((TileEntity) splitPoint).getPos().north());
break;
case 3:
tileEntity = world.getTileEntity(((TileEntity) splitPoint).getPos().south());
break;
case 4:
tileEntity = world.getTileEntity(((TileEntity) splitPoint).getPos().west());
break;
case 5:
tileEntity = world.getTileEntity(((TileEntity) splitPoint).getPos().east());
break;
default:
//Not reachable, only to prevent uninitiated compile errors
tileEntity = null;
break;
}
if (tileEntity instanceof IConductor)
{
nextToSplit[j] = tileEntity;
}
else
{
toDo[j] = false;
}
}
for (int i1 = 0; i1 < 6; i1++)
{
if (toDo[i1])
{
TileEntity connectedBlockA = nextToSplit[i1];
NetworkFinder finder = new NetworkFinder(world, new BlockVec3(connectedBlockA), new BlockVec3((TileEntity) splitPoint));
List<IConductor> partNetwork = finder.exploreNetwork();
//Mark any others still to do in the nextToSplit array which are connected to this, as dealt with
for (int i2 = i1 + 1; i2 < 6; i2++)
{
TileEntity connectedBlockB = nextToSplit[i2];
if (toDo[i2])
{
if (partNetwork.contains(connectedBlockB))
{
toDo[i2] = false;
}
}
}
//Now make the new network from partNetwork
EnergyNetwork newNetwork = new EnergyNetwork();
newNetwork.getTransmitters().addAll(partNetwork);
newNetwork.refreshWithChecks();
}
}
this.destroy();
}
}
//Splitting a 1-block network leaves nothing
else if (this.getTransmitters().size() == 0)
{
this.destroy();
}
}
}
@Override
public String toString()
{
return "EnergyNetwork[" + this.hashCode() + "|Wires:" + this.getTransmitters().size() + "|Acceptors:" + this.connectedAcceptors.size() + "]";
}
}