package erogenousbeef.bigreactors.common.multiblock.tileentity;
import net.minecraft.block.Block;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import erogenousbeef.bigreactors.client.gui.GuiReactorRedstonePort;
import erogenousbeef.bigreactors.common.BigReactors;
import erogenousbeef.bigreactors.common.multiblock.MultiblockReactor;
import erogenousbeef.bigreactors.common.multiblock.block.BlockReactorRedstonePort;
import erogenousbeef.bigreactors.common.multiblock.interfaces.ITickableMultiblockPart;
import erogenousbeef.bigreactors.common.multiblock.tileentity.TileEntityReactorRedNetPort.CircuitType;
import erogenousbeef.bigreactors.gui.container.ContainerBasic;
import erogenousbeef.core.multiblock.MultiblockControllerBase;
import erogenousbeef.core.multiblock.MultiblockValidationException;
public class TileEntityReactorRedstonePort extends TileEntityReactorPartBase
implements ITickableMultiblockPart {
protected CircuitType circuitType;
protected int outputLevel;
protected boolean activeOnPulse;
protected boolean greaterThan; // if false, less than
protected int ticksSinceLastUpdate;
// These are local-only and used for handy state calculations
protected boolean isExternallyPowered;
public TileEntityReactorRedstonePort() {
super();
circuitType = circuitType.DISABLED;
isExternallyPowered = false;
ticksSinceLastUpdate = 0;
}
// Redstone methods
public boolean isRedstoneActive() {
if(!this.isConnected()) { return false; }
MultiblockReactor reactor = (MultiblockReactor)getMultiblockController();
switch(circuitType) {
case outputFuelTemperature:
return checkVariable((int)reactor.getFuelHeat());
case outputCasingTemperature:
return checkVariable((int)reactor.getReactorHeat());
case outputFuelMix:
return checkVariable((int)(reactor.getFuelRichness()*100));
case outputFuelAmount:
return checkVariable(reactor.getFuelAmount());
case outputWasteAmount:
return checkVariable(reactor.getWasteAmount());
case outputEnergyAmount:
return checkVariable(reactor.getEnergyStoredPercentage());
case DISABLED:
return false;
default:
return this.isExternallyPowered;
}
}
public boolean isInput() {
return TileEntityReactorRedNetPort.isInput(this.circuitType);
}
public boolean isOutput() {
return TileEntityReactorRedNetPort.isOutput(this.circuitType);
}
protected boolean checkVariable(int value) {
if(this.greaterThan) {
return value > getOutputLevel();
}
else {
return value < getOutputLevel();
}
}
public void sendRedstoneUpdate() {
if(this.worldObj != null && !this.worldObj.isRemote) {
int md;
if(this.isOutput()) {
md = isRedstoneActive() ? BlockReactorRedstonePort.META_REDSTONE_LIT : BlockReactorRedstonePort.META_REDSTONE_UNLIT;
}
else {
md = isExternallyPowered ? BlockReactorRedstonePort.META_REDSTONE_LIT : BlockReactorRedstonePort.META_REDSTONE_UNLIT;
}
if(md != this.worldObj.getBlockMetadata(xCoord, yCoord, zCoord)) {
this.worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, md, 3);
}
}
}
public void onNeighborBlockChange(int x, int y, int z, Block neighborBlock) {
if(!this.isConnected()) { return; }
if(this.isInput()) {
ForgeDirection out = getOutwardsDir();
boolean nowPowered = isReceivingRedstonePowerFrom(worldObj, xCoord+out.offsetX, yCoord+out.offsetY, zCoord+out.offsetZ, out, neighborBlock);
if(this.isExternallyPowered != nowPowered) {
this.isExternallyPowered = nowPowered;
this.onRedstoneInputUpdated();
this.sendRedstoneUpdate();
}
}
else {
this.isExternallyPowered = false;
}
}
// Called to do business logic when the redstone value has changed
protected void onRedstoneInputUpdated() {
if(!this.isConnected()) { return; }
MultiblockReactor reactor = (MultiblockReactor)getMultiblockController();
switch(this.circuitType) {
case inputActive:
if(this.isInputActiveOnPulse()) {
if(this.isExternallyPowered) {
reactor.setActive(!reactor.getActive());
}
}
else {
reactor.setActive(this.isExternallyPowered);
}
break;
case inputSetControlRod:
// On/off only
if(this.isInputActiveOnPulse()) {
if(this.isExternallyPowered) {
if(this.shouldSetControlRodsInsteadOfChange()) {
reactor.setAllControlRodInsertionValues(this.outputLevel);
}
else {
reactor.changeAllControlRodInsertionValues((short)this.outputLevel); // Can be negative, don't want to mask.
}
}
}
else {
if(this.isExternallyPowered) {
reactor.setAllControlRodInsertionValues(getControlRodLevelWhileOn());
}
else {
reactor.setAllControlRodInsertionValues(getControlRodLevelWhileOff());
}
}
break;
case inputEjectWaste:
// Pulse only
if(this.isExternallyPowered) {
reactor.ejectWaste(false, null);
}
break;
default:
break;
}
}
public int getOutputLevel() { return outputLevel; }
public int getControlRodLevelWhileOff() { return ((outputLevel & 0xFF00) >> 8) & 0xFF; }
public int getControlRodLevelWhileOn () { return outputLevel & 0xFF; }
public static int packControlRodLevels(byte off, byte on) {
return (((int)off << 8) & 0xFF00) | (on & 0xFF);
}
public static int unpackControlRodLevelOn(int level) {
return level & 0xFF;
}
public static int unpackControlRodLevelOff(int level) {
return ((level % 0xFF00) >> 8) & 0xFF;
}
public boolean isInputActiveOnPulse() {
return this.activeOnPulse;
}
/**
* @param newType The type of the new circuit.
* @param param1 For input/control rods, the level(s) to change or set. For outputs, the numerical value
* @param greaterThan For outputs, whether to activate when greater than or less than the outputLevel value. For input/control rods, whether to set (true) or change (false) the values.
*/
public void onReceiveUpdatePacket(int newType, int outputLevel, boolean greaterThan, boolean activeOnPulse) {
this.circuitType = CircuitType.values()[newType];
this.outputLevel = outputLevel;
this.greaterThan = greaterThan;
this.activeOnPulse = activeOnPulse;
if(isAlwaysActiveOnPulse(circuitType)) { this.activeOnPulse = true; }
else if(TileEntityReactorRedNetPort.isOutput(this.circuitType)) { this.activeOnPulse = false; }
// Do updates
if(this.isInput()) {
// Update inputs so we don't pulse/change automatically
ForgeDirection out = getOutwardsDir();
this.isExternallyPowered = isReceivingRedstonePowerFrom(worldObj, xCoord+out.offsetX, yCoord+out.offsetY, zCoord+out.offsetZ, out);
if(!this.isInputActiveOnPulse()) {
onRedstoneInputUpdated();
}
}
else {
this.isExternallyPowered = false;
}
// Ensure visuals and metadata reflect our new settings & state
this.sendRedstoneUpdate();
if(!this.worldObj.isRemote) {
// Propagate the new settings
this.worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
this.worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this);
}
}
@SideOnly(Side.CLIENT)
public boolean getGreaterThan() { return this.greaterThan; }
public CircuitType getCircuitType() { return this.circuitType; }
private boolean shouldSetControlRodsInsteadOfChange() { return !greaterThan; }
public void onRedNetUpdate(int powerLevel) {
if(this.isInput()) {
boolean wasPowered = this.isExternallyPowered;
this.isExternallyPowered = powerLevel > 0;
if(wasPowered != this.isExternallyPowered) {
this.onRedstoneInputUpdated();
this.sendRedstoneUpdate();
}
}
}
/**
* Call with the coordinates of the block to check and the direction
* towards that block from your block.
* If the block towards which this block is emitting power lies north,
* then pass in south.
*/
private boolean isReceivingRedstonePowerFrom(World world, int x, int y, int z, ForgeDirection dir) {
// This is because of bugs in vanilla redstone wires
Block block = world.getBlock(x, y, z);
return isReceivingRedstonePowerFrom(world, x, y, z, dir, block);
}
/**
* Call with the coordinates of the block to check and the direction
* towards that block from your block.
* If the block towards which this block is emitting power lies north,
* then pass in south.
*/
private boolean isReceivingRedstonePowerFrom(World world, int x, int y, int z, ForgeDirection dir, Block neighborBlock) {
if(neighborBlock == Blocks.redstone_wire) {
// Use metadata because of vanilla redstone wire bugs
return world.getBlockMetadata(x, y, z) > 0;
}
else {
return world.getIndirectPowerOutput(x, y, z, dir.ordinal()) || world.isBlockProvidingPowerTo(x, y, z, dir.ordinal()) > 0;
}
}
// TileEntity overrides
// Only refresh if we're switching functionality
// Warning: dragonz!
@Override
public boolean shouldRefresh(Block oldID, Block newID, int oldMeta, int newMeta, World world, int x, int y, int z)
{
if(oldID != newID) {
return true;
}
// All redstone ports are the same, we just use metadata to easily signal changes.
return false;
}
// IReactorTickable
/**
* Updates the redstone block's status, if it's an output network, if there is one.
* Will only send one update per N ticks, where N is a configurable setting.
*/
public void onMultiblockServerTick() {
if(!this.isConnected()) { return; }
ticksSinceLastUpdate++;
if(ticksSinceLastUpdate < BigReactors.ticksPerRedstoneUpdate) { return; }
if(this.isOutput()) {
// Will no-op if there's no change.
this.sendRedstoneUpdate();
}
ticksSinceLastUpdate = 0;
}
// MultiblockTileEntityBase methods
private void readData(NBTTagCompound data) {
if(data.hasKey("circuitType")) {
this.circuitType = circuitType.values()[data.getInteger("circuitType")];
}
if(data.hasKey("outputLevel")) {
this.outputLevel = data.getInteger("outputLevel");
}
if(data.hasKey("greaterThan")) {
this.greaterThan = data.getBoolean("greaterThan");
}
if(data.hasKey("activeOnPulse")) {
this.activeOnPulse = data.getBoolean("activeOnPulse");
}
}
private void writeData(NBTTagCompound data) {
data.setInteger("circuitType", this.circuitType.ordinal());
data.setInteger("outputLevel", this.outputLevel);
data.setBoolean("greaterThan", this.greaterThan);
data.setBoolean("activeOnPulse", this.activeOnPulse);
}
@Override
public void readFromNBT(NBTTagCompound data) {
super.readFromNBT(data);
this.readData(data);
this.sendRedstoneUpdate();
}
@Override
public void writeToNBT(NBTTagCompound data) {
super.writeToNBT(data);
this.writeData(data);
}
@Override
public void decodeDescriptionPacket(NBTTagCompound data) {
super.decodeDescriptionPacket(data);
this.readData(data);
}
@Override
public void encodeDescriptionPacket(NBTTagCompound data) {
super.encodeDescriptionPacket(data);
this.writeData(data);
}
@Override
public void isGoodForFrame() throws MultiblockValidationException {
throw new MultiblockValidationException(String.format("%d, %d, %d - Redstone ports may only be placed on a reactor's external side faces, not as part of the frame", xCoord, yCoord, zCoord));
}
@Override
public void isGoodForSides() throws MultiblockValidationException {
}
@Override
public void isGoodForTop() throws MultiblockValidationException {
throw new MultiblockValidationException(String.format("%d, %d, %d - Redstone ports may only be placed on a reactor's external side faces, not the top", xCoord, yCoord, zCoord));
}
@Override
public void isGoodForBottom() throws MultiblockValidationException {
throw new MultiblockValidationException(String.format("%d, %d, %d - Redstone ports may only be placed on a reactor's external side faces, not the bottom", xCoord, yCoord, zCoord));
}
@Override
public void isGoodForInterior() throws MultiblockValidationException {
throw new MultiblockValidationException(String.format("%d, %d, %d - Redstone ports may not be placed in a reactor's interior", xCoord, yCoord, zCoord));
}
@Override
public void onMachineAssembled(MultiblockControllerBase controller) {
super.onMachineAssembled(controller);
this.sendRedstoneUpdate();
}
@Override
public void onMachineBroken() {
super.onMachineBroken();
this.sendRedstoneUpdate();
}
@Override
public void onMachineActivated() {
this.sendRedstoneUpdate();
}
@Override
public void onMachineDeactivated() {
this.sendRedstoneUpdate();
}
// IBeefGuiEntity
@SideOnly(Side.CLIENT)
@Override
public Object getGuiElement(InventoryPlayer inventoryPlayer) {
return new GuiReactorRedstonePort(new ContainerBasic(), this);
}
@Override
public Object getContainer(InventoryPlayer inventoryPlayer) {
return new ContainerBasic();
}
public static boolean isAlwaysActiveOnPulse(CircuitType circuitType) {
return circuitType == CircuitType.inputEjectWaste;
}
}