/**
* Copyright (c) 2011-2015, SpaceToad and the BuildCraft Team
* http://www.mod-buildcraft.com
* <p/>
* 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.transport;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.common.util.ForgeDirection;
import cofh.api.energy.IEnergyConnection;
import cofh.api.energy.IEnergyHandler;
import cofh.api.energy.IEnergyReceiver;
import buildcraft.BuildCraftCore;
import buildcraft.BuildCraftTransport;
import buildcraft.api.core.SafeTimeTracker;
import buildcraft.api.power.IEngine;
import buildcraft.api.power.IRedstoneEngine;
import buildcraft.api.tiles.IDebuggable;
import buildcraft.api.transport.IPipeTile;
import buildcraft.core.CompatHooks;
import buildcraft.core.DefaultProps;
import buildcraft.core.lib.block.TileBuildCraft;
import buildcraft.core.lib.utils.AverageInt;
import buildcraft.transport.network.PacketPowerUpdate;
import buildcraft.transport.pipes.PipePowerCobblestone;
import buildcraft.transport.pipes.PipePowerDiamond;
import buildcraft.transport.pipes.PipePowerEmerald;
import buildcraft.transport.pipes.PipePowerGold;
import buildcraft.transport.pipes.PipePowerIron;
import buildcraft.transport.pipes.PipePowerQuartz;
import buildcraft.transport.pipes.PipePowerSandstone;
import buildcraft.transport.pipes.PipePowerStone;
import buildcraft.transport.pipes.PipePowerWood;
public class PipeTransportPower extends PipeTransport implements IDebuggable {
public static final Map<Class<? extends Pipe<?>>, Integer> powerCapacities = new HashMap<Class<? extends Pipe<?>>, Integer>();
public static final Map<Class<? extends Pipe<?>>, Float> powerResistances = new HashMap<Class<? extends Pipe<?>>, Float>();
private static final int OVERLOAD_TICKS = 60;
public short[] displayPower = new short[6];
public int[] nextPowerQuery = new int[6];
public double[] internalNextPower = new double[6];
public int overload;
public int maxPower = 80;
public float powerResistance;
public int[] dbgEnergyInput = new int[6];
public int[] dbgEnergyOutput = new int[6];
public int[] dbgEnergyOffered = new int[6];
private final AverageInt[] powerAverage = new AverageInt[6];
private final TileEntity[] tiles = new TileEntity[6];
private final Object[] providers = new Object[6];
private boolean needsInit = true;
private int[] powerQuery = new int[6];
private long currentDate;
private double[] internalPower = new double[6];
private SafeTimeTracker tracker = new SafeTimeTracker(2 * BuildCraftCore.updateFactor);
public PipeTransportPower() {
for (int i = 0; i < 6; ++i) {
powerQuery[i] = 0;
powerAverage[i] = new AverageInt(10);
}
}
@Override
public IPipeTile.PipeType getPipeType() {
return IPipeTile.PipeType.POWER;
}
public void initFromPipe(Class<? extends Pipe<?>> pipeClass) {
if (BuildCraftTransport.usePipeLoss) {
maxPower = 10240;
powerResistance = powerResistances.get(pipeClass);
} else {
maxPower = powerCapacities.get(pipeClass);
}
}
@Override
public boolean canPipeConnect(TileEntity tile, ForgeDirection side) {
if (tile instanceof IPipeTile) {
Pipe<?> pipe2 = (Pipe<?>) ((IPipeTile) tile).getPipe();
if (BlockGenericPipe.isValid(pipe2) && !(pipe2.transport instanceof PipeTransportPower)) {
return false;
}
return true;
}
if (container.pipe instanceof PipePowerWood) {
return isPowerSource(tile, side);
} else {
if (tile instanceof IEngine) {
// Disregard engines for this.
return false;
}
Object provider = CompatHooks.INSTANCE.getEnergyProvider(tile);
if (provider instanceof IEnergyHandler || provider instanceof IEnergyReceiver) {
IEnergyConnection handler = (IEnergyConnection) provider;
if (handler.canConnectEnergy(side.getOpposite())) {
return true;
}
}
}
return false;
}
public boolean isPowerSource(TileEntity tile, ForgeDirection side) {
if (tile instanceof TileBuildCraft && !(tile instanceof IEngine)) {
// Disregard non-engine BC tiles.
// While this, of course, does nothing to work with other mods,
// it at least makes it work nicely with BC's built-in blocks while
// the new RF api isn't out.
return false;
}
if (tile instanceof IRedstoneEngine) {
// Do not render wooden pipe connections to match the look of transport/fluid pipes
// for kinesis.
return false;
}
return tile instanceof IEnergyConnection && ((IEnergyConnection) tile).canConnectEnergy(side.getOpposite());
}
@Override
public void onNeighborChange(ForgeDirection side) {
super.onNeighborChange(side);
updateTile(side);
}
private void updateTile(ForgeDirection side) {
int o = side.ordinal();
TileEntity tile = container.getTile(side);
if (tile != null && container.isPipeConnected(side)) {
tiles[o] = tile;
} else {
tiles[o] = null;
internalPower[o] = 0;
internalNextPower[o] = 0;
powerAverage[o].clear();
}
providers[o] = getEnergyProvider(o);
}
private void init() {
if (needsInit) {
needsInit = false;
for (ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
updateTile(side);
}
}
}
private Object getEnergyProvider(int side) {
ForgeDirection fs = ForgeDirection.getOrientation(side);
if (container.hasPipePluggable(fs)) {
Object pp = container.getPipePluggable(fs);
if (pp instanceof IEnergyReceiver) {
return pp;
}
}
return CompatHooks.INSTANCE.getEnergyProvider(tiles[side]);
}
@Override
public void updateEntity() {
if (container.getWorldObj().isRemote) {
return;
}
step();
init();
for (ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
if (tiles[side.ordinal()] != null && tiles[side.ordinal()].isInvalid()) {
updateTile(side);
}
}
for (int i = 0; i < 6; ++i) {
if (internalPower[i] > 0) {
int totalPowerQuery = 0;
for (int j = 0; j < 6; ++j) {
if (j != i && powerQuery[j] > 0) {
Object ep = providers[j];
if (ep instanceof IPipeTile || ep instanceof IEnergyReceiver || ep instanceof IEnergyHandler) {
totalPowerQuery += powerQuery[j];
}
}
}
if (totalPowerQuery > 0) {
int unusedPowerQuery = totalPowerQuery;
for (int j = 0; j < 6; ++j) {
if (j != i && powerQuery[j] > 0) {
Object ep = providers[j];
double watts = Math.min(internalPower[i] * powerQuery[j] / unusedPowerQuery, internalPower[i]);
unusedPowerQuery -= powerQuery[j];
if (ep instanceof IPipeTile && ((IPipeTile) ep).getPipeType() == IPipeTile.PipeType.POWER) {
Pipe<?> nearbyPipe = (Pipe<?>) ((IPipeTile) ep).getPipe();
PipeTransportPower nearbyTransport = (PipeTransportPower) nearbyPipe.transport;
watts = nearbyTransport.receiveEnergy(
ForgeDirection.VALID_DIRECTIONS[j].getOpposite(),
watts);
internalPower[i] -= watts;
dbgEnergyOutput[j] += watts;
powerAverage[j].push((int) Math.ceil(watts));
powerAverage[i].push((int) Math.ceil(watts));
} else {
int iWatts = (int) watts;
if (ep instanceof IEnergyHandler) {
IEnergyHandler handler = (IEnergyHandler) ep;
if (handler.canConnectEnergy(ForgeDirection.VALID_DIRECTIONS[j].getOpposite())) {
iWatts = handler.receiveEnergy(ForgeDirection.VALID_DIRECTIONS[j].getOpposite(),
iWatts, false);
}
} else if (ep instanceof IEnergyReceiver) {
IEnergyReceiver handler = (IEnergyReceiver) ep;
if (handler.canConnectEnergy(ForgeDirection.VALID_DIRECTIONS[j].getOpposite())) {
iWatts = handler.receiveEnergy(ForgeDirection.VALID_DIRECTIONS[j].getOpposite(),
iWatts, false);
}
}
internalPower[i] -= iWatts;
dbgEnergyOutput[j] += iWatts;
powerAverage[j].push(iWatts);
powerAverage[i].push(iWatts);
}
}
}
}
}
}
short highestPower = 0;
for (int i = 0; i < 6; i++) {
powerAverage[i].tick();
displayPower[i] = (short) Math.round(powerAverage[i].getAverage());
if (displayPower[i] > highestPower) {
highestPower = displayPower[i];
}
}
overload += highestPower > (maxPower * 0.95F) ? 1 : -1;
if (overload < 0) {
overload = 0;
}
if (overload > OVERLOAD_TICKS) {
overload = OVERLOAD_TICKS;
}
// Compute the tiles requesting energy that are not power pipes
for (ForgeDirection dir : ForgeDirection.VALID_DIRECTIONS) {
if (!outputOpen(dir)) {
continue;
}
Object tile = providers[dir.ordinal()];
if (tile instanceof IPipeTile
&& ((IPipeTile) tile).getPipe() != null
&& ((Pipe<?>) ((IPipeTile) tile).getPipe()).transport instanceof PipeTransportPower) {
continue;
}
if (tile instanceof IEnergyHandler) {
IEnergyHandler handler = (IEnergyHandler) tile;
if (handler.canConnectEnergy(dir.getOpposite())) {
int request = handler.receiveEnergy(dir.getOpposite(), this.maxPower, true);
if (request > 0) {
requestEnergy(dir, request);
}
}
} else if (tile instanceof IEnergyReceiver) {
IEnergyReceiver handler = (IEnergyReceiver) tile;
if (handler.canConnectEnergy(dir.getOpposite())) {
int request = handler.receiveEnergy(dir.getOpposite(), this.maxPower, true);
if (request > 0) {
requestEnergy(dir, request);
}
}
}
}
// Sum the amount of energy requested on each side
int[] transferQuery = new int[6];
for (int i = 0; i < 6; ++i) {
transferQuery[i] = 0;
if (!inputOpen(ForgeDirection.getOrientation(i))) {
continue;
}
for (int j = 0; j < 6; ++j) {
if (j != i) {
transferQuery[i] += powerQuery[j];
}
}
transferQuery[i] = Math.min(transferQuery[i], maxPower);
}
// Transfer the requested energy to nearby pipes
for (int i = 0; i < 6; ++i) {
if (transferQuery[i] != 0 && tiles[i] != null) {
TileEntity entity = tiles[i];
if (entity instanceof IPipeTile && ((IPipeTile) entity).getPipeType() == IPipeTile.PipeType.POWER) {
IPipeTile nearbyTile = (IPipeTile) entity;
if (nearbyTile.getPipe() == null || nearbyTile.getPipeType() != IPipeTile.PipeType.POWER) {
continue;
}
PipeTransportPower nearbyTransport = (PipeTransportPower) ((Pipe<?>) nearbyTile.getPipe()).transport;
nearbyTransport.requestEnergy(ForgeDirection.VALID_DIRECTIONS[i].getOpposite(), transferQuery[i]);
}
}
}
if (tracker.markTimeIfDelay(container.getWorldObj())) {
PacketPowerUpdate packet = new PacketPowerUpdate(container.xCoord, container.yCoord, container.zCoord);
packet.displayPower = displayPower;
packet.overload = isOverloaded();
BuildCraftTransport.instance.sendToPlayers(packet, container.getWorldObj(), container.xCoord, container.yCoord, container.zCoord, DefaultProps.PIPE_CONTENTS_RENDER_DIST);
}
}
public boolean isOverloaded() {
return overload >= OVERLOAD_TICKS;
}
private void step() {
if (container != null && container.getWorldObj() != null
&& currentDate != container.getWorldObj().getTotalWorldTime()) {
currentDate = container.getWorldObj().getTotalWorldTime();
Arrays.fill(dbgEnergyInput, 0);
Arrays.fill(dbgEnergyOffered, 0);
Arrays.fill(dbgEnergyOutput, 0);
powerQuery = nextPowerQuery;
nextPowerQuery = new int[6];
double[] next = internalPower;
internalPower = internalNextPower;
internalNextPower = next;
}
}
/**
* Do NOT ever call this from outside Buildcraft. It is NOT part of the API.
* All power input MUST go through designated input pipes, such as Wooden
* Power Pipes or a subclass thereof.
*/
public double receiveEnergy(ForgeDirection from, double tVal) {
int side = from.ordinal();
double val = tVal;
step();
dbgEnergyOffered[side] += val;
if (this.container.pipe instanceof IPipeTransportPowerHook) {
double ret = ((IPipeTransportPowerHook) this.container.pipe).receiveEnergy(from, (int) val);
if (ret >= 0) {
return ret;
}
}
if (internalNextPower[side] > maxPower) {
return 0;
}
if (BuildCraftTransport.usePipeLoss) {
internalNextPower[side] += val * (1.0F - powerResistance);
} else {
internalNextPower[side] += val;
}
if (internalNextPower[side] > maxPower) {
val -= internalNextPower[side] - maxPower;
internalNextPower[side] = maxPower;
if (val < 0) {
val = 0;
}
}
dbgEnergyInput[side] += val;
return val;
}
public void requestEnergy(ForgeDirection from, int amount) {
step();
if (this.container.pipe instanceof IPipeTransportPowerHook) {
nextPowerQuery[from.ordinal()] += ((IPipeTransportPowerHook) this.container.pipe).requestEnergy(from, amount);
} else {
nextPowerQuery[from.ordinal()] += amount;
}
}
@Override
public void initialize() {
currentDate = container.getWorldObj().getTotalWorldTime();
}
@Override
public void readFromNBT(NBTTagCompound nbttagcompound) {
super.readFromNBT(nbttagcompound);
for (int i = 0; i < 6; ++i) {
powerQuery[i] = nbttagcompound.getInteger("powerQuery[" + i + "]");
nextPowerQuery[i] = nbttagcompound.getInteger("nextPowerQuery[" + i + "]");
internalPower[i] = nbttagcompound.getInteger("internalPower[" + i + "]");
internalNextPower[i] = nbttagcompound.getInteger("internalNextPower[" + i + "]");
}
}
@Override
public void writeToNBT(NBTTagCompound nbttagcompound) {
super.writeToNBT(nbttagcompound);
for (int i = 0; i < 6; ++i) {
nbttagcompound.setInteger("powerQuery[" + i + "]", powerQuery[i]);
nbttagcompound.setInteger("nextPowerQuery[" + i + "]", nextPowerQuery[i]);
nbttagcompound.setDouble("internalPower[" + i + "]", internalPower[i]);
nbttagcompound.setDouble("internalNextPower[" + i + "]", internalNextPower[i]);
}
}
/**
* Client-side handler for receiving power updates from the server;
*
* @param packetPower
*/
public void handlePowerPacket(PacketPowerUpdate packetPower) {
displayPower = packetPower.displayPower;
overload = packetPower.overload ? OVERLOAD_TICKS : 0;
}
public boolean isQueryingPower() {
for (int d : powerQuery) {
if (d > 0) {
return true;
}
}
return false;
}
static {
powerCapacities.put(PipePowerCobblestone.class, TransportConstants.PIPE_POWER_BASE_CAP);
powerCapacities.put(PipePowerStone.class, 2 * TransportConstants.PIPE_POWER_BASE_CAP);
powerCapacities.put(PipePowerWood.class, 4 * TransportConstants.PIPE_POWER_BASE_CAP);
powerCapacities.put(PipePowerSandstone.class, 4 * TransportConstants.PIPE_POWER_BASE_CAP);
powerCapacities.put(PipePowerQuartz.class, 8 * TransportConstants.PIPE_POWER_BASE_CAP);
powerCapacities.put(PipePowerIron.class, 16 * TransportConstants.PIPE_POWER_BASE_CAP);
powerCapacities.put(PipePowerGold.class, 32 * TransportConstants.PIPE_POWER_BASE_CAP);
powerCapacities.put(PipePowerEmerald.class, 32 * TransportConstants.PIPE_POWER_BASE_CAP);
powerCapacities.put(PipePowerDiamond.class, 128 * TransportConstants.PIPE_POWER_BASE_CAP);
powerResistances.put(PipePowerCobblestone.class, 0.05F);
powerResistances.put(PipePowerStone.class, 0.025F);
powerResistances.put(PipePowerWood.class, 0.0F);
powerResistances.put(PipePowerSandstone.class, 0.0125F);
powerResistances.put(PipePowerQuartz.class, 0.0125F);
powerResistances.put(PipePowerIron.class, 0.0125F);
powerResistances.put(PipePowerGold.class, 0.003125F);
powerResistances.put(PipePowerEmerald.class, 0.0F);
powerResistances.put(PipePowerDiamond.class, 0.0F);
}
@Override
public void getDebugInfo(List<String> info, ForgeDirection side, ItemStack debugger, EntityPlayer player) {
info.add("PipeTransportPower (" + maxPower + " RF/t)");
info.add("- internalPower: " + Arrays.toString(internalPower) + " <- " + Arrays.toString(internalNextPower));
info.add("- powerQuery: " + Arrays.toString(powerQuery) + " <- " + Arrays.toString(nextPowerQuery));
info.add("- energy: IN " + Arrays.toString(dbgEnergyInput) + ", OUT " + Arrays.toString(dbgEnergyOutput));
info.add("- energy: OFFERED " + Arrays.toString(dbgEnergyOffered));
int[] totalPowerQuery = new int[6];
for (int i = 0; i < 6; ++i) {
if (internalPower[i] > 0) {
for (int j = 0; j < 6; ++j) {
if (j != i && powerQuery[j] > 0) {
Object ep = providers[j];
if (ep instanceof IPipeTile || ep instanceof IEnergyReceiver || ep instanceof IEnergyHandler) {
totalPowerQuery[i] += powerQuery[j];
}
}
}
}
}
info.add("- totalPowerQuery: " + Arrays.toString(totalPowerQuery));
}
}