package mekanism.generators.common;
import static java.lang.Math.max;
import static java.lang.Math.min;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import mekanism.api.Coord4D;
import mekanism.api.IHeatTransfer;
import mekanism.api.gas.GasRegistry;
import mekanism.api.gas.GasStack;
import mekanism.api.gas.GasTank;
import mekanism.api.lasers.ILaserReceptor;
import mekanism.api.reactor.IFusionReactor;
import mekanism.api.reactor.INeutronCapture;
import mekanism.api.reactor.IReactorBlock;
import mekanism.api.util.UnitDisplayUtils.TemperatureUnit;
import mekanism.common.Mekanism;
import mekanism.common.network.PacketTileEntity.TileEntityMessage;
import mekanism.generators.common.item.ItemHohlraum;
import mekanism.generators.common.tile.reactor.TileEntityReactorController;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.item.ItemCoal;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.DamageSource;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTank;
public class FusionReactor implements IFusionReactor
{
public TileEntityReactorController controller;
public Set<IReactorBlock> reactorBlocks = new HashSet<IReactorBlock>();
public Set<INeutronCapture> neutronCaptors = new HashSet<INeutronCapture>();
public Set<IHeatTransfer> heatTransfers = new HashSet<IHeatTransfer>();
//Current stores of temperature - internally uses ambient-relative kelvin units
public double plasmaTemperature;
public double caseTemperature;
//Last values of temperature
public double lastPlasmaTemperature;
public double lastCaseTemperature;
public double heatToAbsorb = 0;
//Reaction characteristics
public static double burnTemperature = TemperatureUnit.AMBIENT.convertFromK(1E8);
public static double burnRatio = 1;
public static double energyPerFuel = 5E6;
public int injectionRate = 0;
//Thermal characteristics
public static double plasmaHeatCapacity = 100;
public static double caseHeatCapacity = 1;
public static double enthalpyOfVaporization = 10;
public static double thermocoupleEfficiency = 0.05;
public static double steamTransferEfficiency = 0.1;
//Heat transfer metrics
public static double plasmaCaseConductivity = 0.2;
public static double caseWaterConductivity = 0.3;
public static double caseAirConductivity = 0.1;
public boolean burning = false;
public boolean activelyCooled = true;
public boolean updatedThisTick;
public boolean formed = false;
public FusionReactor(TileEntityReactorController c)
{
controller = c;
}
@Override
public void addTemperatureFromEnergyInput(double energyAdded)
{
plasmaTemperature += energyAdded / plasmaHeatCapacity * (isBurning() ? 1 : 10);
}
public boolean hasHohlraum()
{
if(controller != null)
{
ItemStack hohlraum = controller.inventory[0];
if(hohlraum != null && hohlraum.getItem() instanceof ItemHohlraum)
{
GasStack gasStack = ((ItemHohlraum)hohlraum.getItem()).getGas(hohlraum);
return gasStack != null && gasStack.getGas() == GasRegistry.getGas("fusionFuelDT") && gasStack.amount == ItemHohlraum.MAX_GAS;
}
}
return false;
}
@Override
public void simulate()
{
if(controller.getWorldObj().isRemote)
{
lastPlasmaTemperature = plasmaTemperature;
lastCaseTemperature = caseTemperature;
return;
}
updatedThisTick = false;
//Only thermal transfer happens unless we're hot enough to burn.
if(plasmaTemperature >= burnTemperature)
{
//If we're not burning yet we need a hohlraum to ignite
if(!burning && hasHohlraum())
{
vaporiseHohlraum();
}
//Only inject fuel if we're burning
if(burning)
{
injectFuel();
int fuelBurned = burnFuel();
neutronFlux(fuelBurned);
if(fuelBurned == 0)
{
burning = false;
}
}
}
else {
burning = false;
}
//Perform the heat transfer calculations
transferHeat();
if(burning)
{
kill();
}
updateTemperatures();
}
@Override
public void updateTemperatures()
{
lastPlasmaTemperature = plasmaTemperature < 1E-1 ? 0 : plasmaTemperature;
lastCaseTemperature = caseTemperature < 1E-1 ? 0 : caseTemperature;
}
public void vaporiseHohlraum()
{
getFuelTank().receive(((ItemHohlraum)controller.inventory[0].getItem()).getGas(controller.inventory[0]), true);
lastPlasmaTemperature = plasmaTemperature;
controller.inventory[0] = null;
burning = true;
}
public void injectFuel()
{
int amountNeeded = getFuelTank().getNeeded();
int amountAvailable = 2*min(getDeuteriumTank().getStored(), getTritiumTank().getStored());
int amountToInject = min(amountNeeded, min(amountAvailable, injectionRate));
amountToInject -= amountToInject % 2;
getDeuteriumTank().draw(amountToInject / 2, true);
getTritiumTank().draw(amountToInject / 2, true);
getFuelTank().receive(new GasStack(GasRegistry.getGas("fusionFuelDT"), amountToInject), true);
}
public int burnFuel()
{
int fuelBurned = (int)min(getFuelTank().getStored(), max(0, lastPlasmaTemperature - burnTemperature)*burnRatio);
getFuelTank().draw(fuelBurned, true);
plasmaTemperature += energyPerFuel * fuelBurned / plasmaHeatCapacity;
return fuelBurned;
}
public void neutronFlux(int fuelBurned)
{
int neutronsRemaining = fuelBurned;
List<INeutronCapture> list = new ArrayList<INeutronCapture>(neutronCaptors);
Collections.shuffle(list);
for(INeutronCapture captor: neutronCaptors)
{
if(neutronsRemaining <= 0)
{
break;
}
neutronsRemaining = captor.absorbNeutrons(neutronsRemaining);
}
controller.radiateNeutrons(neutronsRemaining);
}
public void transferHeat()
{
//Transfer from plasma to casing
double plasmaCaseHeat = plasmaCaseConductivity * (lastPlasmaTemperature - lastCaseTemperature);
plasmaTemperature -= plasmaCaseHeat / plasmaHeatCapacity;
caseTemperature += plasmaCaseHeat / caseHeatCapacity;
//Transfer from casing to water if necessary
if(activelyCooled)
{
double caseWaterHeat = caseWaterConductivity * lastCaseTemperature;
int waterToVaporize = (int)(steamTransferEfficiency * caseWaterHeat / enthalpyOfVaporization);
waterToVaporize = min(waterToVaporize, min(getWaterTank().getFluidAmount(), getSteamTank().getCapacity() - getSteamTank().getFluidAmount()));
if(waterToVaporize > 0)
{
getWaterTank().drain(waterToVaporize, true);
getSteamTank().fill(new FluidStack(FluidRegistry.getFluid("steam"), waterToVaporize), true);
}
caseWaterHeat = waterToVaporize * enthalpyOfVaporization / steamTransferEfficiency;
caseTemperature -= caseWaterHeat / caseHeatCapacity;
for(IHeatTransfer source : heatTransfers)
{
source.simulateHeat();
}
applyTemperatureChange();
}
//Transfer from casing to environment
double caseAirHeat = caseAirConductivity * lastCaseTemperature;
caseTemperature -= caseAirHeat / caseHeatCapacity;
setBufferedEnergy(getBufferedEnergy() + caseAirHeat * thermocoupleEfficiency);
}
@Override
public FluidTank getWaterTank()
{
return controller != null ? controller.waterTank : null;
}
@Override
public FluidTank getSteamTank()
{
return controller.steamTank;
}
@Override
public GasTank getDeuteriumTank()
{
return controller.deuteriumTank;
}
@Override
public GasTank getTritiumTank()
{
return controller.tritiumTank;
}
@Override
public GasTank getFuelTank()
{
return controller.fuelTank;
}
@Override
public double getBufferedEnergy()
{
return controller.getEnergy();
}
@Override
public void setBufferedEnergy(double energy)
{
controller.setEnergy(energy);
}
@Override
public double getPlasmaTemp()
{
return lastPlasmaTemperature;
}
@Override
public void setPlasmaTemp(double temp)
{
plasmaTemperature = temp;
}
@Override
public double getCaseTemp()
{
return lastCaseTemperature;
}
@Override
public void setCaseTemp(double temp)
{
caseTemperature = temp;
}
@Override
public double getBufferSize()
{
return controller.getMaxEnergy();
}
public void kill()
{
AxisAlignedBB death_zone = AxisAlignedBB.getBoundingBox(controller.xCoord - 1, controller.yCoord - 3, controller.zCoord - 1 ,controller.xCoord + 2, controller.yCoord, controller.zCoord + 2);
List<Entity> entitiesToDie = controller.getWorldObj().getEntitiesWithinAABB(Entity.class, death_zone);
for(Entity entity : entitiesToDie)
{
entity.attackEntityFrom(DamageSource.magic, 50000F);
}
}
public void unformMultiblock(boolean keepBurning)
{
for(IReactorBlock block : reactorBlocks)
{
block.setReactor(null);
}
//Don't remove from controller
controller.setReactor(this);
reactorBlocks.clear();
neutronCaptors.clear();
formed = false;
burning = burning && keepBurning;
if(!controller.getWorldObj().isRemote)
{
Mekanism.packetHandler.sendToDimension(new TileEntityMessage(Coord4D.get(controller), controller.getNetworkedData(new ArrayList())), controller.getWorldObj().provider.dimensionId);
}
}
@Override
public void formMultiblock()
{
updatedThisTick = true;
Coord4D controllerPosition = Coord4D.get(controller);
Coord4D centreOfReactor = controllerPosition.getFromSide(ForgeDirection.DOWN, 2);
unformMultiblock(true);
reactorBlocks.add(controller);
if(!createFrame(centreOfReactor))
{
unformMultiblock(false);
return;
}
if(!addSides(centreOfReactor))
{
unformMultiblock(false);
return;
}
if(!centreIsClear(centreOfReactor))
{
unformMultiblock(false);
return;
}
formed = true;
if(!controller.getWorldObj().isRemote)
{
Mekanism.packetHandler.sendToDimension(new TileEntityMessage(Coord4D.get(controller), controller.getNetworkedData(new ArrayList())), controller.getWorldObj().provider.dimensionId);
}
}
public boolean createFrame(Coord4D centre)
{
int[][] positions = new int[][] {
{+2, +2, +0}, {+2, +1, +1}, {+2, +0, +2}, {+2, -1, +1}, {+2, -2, +0}, {+2, -1, -1}, {+2, +0, -2}, {+2, +1, -1},
{+1, +2, +1}, {+1, +1, +2}, {+1, -1, +2}, {+1, -2, +1}, {+1, -2, -1}, {+1, -1, -2}, {+1, +1, -2}, {+1, +2, -1},
{+0, +2, +2}, {+0, -2, +2}, {+0, -2, -2}, {+0, +2, -2},
{-1, +2, +1}, {-1, +1, +2}, {-1, -1, +2}, {-1, -2, +1}, {-1, -2, -1}, {-1, -1, -2}, {-1, +1, -2}, {-1, +2, -1},
{-2, +2, +0}, {-2, +1, +1}, {-2, +0, +2}, {-2, -1, +1}, {-2, -2, +0}, {-2, -1, -1}, {-2, +0, -2}, {-2, +1, -1},
};
for(int[] coords : positions)
{
TileEntity tile = centre.clone().translate(coords[0], coords[1], coords[2]).getTileEntity(controller.getWorldObj());
if(tile instanceof IReactorBlock && ((IReactorBlock)tile).isFrame())
{
reactorBlocks.add((IReactorBlock)tile);
((IReactorBlock)tile).setReactor(this);
}
else {
return false;
}
}
return true;
}
public boolean addSides(Coord4D centre)
{
int[][] positions = new int[][] {
{+2, +0, +0}, {+2, +1, +0}, {+2, +0, +1}, {+2, -1, +0}, {+2, +0, -1}, //EAST
{-2, +0, +0}, {-2, +1, +0}, {-2, +0, +1}, {-2, -1, +0}, {-2, +0, -1}, //WEST
{+0, +2, +0}, {+1, +2, +0}, {+0, +2, +1}, {-1, +2, +0}, {+0, +2, -1}, //TOP
{+0, -2, +0}, {+1, -2, +0}, {+0, -2, +1}, {-1, -2, +0}, {+0, -2, -1}, //BOTTOM
{+0, +0, +2}, {+1, +0, +2}, {+0, +1, +2}, {-1, +0, +2}, {+0, -1, +2}, //SOUTH
{+0, +0, -2}, {+1, +0, -2}, {+0, +1, -2}, {-1, +0, -2}, {+0, -1, -2}, //NORTH
};
for(int[] coords : positions)
{
TileEntity tile = centre.clone().translate(coords[0], coords[1], coords[2]).getTileEntity(controller.getWorldObj());
if(tile instanceof ILaserReceptor && !(coords[1] == 0 && (coords[0] == 0 || coords[2] == 0)))
{
return false;
}
if(tile instanceof IReactorBlock)
{
reactorBlocks.add((IReactorBlock)tile);
((IReactorBlock)tile).setReactor(this);
if(tile instanceof INeutronCapture)
{
neutronCaptors.add((INeutronCapture)tile);
}
if(tile instanceof IHeatTransfer)
{
heatTransfers.add((IHeatTransfer)tile);
}
}
else {
return false;
}
}
return true;
}
public boolean centreIsClear(Coord4D centre)
{
for(int x = -1; x <= 1; x++)
{
for(int y = -1; x <= 1; x++)
{
for(int z = -1; x <= 1; x++)
{
Block tile = centre.clone().translate(x, y, z).getBlock(controller.getWorldObj());
if(!tile.isAir(controller.getWorldObj(), x, y, z))
{
return false;
}
}
}
}
return true;
}
@Override
public boolean isFormed()
{
return formed;
}
@Override
public void setInjectionRate(int rate)
{
injectionRate = rate;
}
@Override
public int getInjectionRate()
{
return injectionRate;
}
@Override
public boolean isBurning()
{
return burning;
}
@Override
public void setBurning(boolean burn)
{
burning = burn;
}
@Override
public int getMinInjectionRate(boolean active)
{
double k = active ? caseWaterConductivity : 0;
double aMin = burnTemperature * burnRatio * plasmaCaseConductivity * (k+caseAirConductivity) / (energyPerFuel * burnRatio * (plasmaCaseConductivity+k+caseAirConductivity) - plasmaCaseConductivity * (k + caseAirConductivity));
return (int)(2 * Math.ceil(aMin/2D));
}
@Override
public double getMaxPlasmaTemperature(boolean active)
{
double k = active ? caseWaterConductivity : 0;
return injectionRate * energyPerFuel/plasmaCaseConductivity * (plasmaCaseConductivity+k+caseAirConductivity) / (k+caseAirConductivity);
}
@Override
public double getMaxCasingTemperature(boolean active)
{
double k = active ? caseWaterConductivity : 0;
return injectionRate * energyPerFuel / (k+caseAirConductivity);
}
@Override
public double getIgnitionTemperature(boolean active)
{
double k = active ? caseWaterConductivity : 0;
return burnTemperature * energyPerFuel * burnRatio * (plasmaCaseConductivity+k+caseAirConductivity) / (energyPerFuel * burnRatio * (plasmaCaseConductivity+k+caseAirConductivity) - plasmaCaseConductivity * (k + caseAirConductivity));
}
@Override
public double getPassiveGeneration(boolean active, boolean current)
{
double temperature = current ? caseTemperature : getMaxCasingTemperature(active);
return thermocoupleEfficiency * caseAirConductivity * temperature;
}
@Override
public int getSteamPerTick(boolean current)
{
double temperature = current ? caseTemperature : getMaxCasingTemperature(true);
return (int)(steamTransferEfficiency * caseWaterConductivity * temperature / enthalpyOfVaporization);
}
@Override
public double getTemp()
{
return lastCaseTemperature;
}
@Override
public double getInverseConductionCoefficient()
{
return 1 / caseAirConductivity;
}
@Override
public double getInsulationCoefficient(ForgeDirection side)
{
return 100000;
}
@Override
public void transferHeatTo(double heat)
{
heatToAbsorb += heat;
}
@Override
public double[] simulateHeat()
{
return null;
}
@Override
public double applyTemperatureChange()
{
caseTemperature += heatToAbsorb / caseHeatCapacity;
heatToAbsorb = 0;
return caseTemperature;
}
@Override
public boolean canConnectHeat(ForgeDirection side)
{
return false;
}
@Override
public IHeatTransfer getAdjacent(ForgeDirection side)
{
return null;
}
@Override
public ItemStack[] getInventory()
{
return isFormed() ? controller.inventory : null;
}
}