Package erogenousbeef.bigreactors.common.multiblock

Source Code of erogenousbeef.bigreactors.common.multiblock.MultiblockReactor

package erogenousbeef.bigreactors.common.multiblock;

import io.netty.buffer.ByteBuf;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
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.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTankInfo;
import net.minecraftforge.fluids.IFluidBlock;
import cofh.api.energy.IEnergyHandler;
import cofh.lib.util.helpers.ItemHelper;
import cpw.mods.fml.common.network.simpleimpl.IMessage;
import erogenousbeef.bigreactors.api.IHeatEntity;
import erogenousbeef.bigreactors.api.registry.Reactants;
import erogenousbeef.bigreactors.api.registry.ReactorInterior;
import erogenousbeef.bigreactors.common.BRLog;
import erogenousbeef.bigreactors.common.BigReactors;
import erogenousbeef.bigreactors.common.data.RadiationData;
import erogenousbeef.bigreactors.common.data.StandardReactants;
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.helpers.RadiationHelper;
import erogenousbeef.bigreactors.common.multiblock.interfaces.IActivateable;
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.net.CommonPacketHandler;
import erogenousbeef.bigreactors.net.message.multiblock.ReactorUpdateMessage;
import erogenousbeef.bigreactors.net.message.multiblock.ReactorUpdateWasteEjectionMessage;
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 erogenousbeef.core.multiblock.rectangular.RectangularMultiblockControllerBase;

public class MultiblockReactor extends RectangularMultiblockControllerBase implements IEnergyHandler, IReactorFuelInfo, IMultipleFluidHandler, IActivateable {
  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 RadiationHelper radiationHelper;
  protected CoolantContainer coolantContainer;

  // Game stuff - derived at runtime
  protected float fuelToReactorHeatTransferCoefficient;
  protected float reactorToCoolantSystemHeatTransferCoefficient;
  protected float reactorHeatLossCoefficient;
 
  protected Iterator<TileEntityReactorFuelRod> currentFuelRod;
  int reactorVolume;

  // UI stuff
  private float energyGeneratedLastTick;
  private float fuelConsumedLastTick;
 
  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<TileEntityReactorFuelRod> 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 MultiblockReactor(World world) {
    super(world);

    // 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<TileEntityReactorFuelRod>();
    attachedCoolantPorts = new HashSet<TileEntityReactorCoolantPort>();
    attachedGlass = new HashSet<TileEntityReactorGlass>();
   
    currentFuelRod = null;

    updatePlayers = new HashSet<EntityPlayer>();
   
    ticksSinceLastUpdate = 0;
    fuelContainer = new FuelContainer();
    radiationHelper = new RadiationHelper();
    coolantContainer = new CoolantContainer();
   
    reactorVolume = 0;
  }
 
  public void beginUpdatingPlayer(EntityPlayer playerToUpdate) {
    updatePlayers.add(playerToUpdate);
    sendIndividualUpdate(playerToUpdate);
  }
 
  public void stopUpdatingPlayer(EntityPlayer playerToRemove) {
    updatePlayers.remove(playerToRemove);
  }
 
  @Override
  protected void onBlockAdded(IMultiblockPart 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 TileEntityReactorFuelRod) {
      TileEntityReactorFuelRod fuelRod = (TileEntityReactorFuelRod)part;
      attachedFuelRods.add(fuelRod);

      // Reset iterator
      currentFuelRod = attachedFuelRods.iterator();

      if(worldObj.isRemote) {
        worldObj.markBlockForUpdate(fuelRod.xCoord, fuelRod.yCoord, fuelRod.zCoord);
      }
    }
   
    if(part instanceof TileEntityReactorCoolantPort) {
      attachedCoolantPorts.add((TileEntityReactorCoolantPort) part);
    }
   
