package org.sidoh.reactor_simulator.simulator; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import cofh.api.energy.IEnergyHandler; import cofh.lib.util.helpers.ItemHelper; import com.google.common.collect.Sets; import erogenousbeef.bigreactors.api.IHeatEntity; import erogenousbeef.bigreactors.api.registry.Reactants; import erogenousbeef.bigreactors.api.registry.ReactorInterior; import erogenousbeef.bigreactors.common.BigReactors; import erogenousbeef.bigreactors.common.data.RadiationData; import erogenousbeef.bigreactors.common.interfaces.IMultipleFluidHandler; import erogenousbeef.bigreactors.common.interfaces.IReactorFuelInfo; import erogenousbeef.bigreactors.common.multiblock.block.BlockReactorPart; import erogenousbeef.bigreactors.common.multiblock.helpers.CoolantContainer; import erogenousbeef.bigreactors.common.multiblock.helpers.FuelContainer; import erogenousbeef.bigreactors.common.multiblock.interfaces.ITickableMultiblockPart; import erogenousbeef.bigreactors.common.multiblock.tileentity.TileEntityReactorAccessPort; import erogenousbeef.bigreactors.common.multiblock.tileentity.TileEntityReactorControlRod; import erogenousbeef.bigreactors.common.multiblock.tileentity.TileEntityReactorCoolantPort; import erogenousbeef.bigreactors.common.multiblock.tileentity.TileEntityReactorFuelRod; import erogenousbeef.bigreactors.common.multiblock.tileentity.TileEntityReactorGlass; import erogenousbeef.bigreactors.common.multiblock.tileentity.TileEntityReactorPart; import erogenousbeef.bigreactors.common.multiblock.tileentity.TileEntityReactorPowerTap; import erogenousbeef.bigreactors.utils.StaticUtils; import erogenousbeef.core.common.CoordTriplet; import erogenousbeef.core.multiblock.IMultiblockPart; import erogenousbeef.core.multiblock.MultiblockControllerBase; import erogenousbeef.core.multiblock.MultiblockValidationException; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidTankInfo; import net.minecraftforge.fluids.IFluidBlock; public class MultiblockReactorSimulator implements IEnergyHandler, IReactorFuelInfo, IMultipleFluidHandler { public static final int FuelCapacityPerFuelRod = 4 * Reactants.standardSolidReactantAmount; // 4 ingots per rod public static final int FLUID_SUPERHEATED = CoolantContainer.HOT; public static final int FLUID_COOLANT = CoolantContainer.COLD; private static final float passiveCoolingPowerEfficiency = 0.5f; // 50% power penalty, so this comes out as about 1/3 a basic water-cooled reactor private static final float passiveCoolingTransferEfficiency = 0.2f; // 20% of available heat transferred per tick when passively cooled private static final float reactorHeatLossConductivity = 0.001f; // circa 1RF per tick per external surface block // Game stuff - stored protected boolean active; private float reactorHeat; private float fuelHeat; private WasteEjectionSetting wasteEjection; private float energyStored; protected FuelContainer fuelContainer; protected RadiationHelperSimulator radiationHelper; protected CoolantContainer coolantContainer; // Game stuff - derived at runtime public float fuelToReactorHeatTransferCoefficient; public float reactorToCoolantSystemHeatTransferCoefficient; public float reactorHeatLossCoefficient; protected Iterator<TileEntityReactorFuelRodSimulator> currentFuelRod; int reactorVolume; // UI stuff private float energyGeneratedLastTick; private float fuelConsumedLastTick; private CoordTriplet minCoord; public CoordTriplet maxCoord; private Set connectedParts = Sets.newHashSet(); private IFakeReactorWorld worldObj; public enum WasteEjectionSetting { kAutomatic, // Full auto, always remove waste kManual, // Manual, only on button press } public static final WasteEjectionSetting[] s_EjectionSettings = WasteEjectionSetting.values(); // Lists of connected parts private Set<TileEntityReactorPowerTap> attachedPowerTaps; private Set<ITickableMultiblockPart> attachedTickables; private Set<TileEntityReactorControlRod> attachedControlRods; // Highest internal Y-coordinate in the fuel column private Set<TileEntityReactorAccessPort> attachedAccessPorts; private Set<TileEntityReactorPart> attachedControllers; private Set<TileEntityReactorFuelRodSimulator> attachedFuelRods; private Set<TileEntityReactorCoolantPort> attachedCoolantPorts; private Set<TileEntityReactorGlass> attachedGlass; // Updates private Set<EntityPlayer> updatePlayers; private int ticksSinceLastUpdate; private static final int ticksBetweenUpdates = 3; private static final int maxEnergyStored = 10000000; public MultiblockReactorSimulator(IFakeReactorWorld world, String fuel, boolean activelyCooled) { // Game stuff active = false; reactorHeat = 0f; fuelHeat = 0f; energyStored = 0f; wasteEjection = WasteEjectionSetting.kAutomatic; // Derived stats fuelToReactorHeatTransferCoefficient = 0f; reactorToCoolantSystemHeatTransferCoefficient = 0f; reactorHeatLossCoefficient = 0f; // UI and stats energyGeneratedLastTick = 0f; fuelConsumedLastTick = 0f; attachedPowerTaps = new HashSet<TileEntityReactorPowerTap>(); attachedTickables = new HashSet<ITickableMultiblockPart>(); attachedControlRods = new HashSet<TileEntityReactorControlRod>(); attachedAccessPorts = new HashSet<TileEntityReactorAccessPort>(); attachedControllers = new HashSet<TileEntityReactorPart>(); attachedFuelRods = new HashSet<TileEntityReactorFuelRodSimulator>(); attachedCoolantPorts = new HashSet<TileEntityReactorCoolantPort>(); attachedGlass = new HashSet<TileEntityReactorGlass>(); currentFuelRod = null; updatePlayers = new HashSet<EntityPlayer>(); ticksSinceLastUpdate = 0; fuelContainer = new FuelContainer(); radiationHelper = new RadiationHelperSimulator(); coolantContainer = new CoolantContainer(); reactorVolume = 0; this.worldObj = world; this.maxCoord = world.getMaxCoord(); this.minCoord = world.getMinCoord(); if(activelyCooled) { this.coolantContainer = new CoolantContainerSimulator(); coolantContainer.setCapacity(Integer.MAX_VALUE); } for (TileEntity tileEntity : world.getParts()) { this.onBlockAdded((IMultiblockPart)tileEntity); } fuelContainer.setCapacity(Integer.MAX_VALUE); fuelContainer.addFuel(fuel, Integer.MAX_VALUE, true); //fill to the brim, next call will clamp to actual size recalculateDerivedValues(); } public void beginUpdatingPlayer(EntityPlayer playerToUpdate) { updatePlayers.add(playerToUpdate); } public void stopUpdatingPlayer(EntityPlayer playerToRemove) { updatePlayers.remove(playerToRemove); } protected void onBlockAdded(IMultiblockPart part) { connectedParts.add(part); if (part instanceof TileEntityReactorAccessPort) { attachedAccessPorts.add((TileEntityReactorAccessPort)part); } if (part instanceof TileEntityReactorControlRod) { TileEntityReactorControlRod controlRod = (TileEntityReactorControlRod)part; attachedControlRods.add(controlRod); } if (part instanceof TileEntityReactorPowerTap) { attachedPowerTaps.add((TileEntityReactorPowerTap)part); } // if (part instanceof TileEntityReactorPart) { // TileEntityReactorPart reactorPart = (TileEntityReactorPart)part; // if (BlockReactorPart.isController(reactorPart.getBlockMetadata())) { // attachedControllers.add(reactorPart); // } // } if (part instanceof ITickableMultiblockPart) { attachedTickables.add((ITickableMultiblockPart)part); } if (part instanceof TileEntityReactorFuelRodSimulator) { TileEntityReactorFuelRodSimulator fuelRod = (TileEntityReactorFuelRodSimulator)part; attachedFuelRods.add(fuelRod); // Reset iterator currentFuelRod = attachedFuelRods.iterator(); } if (part instanceof TileEntityReactorCoolantPort) { attachedCoolantPorts.add((TileEntityReactorCoolantPort)part); } if (part instanceof TileEntityReactorGlass) { attachedGlass.add((TileEntityReactorGlass)part); } } protected void onBlockRemoved(IMultiblockPart part) { if (part instanceof TileEntityReactorAccessPort) { attachedAccessPorts.remove((TileEntityReactorAccessPort)part); } if (part instanceof TileEntityReactorControlRod) { attachedControlRods.remove((TileEntityReactorControlRod)part); } if (part instanceof TileEntityReactorPowerTap) { attachedPowerTaps.remove((TileEntityReactorPowerTap)part); } if (part instanceof TileEntityReactorPart) { TileEntityReactorPart reactorPart = (TileEntityReactorPart)part; if (BlockReactorPart.isController(reactorPart.getBlockMetadata())) { attachedControllers.remove(reactorPart); } } if (part instanceof ITickableMultiblockPart) { attachedTickables.remove((ITickableMultiblockPart)part); } if (part instanceof TileEntityReactorFuelRod) { attachedFuelRods.remove(part); currentFuelRod = attachedFuelRods.iterator(); } if (part instanceof TileEntityReactorCoolantPort) { attachedCoolantPorts.remove((TileEntityReactorCoolantPort)part); } if (part instanceof TileEntityReactorGlass) { attachedGlass.remove((TileEntityReactorGlass)part); } } // Update loop. Only called when the machine is assembled. public boolean updateServer() { if (Float.isNaN(this.getReactorHeat())) { this.setReactorHeat(0.0f); } float oldHeat = this.getReactorHeat(); float oldEnergy = this.getEnergyStored(); energyGeneratedLastTick = 0f; fuelConsumedLastTick = 0f; float newHeat = 0f; // Select a control rod to radiate from. Reset the iterator and select a new Y-level if needed. if (!currentFuelRod.hasNext()) { currentFuelRod = attachedFuelRods.iterator(); } // Radiate from that control rod TileEntityReactorFuelRodSimulator source = currentFuelRod.next(); TileEntityReactorControlRod sourceControlRod = (TileEntityReactorControlRod)worldObj.getTileEntity(source.xCoord, maxCoord.y, source.zCoord); if (source != null && sourceControlRod != null) { RadiationData radData = radiationHelper.radiate(worldObj, fuelContainer, source, sourceControlRod, getFuelHeat(), getReactorHeat(), attachedControlRods.size(), this); // Assimilate results of radiation if (radData != null) { addFuelHeat(radData.getFuelHeatChange(attachedFuelRods.size())); addReactorHeat(radData.getEnvironmentHeatChange(getReactorVolume())); fuelConsumedLastTick += radData.fuelUsage; } } // Allow radiation to decay even when reactor is off. radiationHelper.tick(true); // Heat Transfer: Fuel Pool <> Reactor Environment float tempDiff = fuelHeat - reactorHeat; if (tempDiff > 0.01f) { float rfTransferred = tempDiff * fuelToReactorHeatTransferCoefficient; float fuelRf = StaticUtils.Energy.getRFFromVolumeAndTemp(attachedFuelRods.size(), fuelHeat); fuelRf -= rfTransferred; setFuelHeat(StaticUtils.Energy.getTempFromVolumeAndRF(attachedFuelRods.size(), fuelRf)); // Now see how much the reactor's temp has increased float reactorRf = StaticUtils.Energy.getRFFromVolumeAndTemp(getReactorVolume(), getReactorHeat()); reactorRf += rfTransferred; setReactorHeat(StaticUtils.Energy.getTempFromVolumeAndRF(getReactorVolume(), reactorRf)); } // If we have a temperature differential between environment and coolant system, move heat between them. tempDiff = getReactorHeat() - getCoolantTemperature(); if (tempDiff > 0.01f) { float rfTransferred = tempDiff * reactorToCoolantSystemHeatTransferCoefficient; float reactorRf = StaticUtils.Energy.getRFFromVolumeAndTemp(getReactorVolume(), getReactorHeat()); if (isPassivelyCooled()) { rfTransferred *= passiveCoolingTransferEfficiency; generateEnergy(rfTransferred * passiveCoolingPowerEfficiency); } else { rfTransferred -= coolantContainer.onAbsorbHeat(rfTransferred); energyGeneratedLastTick = coolantContainer.getFluidVaporizedLastTick(); // Piggyback so we don't have useless stuff in the update packet } reactorRf -= rfTransferred; setReactorHeat(StaticUtils.Energy.getTempFromVolumeAndRF(getReactorVolume(), reactorRf)); } // Do passive heat loss - this is always versus external environment tempDiff = getReactorHeat() - getPassiveCoolantTemperature(); if (tempDiff > 0.000001f) { float rfLost = Math.max(1f, tempDiff * reactorHeatLossCoefficient); // Lose at least 1RF/t float reactorNewRf = Math.max(0f, StaticUtils.Energy.getRFFromVolumeAndTemp(getReactorVolume(), getReactorHeat()) - rfLost); setReactorHeat(StaticUtils.Energy.getTempFromVolumeAndRF(getReactorVolume(), reactorNewRf)); } // Prevent cryogenics if (reactorHeat < 0f) { setReactorHeat(0f); } if (fuelHeat < 0f) { setFuelHeat(0f); } // Distribute available power int energyAvailable = (int)getEnergyStored(); int energyRemaining = energyAvailable; if (attachedPowerTaps.size() > 0 && energyRemaining > 0) { // First, try to distribute fairly int splitEnergy = energyRemaining / attachedPowerTaps.size(); for (TileEntityReactorPowerTap powerTap : attachedPowerTaps) { if (energyRemaining <= 0) { break; } if (powerTap == null || !powerTap.isConnected()) { continue; } energyRemaining -= splitEnergy - powerTap.onProvidePower(splitEnergy); } // Next, just hose out whatever we can, if we have any left if (energyRemaining > 0) { for (TileEntityReactorPowerTap powerTap : attachedPowerTaps) { if (energyRemaining <= 0) { break; } if (powerTap == null || !powerTap.isConnected()) { continue; } energyRemaining = powerTap.onProvidePower(energyRemaining); } } } if (energyAvailable != energyRemaining) { reduceStoredEnergy((energyAvailable - energyRemaining)); } // Send updates periodically ticksSinceLastUpdate++; if (ticksSinceLastUpdate >= ticksBetweenUpdates) { ticksSinceLastUpdate = 0; } // TODO: Overload/overheat // Update any connected tickables for (ITickableMultiblockPart tickable : attachedTickables) { if (tickable == null) { continue; } tickable.onMultiblockServerTick(); } this.fuelContainer.addFuel("yellorium", Integer.MAX_VALUE, true); if (this.fuelContainer.getWasteAmount() >= 1000) { this.fuelContainer.dumpWaste(); } return (oldHeat != this.getReactorHeat() || oldEnergy != this.getEnergyStored()); } public void setEnergyStored(float oldEnergy) { energyStored = oldEnergy; if (energyStored < 0.0 || Float.isNaN(energyStored)) { energyStored = 0.0f; } else if (energyStored > maxEnergyStored) { energyStored = maxEnergyStored; } } /** * Generate energy, internally. Will be multiplied by the BR Setting powerProductionMultiplier * @param newEnergy Base, unmultiplied energy to generate */ protected void generateEnergy(float newEnergy) { this.energyGeneratedLastTick += newEnergy * BigReactors.powerProductionMultiplier; this.addStoredEnergy(newEnergy * BigReactors.powerProductionMultiplier); } /** * Add some energy to the internal storage buffer. * Will not increase the buffer above the maximum or reduce it below 0. * @param newEnergy */ protected void addStoredEnergy(float newEnergy) { if (Float.isNaN(newEnergy)) { return; } energyStored += newEnergy; if (energyStored > maxEnergyStored) { energyStored = maxEnergyStored; } if (-0.00001f < energyStored && energyStored < 0.00001f) { // Clamp to zero energyStored = 0f; } } /** * Remove some energy from the internal storage buffer. * Will not reduce the buffer below 0. * @param energy Amount by which the buffer should be reduced. */ protected void reduceStoredEnergy(float energy) { this.addStoredEnergy(-1f * energy); } protected void addReactorHeat(float newCasingHeat) { if (Float.isNaN(newCasingHeat)) { return; } reactorHeat += newCasingHeat; // Clamp to zero to prevent floating point issues if (-0.00001f < reactorHeat && reactorHeat < 0.00001f) { reactorHeat = 0.0f; } } public float getReactorHeat() { return reactorHeat; } public void setReactorHeat(float newHeat) { if (Float.isNaN(newHeat)) { reactorHeat = 0.0f; } else { reactorHeat = newHeat; } } protected void addFuelHeat(float additionalHeat) { if (Float.isNaN(additionalHeat)) { return; } fuelHeat += additionalHeat; if (-0.00001f < fuelHeat & fuelHeat < 0.00001f) { fuelHeat = 0f; } } public float getFuelHeat() { return fuelHeat; } public void setFuelHeat(float newFuelHeat) { if (Float.isNaN(newFuelHeat)) { fuelHeat = 0f; } else { fuelHeat = newFuelHeat; } } public int getFuelRodCount() { return attachedControlRods.size(); } // Static validation helpers // Water, air, and metal blocks protected void isBlockGoodForInterior(World world, int x, int y, int z) throws MultiblockValidationException { if (world.isAirBlock(x, y, z)) { return; } // Air is OK Material material = world.getBlock(x, y, z).getMaterial(); if (material == net.minecraft.block.material.MaterialLiquid.water) { return; } Block block = world.getBlock(x, y, z); if (block == Blocks.iron_block || block == Blocks.gold_block || block == Blocks.diamond_block || block == Blocks.emerald_block) { return; } // Permit registered moderator blocks int metadata = world.getBlockMetadata(x, y, z); if (ReactorInterior.getBlockData(ItemHelper.oreProxy.getOreName(new ItemStack(block, 1, metadata))) != null) { return; } // Permit TE fluids if (block != null) { if (block instanceof IFluidBlock) { Fluid fluid = ((IFluidBlock)block).getFluid(); String fluidName = fluid.getName(); if (ReactorInterior.getFluidData(fluidName) != null) { return; } throw new MultiblockValidationException(String.format("%d, %d, %d - The fluid %s is not valid for the reactor's interior", x, y, z, fluidName)); } else { throw new MultiblockValidationException(String.format("%d, %d, %d - %s is not valid for the reactor's interior", x, y, z, block.getLocalizedName())); } } else { throw new MultiblockValidationException(String.format("%d, %d, %d - Null block found, not valid for the reactor's interior", x, y, z)); } } /** * Attempt to distribute a stack of ingots to a given access port, sensitive to the amount and type of ingots already in it. * @param port The port to which we're distributing ingots. * @param itemsToDistribute The stack of ingots to distribute. Will be modified during the operation and may be returned with stack size 0. * @param distributeToInputs Should we try to send ingots to input ports? * @return The number of waste items distributed, i.e. the differential in stack size for wasteToDistribute. */ private int tryDistributeItems(TileEntityReactorAccessPort port, ItemStack itemsToDistribute, boolean distributeToInputs) { ItemStack existingStack = port.getStackInSlot(TileEntityReactorAccessPort.SLOT_OUTLET); int initialWasteAmount = itemsToDistribute.stackSize; if (!port.isInlet() || (distributeToInputs || attachedAccessPorts.size() < 2)) { // Dump waste preferentially to outlets, unless we only have one access port if (existingStack == null) { if (itemsToDistribute.stackSize > port.getInventoryStackLimit()) { ItemStack newStack = itemsToDistribute.splitStack(port.getInventoryStackLimit()); port.setInventorySlotContents(TileEntityReactorAccessPort.SLOT_OUTLET, newStack); } else { port.setInventorySlotContents(TileEntityReactorAccessPort.SLOT_OUTLET, itemsToDistribute.copy()); itemsToDistribute.stackSize = 0; } } else if (existingStack.isItemEqual(itemsToDistribute)) { if (existingStack.stackSize + itemsToDistribute.stackSize <= existingStack.getMaxStackSize()) { existingStack.stackSize += itemsToDistribute.stackSize; itemsToDistribute.stackSize = 0; } else { int amt = existingStack.getMaxStackSize() - existingStack.stackSize; itemsToDistribute.stackSize -= existingStack.getMaxStackSize() - existingStack.stackSize; existingStack.stackSize += amt; } } port.onItemsReceived(); } return initialWasteAmount - itemsToDistribute.stackSize; } protected void onAssimilated(MultiblockControllerBase otherMachine) { this.attachedPowerTaps.clear(); this.attachedTickables.clear(); this.attachedAccessPorts.clear(); this.attachedControllers.clear(); this.attachedControlRods.clear(); currentFuelRod = null; } public float getEnergyStored() { return energyStored; } protected void onMachineAssembled() { recalculateDerivedValues(); } private void recalculateDerivedValues() { fuelContainer.setCapacity(attachedFuelRods.size() * FuelCapacityPerFuelRod); // Calculate derived stats // Calculate heat transfer based on fuel rod environment fuelToReactorHeatTransferCoefficient = 0f; for (TileEntityReactorFuelRodSimulator fuelRod : attachedFuelRods) { fuelToReactorHeatTransferCoefficient += fuelRod.getHeatTransferRate(worldObj); } // Calculate heat transfer to coolant system based on reactor interior surface area. // This is pretty simple to start with - surface area of the rectangular prism defining the interior. int xSize = maxCoord.x - minCoord.x - 1; int ySize = maxCoord.y - minCoord.y - 1; int zSize = maxCoord.z - minCoord.z - 1; int surfaceArea = 2 * (xSize * ySize + xSize * zSize + ySize * zSize); reactorToCoolantSystemHeatTransferCoefficient = IHeatEntity.conductivityIron * surfaceArea; // Calculate passive heat loss. // Get external surface area xSize += 2; ySize += 2; zSize += 2; surfaceArea = 2 * (xSize * ySize + xSize * zSize + ySize * zSize); reactorHeatLossCoefficient = reactorHeatLossConductivity * surfaceArea; calculateReactorVolume(); // if (attachedCoolantPorts.size() > 0) { // int outerVolume = StaticUtils.ExtraMath.Volume(minCoord, maxCoord) - reactorVolume; // coolantContainer.setCapacity(Math.max(0, Math.min(50000, outerVolume * 100))); // } else { // coolantContainer.setCapacity(0); // } } protected int getMaximumXSize() { return BigReactors.maximumReactorSize; } protected int getMaximumZSize() { return BigReactors.maximumReactorSize; } protected int getMaximumYSize() { return BigReactors.maximumReactorHeight; } /** * Used to update the UI */ public void setEnergyGeneratedLastTick(float energyGeneratedLastTick) { this.energyGeneratedLastTick = energyGeneratedLastTick; } /** * UI Helper */ public float getEnergyGeneratedLastTick() { return this.energyGeneratedLastTick; } /** * Used to update the UI */ public void setFuelConsumedLastTick(float fuelConsumed) { fuelConsumedLastTick = fuelConsumed; } /** * UI Helper */ public float getFuelConsumedLastTick() { return fuelConsumedLastTick; } public FuelContainer getFuelContainer() { return fuelContainer; } /** * UI Helper * @return Percentile fuel richness (fuel/fuel+waste), or 0 if all control rods are empty */ public float getFuelRichness() { int amtFuel, amtWaste; amtFuel = fuelContainer.getFuelAmount(); amtWaste = fuelContainer.getWasteAmount(); if (amtFuel + amtWaste <= 0f) { return 0f; } else { return (float)amtFuel / (float)(amtFuel + amtWaste); } } /** DO NOT USE **/ @Override public int receiveEnergy(ForgeDirection from, int maxReceive, boolean simulate) { int amtReceived = (int)Math.min(maxReceive, Math.floor(this.maxEnergyStored - this.energyStored)); if (!simulate) { this.addStoredEnergy(amtReceived); } return amtReceived; } @Override public int extractEnergy(ForgeDirection from, int maxExtract, boolean simulate) { int amtRemoved = (int)Math.min(maxExtract, this.energyStored); if (!simulate) { this.reduceStoredEnergy(amtRemoved); } return amtRemoved; } @Override public boolean canConnectEnergy(ForgeDirection from) { return false; } @Override public int getEnergyStored(ForgeDirection from) { return (int)energyStored; } @Override public int getMaxEnergyStored(ForgeDirection from) { return maxEnergyStored; } // Redstone helper public void setAllControlRodInsertionValues(int newValue) { for (TileEntityReactorControlRod cr : attachedControlRods) { if (cr != null && cr.isConnected()) { cr.setControlRodInsertion((short)newValue); } } } public void changeAllControlRodInsertionValues(short delta) { for (TileEntityReactorControlRod cr : attachedControlRods) { if (cr != null && cr.isConnected()) { cr.setControlRodInsertion((short)(cr.getControlRodInsertion() + delta)); } } } public CoordTriplet[] getControlRodLocations() { CoordTriplet[] coords = new CoordTriplet[this.attachedControlRods.size()]; int i = 0; for (TileEntityReactorControlRod cr : attachedControlRods) { coords[i++] = cr.getWorldLocation(); } return coords; } public int getFuelAmount() { return fuelContainer.getFuelAmount(); } public int getWasteAmount() { return fuelContainer.getWasteAmount(); } public String getFuelType() { return fuelContainer.getFuelType(); } public String getWasteType() { return fuelContainer.getWasteType(); } public int getEnergyStoredPercentage() { return (int)(this.energyStored / (float)this.maxEnergyStored * 100f); } @Override public int getCapacity() { return fuelContainer.getCapacity(); } public float getFuelFertility() { return radiationHelper.getFertilityModifier(); } // Coolant subsystem public CoolantContainer getCoolantContainer() { return coolantContainer; } protected float getPassiveCoolantTemperature() { return IHeatEntity.ambientHeat; } public float getCoolantTemperature() { if (isPassivelyCooled()) { return getPassiveCoolantTemperature(); } else { return coolantContainer.getCoolantTemperature(getReactorHeat()); } } public boolean isPassivelyCooled() { if (coolantContainer == null || coolantContainer.getCapacity() <= 0) { return true; } else { return false; } } protected int getReactorVolume() { return reactorVolume; } protected void calculateReactorVolume() { CoordTriplet minInteriorCoord = minCoord.copy(); minInteriorCoord.x += 1; minInteriorCoord.y += 1; minInteriorCoord.z += 1; CoordTriplet maxInteriorCoord = maxCoord.copy(); maxInteriorCoord.x -= 1; maxInteriorCoord.y -= 1; maxInteriorCoord.z -= 1; reactorVolume = StaticUtils.ExtraMath.Volume(minInteriorCoord, maxInteriorCoord); } private static final FluidTankInfo[] emptyTankInfo = new FluidTankInfo[0]; @Override public FluidTankInfo[] getTankInfo() { if (isPassivelyCooled()) { return emptyTankInfo; } return coolantContainer.getTankInfo(-1); } public String getDebugInfo() { StringBuilder sb = new StringBuilder(); sb.append("Attached Blocks: ").append(Integer.toString(connectedParts.size())).append("\n"); sb.append("\nStored Energy: ").append(Float.toString(getEnergyStored())); sb.append("\nCasing Heat: ").append(Float.toString(getReactorHeat())); sb.append("\nFuel Heat: ").append(Float.toString(getFuelHeat())); sb.append("\n\nReactant Tanks:\n"); sb.append(fuelContainer.getDebugInfo()); sb.append("\n\nActively Cooled: ").append(Boolean.toString(!isPassivelyCooled())); if (!isPassivelyCooled()) { sb.append("\n\nCoolant Tanks:\n"); sb.append(coolantContainer.getDebugInfo()); } return sb.toString(); } }