/** * 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; /** * The PowerHandler is similar to FluidTank in that it holds your power and * allows standardized interaction between machines. * * To receive power to your machine you needs create an instance of PowerHandler * and implement IPowerReceptor on the TileEntity. * * 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. * * See TileRefinery for a simple example of a power using machine. * * @see IPowerReceptor * @see IPowerEmitter */ @Deprecated public final class PowerHandler { 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). * * 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 iPowerLoss) { if (iPowerLoss < MIN_POWERLOSS) { powerLoss = iPowerLoss; } else { powerLoss = MIN_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 * @return */ public double applyPerdition(PowerHandler powerHandler, double current, long ticksPassed) { double result = current; result -= powerLoss * ticksPassed; if (result < 0) { result = 0; } return result; } /** * Taxes a flat rate on all incoming power. * * 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 minEnergyReceived; private double maxEnergyReceived; private double maxEnergyStored; private double activationEnergy; private double energyStored = 0; private final SafeTimeTracker doWorkTracker = new SafeTimeTracker(); private final SafeTimeTracker sourcesTracker = new SafeTimeTracker(); private final SafeTimeTracker perditionTracker = new SafeTimeTracker(); private PerditionCalculator perdition; private final PowerReceiver receiver; private final Type type; // Tracking private double averageLostPower = 0; private double averageReceivedPower = 0; private double averageUsedPower = 0; public PowerHandler(IPowerReceptor receptor, Type type) { this.receptor = receptor; this.type = type; this.receiver = new PowerReceiver(); this.perdition = DEFAULT_PERDITION; } public PowerReceiver getPowerReceiver() { return receiver; } public double getMinEnergyReceived() { return minEnergyReceived; } public double getMaxEnergyReceived() { return maxEnergyReceived; } public double getMaxEnergyStored() { return maxEnergyStored; } public double getActivationEnergy() { return activationEnergy; } public double getEnergyStored() { return energyStored; } /** * 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 iMaxEnergyReceived * 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 iMinEnergyReceived, double iMaxEnergyReceived, double iActivationEnergy, double iMaxStoredEnergy) { if (iMinEnergyReceived > maxEnergyReceived) { maxEnergyReceived = iMinEnergyReceived; } else { maxEnergyReceived = iMaxEnergyReceived; } minEnergyReceived = iMinEnergyReceived; maxEnergyStored = iMaxStoredEnergy; activationEnergy = iActivationEnergy; } /** * Allows you define perdition in terms of loss/ticks. * * 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. * * For example if you want exponentially increasing loss based on amount * stored. * * @param iPerdition */ public void setPerdition(PerditionCalculator iPerdition) { if (iPerdition == null) { perdition = DEFAULT_PERDITION; } else { perdition = iPerdition; } } public PerditionCalculator getPerdition() { if (perdition == null) { return DEFAULT_PERDITION; } return perdition; } /** * Ticks the power handler. You should call this if you can, but its not * required. * * 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() { if (perditionTracker.markTimeIfDelay(receptor.getWorld(), 1) && energyStored > 0) { double prev = energyStored; double newEnergy = getPerdition().applyPerdition(this, energyStored, perditionTracker.durationOfLastDelay()); if (newEnergy == 0 || newEnergy < energyStored) { energyStored = newEnergy; } else { energyStored = DEFAULT_PERDITION.applyPerdition(this, energyStored, perditionTracker.durationOfLastDelay()); } validateEnergy(); averageLostPower = (averageLostPower * ROLLING_AVERAGE_NUMERATOR + (prev - energyStored)) * ROLLING_AVERAGE_DENOMINATOR; } } private void applyWork() { if (energyStored >= activationEnergy) { if (doWorkTracker.markTimeIfDelay(receptor.getWorld(), 1)) { receptor.doWork(this); } } } private void updateSources(ForgeDirection source) { if (sourcesTracker.markTimeIfDelay(receptor.getWorld(), 1)) { 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; if (energyStored >= min) { if (energyStored <= max) { result = energyStored; if (doUse) { energyStored = 0; } } else { result = max; if (doUse) { energyStored -= max; } } } 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); energyStored = 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", energyStored); data.setTag(tag, nbt); } public final class PowerReceiver { private PowerReceiver() { } public double getMinEnergyReceived() { return minEnergyReceived; } public double getMaxEnergyReceived() { return maxEnergyReceived; } public double getMaxEnergyStored() { return maxEnergyStored; } public double getActivationEnergy() { return activationEnergy; } public double getEnergyStored() { return energyStored; } 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. * * @return */ public double powerRequest() { update(); return Math.min(maxEnergyReceived, maxEnergyStored - energyStored); } /** * Add power to the PowerReceiver from an external source. * * 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 < minEnergyReceived) { return 0; } else if (used > maxEnergyReceived) { used = maxEnergyReceived; } } updateSources(from); used -= used * getPerdition().getTaxPercent(); used = addEnergy(used); applyWork(); if (source == Type.ENGINE && type.eatsEngineExcess()) { used = Math.min(quantity, maxEnergyReceived); } averageReceivedPower = (averageReceivedPower * ROLLING_AVERAGE_NUMERATOR + used) * ROLLING_AVERAGE_DENOMINATOR; return used; } } /** * * @return the amount the power changed by */ public double addEnergy(double iQuantity) { energyStored += iQuantity; double added = iQuantity; if (energyStored > maxEnergyStored) { added -= energyStored - maxEnergyStored; energyStored = maxEnergyStored; } else if (energyStored < 0) { added -= energyStored; energyStored = 0; } applyPerdition(); return added; } public void setEnergy(double quantity) { this.energyStored = quantity; validateEnergy(); } public boolean isPowerSource(ForgeDirection from) { return powerSources[from.ordinal()] != 0; } private void validateEnergy() { if (energyStored < 0) { energyStored = 0; } if (energyStored > maxEnergyStored) { energyStored = maxEnergyStored; } } }