    if(part instanceof TileEntityReactorGlass) {
      attachedGlass.add((TileEntityReactorGlass)part);
    }
  }
 
  @Override
  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);
    }
  }
 
  @Override
  protected void isMachineWhole() throws MultiblockValidationException {
    // Ensure that there is at least one controller and control rod attached.
    if(attachedControlRods.size() < 1) {
      throw new MultiblockValidationException("Not enough control rods. Reactors require at least 1.");
    }
   
    if(attachedControllers.size() < 1) {
      throw new MultiblockValidationException("Not enough controllers. Reactors require at least 1.");
    }
   
    super.isMachineWhole();
  }

  @Override
  public void updateClient() {}
 
  // Update loop. Only called when the machine is assembled.
  @Override
  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;
   
    if(getActive()) {
      // 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
      TileEntityReactorFuelRod source  = currentFuelRod.next();
      TileEntityReactorControlRod sourceControlRod = (TileEntityReactorControlRod)worldObj.getTileEntity(source.xCoord, getMaximumCoord().y, source.zCoord);
      if(sourceControlRod != null)
      {
        RadiationData radData = radiationHelper.radiate(worldObj, fuelContainer, source, sourceControlRod, getFuelHeat(), getReactorHeat(), attachedControlRods.size());

        // 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(getActive());

    // If we can, poop out waste and inject new fuel.
    if(wasteEjection == WasteEjectionSetting.kAutomatic) {
      ejectWaste(false, null);
    }
   
    refuel();

    // 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.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.isConnected()) { continue; }

          energyRemaining = powerTap.onProvidePower(energyRemaining);
        }
      }
    }
   
    if(energyAvailable != energyRemaining) {
      reduceStoredEnergy((energyAvailable - energyRemaining));
    }

    // Send updates periodically
    ticksSinceLastUpdate++;
    if(ticksSinceLastUpdate >= ticksBetweenUpdates) {
      ticksSinceLastUpdate = 0;
      sendTickUpdate();
    }
   
    // TODO: Overload/overheat

    // Update any connected tickables
    for(ITickableMultiblockPart tickable : attachedTickables) {
      tickable.onMultiblockServerTick();
    }

    if(attachedGlass.size() > 0 && fuelContainer.shouldUpdate()) {
      markReferenceCoordForUpdate();
    }
   
    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);
  }
 
  public void setActive(boolean act) {
    if(act == this.active) { return; }
    this.active = act;
   
    for(IMultiblockPart part : connectedParts) {
      if(this.active) { part.onMachineActivated(); }
      else { part.onMachineDeactivated(); }
    }
   
    if(worldObj.isRemote) {
      // Force controllers to re-render on client
      for(IMultiblockPart part : attachedControllers) {
        worldObj.markBlockForUpdate(part.xCoord, part.yCoord, part.zCoord);
      }
    }
    else {
      this.markReferenceCoordForUpdate();
    }
  }

  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
  @Override
  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));
    }
  }
 
  @Override
  public void writeToNBT(NBTTagCompound data) {
    data.setBoolean("reactorActive", this.active);
    data.setFloat("heat", this.reactorHeat);
    data.setFloat("fuelHeat", fuelHeat);
    data.setFloat("storedEnergy", this.energyStored);
    data.setInteger("wasteEjection2", this.wasteEjection.ordinal());
    data.setTag("fuelContainer", fuelContainer.writeToNBT(new NBTTagCompound()));
    data.setTag("radiation", radiationHelper.writeToNBT(new NBTTagCompound()));
    data.setTag("coolantContainer", coolantContainer.writeToNBT(new NBTTagCompound()));
  }

  @Override
  public void readFromNBT(NBTTagCompound data) {
    if(data.hasKey("reactorActive")) {
      setActive(data.getBoolean("reactorActive"));
    }
   
    if(data.hasKey("heat")) {
      setReactorHeat(Math.max(getReactorHeat(), data.getFloat("heat")));
    }
   
    if(data.hasKey("storedEnergy")) {
      setEnergyStored(Math.max(getEnergyStored(), data.getFloat("storedEnergy")));
    }
   
    if(data.hasKey("wasteEjection")) {
      this.wasteEjection = s_EjectionSettings[data.getInteger("wasteEjection")];
    }
   
    if(data.hasKey("fuelHeat")) {
      setFuelHeat(data.getFloat("fuelHeat"));
    }
   
    if(data.hasKey("fuelContainer")) {
      fuelContainer.readFromNBT(data.getCompoundTag("fuelContainer"));
    }
   
    if(data.hasKey("radiation")) {
      radiationHelper.readFromNBT(data.getCompoundTag("radiation"));
    }
   
    if(data.hasKey("coolantContainer")) {
      coolantContainer.readFromNBT(data.getCompoundTag("coolantContainer"));
    }
  }

  @Override
  protected int getMinimumNumberOfBlocksForAssembledMachine() {
    // Hollow cube.
    return 26;
  }

  @Override
  public void formatDescriptionPacket(NBTTagCompound data) {
    writeToNBT(data);
  }

  @Override
  public void decodeDescriptionPacket(NBTTagCompound data) {
    readFromNBT(data);
    onFuelStatusChanged();
  }

  // Network & Storage methods
  /*
   * Serialize a reactor into a given Byte buffer
   * @param buf The byte buffer to serialize into
   */
  public void serialize(ByteBuf buf) {
    int fuelTypeID, wasteTypeID, coolantTypeID, vaporTypeID;

    // Marshal fluid types into integers
    {
      Fluid coolantType, vaporType;
      coolantType = coolantContainer.getCoolantType();
      vaporType = coolantContainer.getVaporType();     
      coolantTypeID = coolantType == null ? -1 : coolantType.getID();
      vaporTypeID = vaporType == null ? -1 : vaporType.getID();
    }

    // Basic data
    buf.writeBoolean(active);
    buf.writeFloat(reactorHeat);
    buf.writeFloat(fuelHeat);
    buf.writeFloat(energyStored);
    buf.writeFloat(radiationHelper.getFertility());
   
    // Statistics
    buf.writeFloat(energyGeneratedLastTick);
    buf.writeFloat(fuelConsumedLastTick);
   
    // Coolant data
    buf.writeInt(coolantTypeID);
    buf.writeInt(coolantContainer.getCoolantAmount());
    buf.writeInt(vaporTypeID);
    buf.writeInt(coolantContainer.getVaporAmount());
   
    fuelContainer.serialize(buf);
  }

  /*
   * Deserialize a reactor's data from a given Byte buffer
   * @param buf The byte buffer containing reactor data
   */
  public void deserialize(ByteBuf buf) {
    // Basic data
    setActive(buf.readBoolean());
    setReactorHeat(buf.readFloat());
    setFuelHeat(buf.readFloat());
    setEnergyStored(buf.readFloat());
    radiationHelper.setFertility(buf.readFloat());
   
    // Statistics
    setEnergyGeneratedLastTick(buf.readFloat());
    setFuelConsumedLastTick(buf.readFloat());
   

    // Coolant data
    int coolantTypeID = buf.readInt();
    int coolantAmt = buf.readInt();
    int vaporTypeID = buf.readInt();
    int vaporAmt = buf.readInt();

    // Fuel & waste data
    fuelContainer.deserialize(buf);

    if(coolantTypeID == -1) {
      coolantContainer.emptyCoolant();
    }
    else {
      coolantContainer.setCoolant(new FluidStack(FluidRegistry.getFluid(coolantTypeID), coolantAmt));
    }
   
    if(vaporTypeID == -1) {
      coolantContainer.emptyVapor();
    }
    else {
      coolantContainer.setVapor(new FluidStack(FluidRegistry.getFluid(vaporTypeID), vaporAmt));
    }
   
  }
 
  protected IMessage getUpdatePacket() {
        return new ReactorUpdateMessage(this);
  }
 
  /**
   * Sends a full state update to a player.
   */
  protected void sendIndividualUpdate(EntityPlayer player) {
    if(this.worldObj.isRemote) { return; }

        CommonPacketHandler.INSTANCE.sendTo(getUpdatePacket(), (EntityPlayerMP)player);
  }
 
  /**
   * Send an update to any clients with GUIs open
   */
  protected void sendTickUpdate() {
    if(this.worldObj.isRemote) { return; }
    if(this.updatePlayers.size() <= 0) { return; }

    for(EntityPlayer player : updatePlayers) {
            CommonPacketHandler.INSTANCE.sendTo(getUpdatePacket(), (EntityPlayerMP)player);
    }
  }
 
  /**
   * 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;
  }

  @Override
  protected void onAssimilated(MultiblockControllerBase otherMachine) {
    this.attachedPowerTaps.clear();
    this.attachedTickables.clear();
    this.attachedAccessPorts.clear();
    this.attachedControllers.clear();
    this.attachedControlRods.clear();
    currentFuelRod = null;
  }
 
  @Override
  protected void onAssimilate(MultiblockControllerBase otherMachine) {
    if(!(otherMachine instanceof MultiblockReactor)) {
      BRLog.warning("[%s] Reactor @ %s is attempting to assimilate a non-Reactor machine! That machine's data will be lost!", worldObj.isRemote?"CLIENT":"SERVER", getReferenceCoord());
      return;
    }
   
    MultiblockReactor otherReactor = (MultiblockReactor)otherMachine;

    if(otherReactor.reactorHeat > this.reactorHeat) { setReactorHeat(otherReactor.reactorHeat); }
    if(otherReactor.fuelHeat > this.fuelHeat) { setFuelHeat(otherReactor.fuelHeat); }

    if(otherReactor.getEnergyStored() > this.getEnergyStored()) { this.setEnergyStored(otherReactor.getEnergyStored()); }

    fuelContainer.merge(otherReactor.fuelContainer);
    radiationHelper.merge(otherReactor.radiationHelper);
    coolantContainer.merge(otherReactor.coolantContainer);
  }
 
  @Override
  public void onAttachedPartWithMultiblockData(IMultiblockPart part, NBTTagCompound data) {
    this.readFromNBT(data);
  }
 
  public float getEnergyStored() {
    return energyStored;
  }

  /**
   * Directly set the waste ejection setting. Will dispatch network updates
   * from server to interested clients.
   * @param newSetting The new waste ejection setting.
   */
  public void setWasteEjection(WasteEjectionSetting newSetting) {
    if(this.wasteEjection != newSetting) {
      this.wasteEjection = newSetting;
     
      if(!this.worldObj.isRemote) {
        if(this.updatePlayers.size() > 0) {
          for(EntityPlayer player : updatePlayers) {
                        CommonPacketHandler.INSTANCE.sendTo(new ReactorUpdateWasteEjectionMessage(this), (EntityPlayerMP)player);
          }
        }
      }
    }
  }
 
  public WasteEjectionSetting getWasteEjection() {
    return this.wasteEjection;
  }

  protected void refuel() {
    // For now, we only need to check fuel ports when we have more space than can accomodate 1 ingot
    if(fuelContainer.getRemainingSpace() < Reactants.standardSolidReactantAmount) {
      return;
    }
   
    int amtAdded = 0;
   
    // Loop: Consume input reactants from all ports
    for(TileEntityReactorAccessPort port : attachedAccessPorts)
    {
      if(fuelContainer.getRemainingSpace() <= 0) { break; }

      if(!port.isConnected())  { continue; }

      // See what type of reactant the port contains; if none, skip it.
      String portReactantType = port.getInputReactantType();
      int portReactantAmount = port.getInputReactantAmount();
      if(portReactantType == null || portReactantAmount <= 0) { continue; }
     
      if(!Reactants.isFuel(portReactantType)) { continue; } // Skip nonfuels

      // HACK; TEMPORARY
      // Alias blutonium to yellorium temporarily, until mixed fuels are implemented
      if(portReactantType.equals(StandardReactants.blutonium)) {
        portReactantType = StandardReactants.yellorium;
      }
     
      // How much fuel can we actually add from this type of reactant?
      int amountToAdd = fuelContainer.addFuel(portReactantType, portReactantAmount, false);
      if(amountToAdd <= 0) { continue; }
     
      int portCanAdd = port.consumeReactantItem(amountToAdd);
      if(portCanAdd <= 0) { continue; }
     
      amtAdded = fuelContainer.addFuel(portReactantType, portReactantAmount, true);
    }
   
    if(amtAdded > 0) {
      markReferenceCoordForUpdate();
      markReferenceCoordDirty();
    }
  }
 
  /**
   * Attempt to eject waste contained in the reactor
   * @param dumpAll If true, any waste remaining after ejection will be discarded.
   * @param destination If set, waste will only be ejected to ports with coordinates matching this one.
   */
  public void ejectWaste(boolean dumpAll, CoordTriplet destination)
  {
    // For now, we can optimize by only running this when we have enough waste to product an ingot
    int amtEjected = 0;

    String wasteReactantType = fuelContainer.getWasteType();
    if(wasteReactantType == null) {
      return;
    }

    int minimumReactantAmount = Reactants.getMinimumReactantToProduceSolid(wasteReactantType);
    if(fuelContainer.getWasteAmount() >= minimumReactantAmount) {

      for(TileEntityReactorAccessPort port : attachedAccessPorts) {
        if(fuelContainer.getWasteAmount() < minimumReactantAmount) {
          continue;
        }
       
        if(!port.isConnected()) { continue; }
        if(destination != null && !destination.equals(port.xCoord, port.yCoord, port.zCoord)) {
          continue;
        }
       
        // First time through, we eject only to outlet ports
        if(destination == null && !port.isInlet()) {
          int reactantEjected = port.emitReactant(wasteReactantType, fuelContainer.getWasteAmount());
          fuelContainer.dumpWaste(reactantEjected);
          amtEjected += reactantEjected;
        }
      }
     
      if(destination == null && fuelContainer.getWasteAmount() > minimumReactantAmount) {
        // Loop a second time when destination is null and we still have waste
        for(TileEntityReactorAccessPort port : attachedAccessPorts) {
          if(fuelContainer.getWasteAmount() < minimumReactantAmount) {
            continue;
          }
         
          if(!port.isConnected()) { continue; }
          int reactantEjected = port.emitReactant(wasteReactantType, fuelContainer.getWasteAmount());
          fuelContainer.dumpWaste(reactantEjected);
          amtEjected += reactantEjected;
        }
      }
    }

    if(dumpAll)
    {
      amtEjected += fuelContainer.getWasteAmount();
      fuelContainer.setWaste(null);
    }

    if(amtEjected > 0) {
      markReferenceCoordForUpdate();
      markReferenceCoordDirty();
    }
  }
 
  /**
   * Eject fuel contained in the reactor.
   * @param dumpAll If true, any remaining fuel will simply be lost.
   * @param destination If not null, then fuel will only be distributed to a port matching these coordinates.
   */
  public void ejectFuel(boolean dumpAll, CoordTriplet destination) {
    // For now, we can optimize by only running this when we have enough waste to product an ingot
    int amtEjected = 0;

    String fuelReactantType = fuelContainer.getFuelType();
    if(fuelReactantType == null) {
      return;
    }

    int minimumReactantAmount = Reactants.getMinimumReactantToProduceSolid(fuelReactantType);
    if(fuelContainer.getFuelAmount() >= minimumReactantAmount) {

      for(TileEntityReactorAccessPort port : attachedAccessPorts) {
        if(fuelContainer.getFuelAmount() < minimumReactantAmount) {
          continue;
        }
       
        if(!port.isConnected()) { continue; }
        if(destination != null && !destination.equals(port.xCoord, port.yCoord, port.zCoord)) {
          continue;
        }
       
        int reactantEjected = port.emitReactant(fuelReactantType, fuelContainer.getFuelAmount());
        fuelContainer.dumpFuel(reactantEjected);
        amtEjected += reactantEjected;
      }
    }

    if(dumpAll)
    {
      amtEjected += fuelContainer.getFuelAmount();
      fuelContainer.setFuel(null);
    }

    if(amtEjected > 0) {
      markReferenceCoordForUpdate();
      markReferenceCoordDirty();
    }
  }

  @Override
  protected void onMachineAssembled() {
    recalculateDerivedValues();
  }

  @Override
  protected void onMachineRestored() {
    recalculateDerivedValues();
  }

  @Override
  protected void onMachinePaused() {
  }

  @Override
  protected void onMachineDisassembled() {
    this.active = false;
  }

  private void recalculateDerivedValues() {
    // Recalculate size of fuel/waste tank via fuel rods
    CoordTriplet minCoord, maxCoord;
    minCoord = getMinimumCoord();
    maxCoord = getMaximumCoord();
   
    fuelContainer.setCapacity(attachedFuelRods.size() * FuelCapacityPerFuelRod);

    // Calculate derived stats
   
    // Calculate heat transfer based on fuel rod environment
    fuelToReactorHeatTransferCoefficient = 0f;
    for(TileEntityReactorFuelRod fuelRod : attachedFuelRods) {
      fuelToReactorHeatTransferCoefficient += fuelRod.getHeatTransferRate();
    }

    // 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;
   
    if(worldObj.isRemote) {
      // Make sure our fuel rods re-render
      this.onFuelStatusChanged();
    }
    else {
      // Force an update of the client's multiblock information
      markReferenceCoordForUpdate();
    }
   
    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);
    }
  }

  @Override
  protected int getMaximumXSize() {
    return BigReactors.maximumReactorSize;
  }

  @Override
  protected int getMaximumZSize() {
    return BigReactors.maximumReactorSize;
  }

  @Override
  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;
  }

  /**
   * 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) {
    if(this.assemblyState != AssemblyState.Assembled) { return; }
   
    for(TileEntityReactorControlRod cr : attachedControlRods) {
      if(cr.isConnected()) {
        cr.setControlRodInsertion((short)newValue);
      }
    }
  }
 
  public void changeAllControlRodInsertionValues(short delta) {
    if(this.assemblyState != AssemblyState.Assembled) { return; }
   
    for(TileEntityReactorControlRod cr : attachedControlRods) {
      if(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() {
    if(worldObj.isRemote && assemblyState != AssemblyState.Assembled) {
      // Estimate capacity
      return attachedFuelRods.size() * FuelCapacityPerFuelRod;
    }

    return fuelContainer.getCapacity();
  }
 
  public float getFuelFertility() {
    return radiationHelper.getFertilityModifier();
  }
 
  // Coolant subsystem
  public CoolantContainer getCoolantContainer() {
    return coolantContainer;
  }
 
  protected float getPassiveCoolantTemperature() {
    return IHeatEntity.ambientHeat;
  }

  protected 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 = getMinimumCoord();
    minInteriorCoord.x += 1;
    minInteriorCoord.y += 1;
    minInteriorCoord.z += 1;
   
    CoordTriplet maxInteriorCoord = getMaximumCoord();
    maxInteriorCoord.x -= 1;
    maxInteriorCoord.y -= 1;
    maxInteriorCoord.z -= 1;
   
    reactorVolume = StaticUtils.ExtraMath.Volume(minInteriorCoord, maxInteriorCoord);
  }

  // Client-only
  protected void onFuelStatusChanged() {
    if(worldObj.isRemote) {
      // On the client, re-render all the fuel rod blocks when the fuel status changes
      for(TileEntityReactorFuelRod fuelRod : attachedFuelRods) {
        worldObj.markBlockForUpdate(fuelRod.xCoord, fuelRod.yCoord, fuelRod.zCoord);
      }
    }
  }

  private static final FluidTankInfo[] emptyTankInfo = new FluidTankInfo[0];
 
  @Override
  public FluidTankInfo[] getTankInfo() {
    if(isPassivelyCooled()) { return emptyTankInfo; }
   
    return coolantContainer.getTankInfo(-1);
  }
 
  protected void markReferenceCoordForUpdate() {
    CoordTriplet rc = getReferenceCoord();
    if(worldObj != null && rc != null) {
      worldObj.markBlockForUpdate(rc.x, rc.y, rc.z);
    }
  }
 
  protected void markReferenceCoordDirty() {
    if(worldObj == null || worldObj.isRemote) { return; }

    CoordTriplet referenceCoord = getReferenceCoord();
    if(referenceCoord == null) { return; }

    TileEntity saveTe = worldObj.getTileEntity(referenceCoord.x, referenceCoord.y, referenceCoord.z);
    worldObj.markTileEntityChunkModified(referenceCoord.x, referenceCoord.y, referenceCoord.z, saveTe);
  }

  @Override
  public boolean getActive() {
    return this.active;
  }
 
  public String getDebugInfo() {
    StringBuilder sb = new StringBuilder();
    sb.append("Assembled: ").append(Boolean.toString(isAssembled())).append("\n");
    sb.append("Attached Blocks: ").append(Integer.toString(connectedParts.size())).append("\n");
    if(getLastValidationException() != null) {
      sb.append("Validation Exception:\n").append(getLastValidationException().getMessage()).append("\n");
    }
   
    if(isAssembled()) {
      sb.append("\nActive: ").append(Boolean.toString(getActive()));
      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();
  }
}
TOP

Related Classes of erogenousbeef.bigreactors.common.multiblock.MultiblockReactor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.