blob: bcf8bffb5d70411aed8f9724c4c678924bbd2265 [file] [log] [blame] [raw]
/**
* Copyright (c) 2011-2014, SpaceToad and the BuildCraft Team
* http://www.mod-buildcraft.com
*
* BuildCraft is distributed under the terms of the Minecraft Mod Public
* License 1.0, or MMPL. Please check the contents of the license located in
* http://www.mod-buildcraft.com/MMPL-1.0.txt
*/
package buildcraft.api.power;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.common.util.ForgeDirection;
import buildcraft.api.core.SafeTimeTracker;
import buildcraft.api.mj.BatteryObject;
import buildcraft.api.mj.IBatteryObject;
import buildcraft.api.mj.IBatteryProvider;
import buildcraft.api.mj.IOMode;
import buildcraft.api.mj.MjAPI;
import buildcraft.api.mj.MjBattery;
/**
* The PowerHandler is similar to FluidTank in that it holds your power and
* allows standardized interaction between machines.
* <p/>
* To receive power to your machine you needs create an instance of PowerHandler
* and implement IPowerReceptor on the TileEntity.
* <p/>
* If you plan emit power, you need only implement IPowerEmitter. You do not
* need a PowerHandler. Engines have a PowerHandler because they can also
* receive power from other Engines.
* <p/>
* See TileRefinery for a simple example of a power using machine.
*
* @see IPowerReceptor
* @see IPowerEmitter
*/
public final class PowerHandler implements IBatteryProvider {
public static enum Type {
ENGINE, GATE, MACHINE, PIPE, STORAGE;
public boolean canReceiveFromPipes() {
switch (this) {
case MACHINE:
case STORAGE:
return true;
default:
return false;
}
}
public boolean eatsEngineExcess() {
switch (this) {
case MACHINE:
case STORAGE:
return true;
default:
return false;
}
}
}
/**
* Extend this class to create custom Perdition algorithms (its not final).
* <p/>
* NOTE: It is not possible to create a Zero perdition algorithm.
*/
public static class PerditionCalculator {
public static final float DEFAULT_POWERLOSS = 1F;
public static final float MIN_POWERLOSS = 0.01F;
private final double powerLoss;
public PerditionCalculator() {
powerLoss = DEFAULT_POWERLOSS;
}
/**
* Simple constructor for simple Perdition per tick.
*
* @param powerLoss power loss per tick
*/
public PerditionCalculator(double powerLoss) {
this.powerLoss = powerLoss;
}
/**
* Apply the perdition algorithm to the current stored energy. This
* function can only be called once per tick, but it might not be called
* every tick. It is triggered by any manipulation of the stored energy.
*
* @param powerHandler the PowerHandler requesting the perdition update
* @param current the current stored energy
* @param ticksPassed ticks since the last time this function was called
*/
public double applyPerdition(PowerHandler powerHandler, double current, long ticksPassed) {
double newPower = current - powerLoss * ticksPassed;
if (newPower < 0) {
newPower = 0;
}
return newPower;
}
/**
* Taxes a flat rate on all incoming power.
* <p/>
* Defaults to 0% tax rate.
*
* @return percent of input to tax
*/
public double getTaxPercent() {
return 0;
}
}
public static final PerditionCalculator DEFAULT_PERDITION = new PerditionCalculator();
public static final double ROLLING_AVERAGE_WEIGHT = 100.0;
public static final double ROLLING_AVERAGE_NUMERATOR = ROLLING_AVERAGE_WEIGHT - 1;
public static final double ROLLING_AVERAGE_DENOMINATOR = 1.0 / ROLLING_AVERAGE_WEIGHT;
public final int[] powerSources = new int[6];
public final IPowerReceptor receptor;
private double activationEnergy;
private final SafeTimeTracker doWorkTracker = new SafeTimeTracker(1);
private final SafeTimeTracker sourcesTracker = new SafeTimeTracker(1);
private final SafeTimeTracker perditionTracker = new SafeTimeTracker(1);
private PerditionCalculator perdition;
private final PowerReceiver receiver;
private final Type type;
private IBatteryObject battery;
// Tracking
private double averageLostPower = 0;
private double averageReceivedPower = 0;
private double averageUsedPower = 0;
public PowerHandler(IPowerReceptor receptor, Type type) {
this(receptor, type, null);
}
public PowerHandler(IPowerReceptor receptor, Type type, Object battery) {
this.receptor = receptor;
this.type = type;
this.receiver = new PowerReceiver();
this.perdition = DEFAULT_PERDITION;
boolean created = false;
if (battery instanceof IBatteryObject) {
this.battery = (BatteryObject) battery;
} else if (battery != null) {
this.battery = MjAPI.createBattery(battery, MjAPI.DEFAULT_POWER_FRAMEWORK, ForgeDirection.UNKNOWN);
created = true;
} else {
this.battery = MjAPI.createBattery(new AnonymousBattery(), MjAPI.DEFAULT_POWER_FRAMEWORK, ForgeDirection.UNKNOWN);
created = true;
}
if (receptor instanceof IPowerEmitter && created) {
MjAPI.reconfigure().mode(this.battery, IOMode.Send);
}
}
public PowerReceiver getPowerReceiver() {
return receiver;
}
public double getMinEnergyReceived() {
return battery.minimumConsumption();
}
public double getMaxEnergyReceived() {
return battery.getEnergyRequested();
}
public double getMaxEnergyStored() {
return battery.maxCapacity();
}
public double getActivationEnergy() {
return activationEnergy;
}
public double getEnergyStored() {
return battery.getEnergyStored();
}
@Override
public IBatteryObject getMjBattery(String kind) {
return battery.kind().equals(kind) ? battery : null;
}
/**
* Setup your PowerHandler's settings.
*
* @param minEnergyReceived
* This is the minimum about of power that will be accepted by
* the PowerHandler. This should generally be greater than the
* activationEnergy if you plan to use the doWork() callback.
* Anything greater than 1 will prevent Redstone Engines from
* powering this Provider.
* @param maxEnergyReceived
* The maximum amount of power accepted by the PowerHandler. This
* should generally be less than 500. Too low and larger engines
* will overheat while trying to power the machine. Too high, and
* the engines will never warm up. Greater values also place
* greater strain on the power net.
* @param activationEnergy
* If the stored energy is greater than this value, the doWork()
* callback is called (once per tick).
* @param maxStoredEnergy
* The maximum amount of power this PowerHandler can store.
* Values tend to range between 100 and 5000. With 1000 and 1500
* being common.
*/
public void configure(double minEnergyReceived, double maxEnergyReceived, double activationEnergy,
double maxStoredEnergy) {
double localMaxEnergyReceived = maxEnergyReceived;
if (minEnergyReceived > localMaxEnergyReceived) {
localMaxEnergyReceived = minEnergyReceived;
}
this.activationEnergy = activationEnergy;
MjAPI.reconfigure().maxCapacity(battery, maxStoredEnergy);
MjAPI.reconfigure().maxReceivedPerCycle(battery, localMaxEnergyReceived);
MjAPI.reconfigure().minimumConsumption(battery, minEnergyReceived);
}
/**
* Allows you define perdition in terms of loss/ticks.
* <p/>
* This function is mostly for legacy implementations. See
* PerditionCalculator for more complex perdition formulas.
*
* @param powerLoss
* @param powerLossRegularity
* @see PerditionCalculator
*/
public void configurePowerPerdition(int powerLoss, int powerLossRegularity) {
if (powerLoss == 0 || powerLossRegularity == 0) {
perdition = new PerditionCalculator(0);
return;
}
perdition = new PerditionCalculator((float) powerLoss / (float) powerLossRegularity);
}
/**
* Allows you to define a new PerditionCalculator class to handler perdition
* calculations.
* <p/>
* For example if you want exponentially increasing loss based on amount
* stored.
*
* @param perdition
*/
public void setPerdition(PerditionCalculator perdition) {
if (perdition == null) {
this.perdition = DEFAULT_PERDITION;
} else {
this.perdition = perdition;
}
}
public PerditionCalculator getPerdition() {
if (perdition == null) {
return DEFAULT_PERDITION;
} else {
return perdition;
}
}
/**
* Ticks the power handler. You should call this if you can, but its not
* required.
* <p/>
* If you don't call it, the possibility exists for some weirdness with the
* perdition algorithm and work callback as its possible they will not be
* called on every tick they otherwise would be. You should be able to
* design around this though if you are aware of the limitations.
*/
public void update() {
applyPerdition();
applyWork();
validateEnergy();
}
private void applyPerdition() {
double energyStored = getEnergyStored();
if (perditionTracker.markTimeIfDelay(receptor.getWorld()) && energyStored > 0) {
double newEnergy = getPerdition().applyPerdition(this, energyStored, perditionTracker.durationOfLastDelay());
if (newEnergy != energyStored) {
battery.setEnergyStored(energyStored = newEnergy);
}
validateEnergy();
averageLostPower = (averageLostPower * ROLLING_AVERAGE_NUMERATOR + (getEnergyStored() - energyStored)) * ROLLING_AVERAGE_DENOMINATOR;
}
}
private void applyWork() {
if (getEnergyStored() >= activationEnergy) {
if (doWorkTracker.markTimeIfDelay(receptor.getWorld())) {
receptor.doWork(this);
}
}
}
private void updateSources(ForgeDirection source) {
if (sourcesTracker.markTimeIfDelay(receptor.getWorld())) {
for (int i = 0; i < 6; ++i) {
powerSources[i] -= sourcesTracker.durationOfLastDelay();
if (powerSources[i] < 0) {
powerSources[i] = 0;
}
}
}
if (source != null) {
powerSources[source.ordinal()] = 10;
}
}
/**
* Extract energy from the PowerHandler. You must call this even if doWork()
* triggers.
*
* @param min
* @param max
* @param doUse
* @return amount used
*/
public double useEnergy(double min, double max, boolean doUse) {
applyPerdition();
double result = 0;
double energyStored = getEnergyStored();
if (energyStored >= min) {
if (energyStored <= max) {
result = energyStored;
if (doUse) {
energyStored = 0;
}
} else {
result = max;
if (doUse) {
energyStored -= max;
}
}
}
if (energyStored != getEnergyStored()) {
battery.setEnergyStored(energyStored);
}
validateEnergy();
if (doUse) {
averageUsedPower = (averageUsedPower * ROLLING_AVERAGE_NUMERATOR + result) * ROLLING_AVERAGE_DENOMINATOR;
}
return result;
}
public void readFromNBT(NBTTagCompound data) {
readFromNBT(data, "powerProvider");
}
public void readFromNBT(NBTTagCompound data, String tag) {
NBTTagCompound nbt = data.getCompoundTag(tag);
battery.setEnergyStored(nbt.getDouble("energyStored"));
}
public void writeToNBT(NBTTagCompound data) {
writeToNBT(data, "powerProvider");
}
public void writeToNBT(NBTTagCompound data, String tag) {
NBTTagCompound nbt = new NBTTagCompound();
nbt.setDouble("energyStored", battery.getEnergyStored());
data.setTag(tag, nbt);
}
public final class PowerReceiver {
private PowerReceiver() {
}
public double getMinEnergyReceived() {
return PowerHandler.this.getMinEnergyReceived();
}
public double getMaxEnergyReceived() {
return PowerHandler.this.getMaxEnergyReceived();
}
public double getMaxEnergyStored() {
return PowerHandler.this.getMaxEnergyStored();
}
public double getActivationEnergy() {
return activationEnergy;
}
public double getEnergyStored() {
return PowerHandler.this.getEnergyStored();
}
public double getAveragePowerReceived() {
return averageReceivedPower;
}
public double getAveragePowerUsed() {
return averageUsedPower;
}
public double getAveragePowerLost() {
return averageLostPower;
}
public Type getType() {
return type;
}
public void update() {
PowerHandler.this.update();
}
/**
* The amount of power that this PowerHandler currently needs.
*/
public double powerRequest() {
update();
return battery.getEnergyRequested();
}
/**
* Add power to the PowerReceiver from an external source.
* <p/>
* IPowerEmitters are responsible for calling this themselves.
*
* @param quantity
* @param from
* @return the amount of power used
*/
public double receiveEnergy(Type source, final double quantity, ForgeDirection from) {
double used = quantity;
if (source == Type.ENGINE) {
if (used < getMinEnergyReceived()) {
return 0;
} else if (used > getMaxEnergyReceived()) {
used = getMaxEnergyReceived();
}
}
updateSources(from);
used -= used * getPerdition().getTaxPercent();
used = addEnergy(used);
applyWork();
if (source == Type.ENGINE && type.eatsEngineExcess()) {
used = Math.min(quantity, getMaxEnergyReceived());
}
averageReceivedPower = (averageReceivedPower * ROLLING_AVERAGE_NUMERATOR + used) * ROLLING_AVERAGE_DENOMINATOR;
return used;
}
public IBatteryObject getMjBattery() {
return battery;
}
}
/**
* @return the amount the power changed by
*/
public double addEnergy(double quantity) {
final double used = battery.addEnergy(quantity);
applyPerdition();
return used;
}
public void setEnergy(double quantity) {
battery.setEnergyStored(quantity);
validateEnergy();
}
public boolean isPowerSource(ForgeDirection from) {
return powerSources[from.ordinal()] != 0;
}
private void validateEnergy() {
double energyStored = getEnergyStored();
double maxEnergyStored = getMaxEnergyStored();
if (energyStored < 0) {
energyStored = 0;
}
if (energyStored > maxEnergyStored) {
energyStored = maxEnergyStored;
}
if (energyStored != battery.getEnergyStored()) {
battery.setEnergyStored(energyStored);
}
}
private static class AnonymousBattery {
@MjBattery
public double mjStored;
}
}