buildcraft/common/buildcraft/transport/Gate.java

502 lines
15 KiB
Java

/**
* 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.transport;
import java.util.BitSet;
import java.util.LinkedList;
import java.util.List;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.common.util.ForgeDirection;
import buildcraft.BuildCraftTransport;
import buildcraft.api.gates.GateExpansionController;
import buildcraft.api.gates.IAction;
import buildcraft.api.gates.IActionParameter;
import buildcraft.api.gates.IActionReceptor;
import buildcraft.api.gates.IGate;
import buildcraft.api.gates.IGateExpansion;
import buildcraft.api.gates.ITrigger;
import buildcraft.api.gates.ITriggerParameter;
import buildcraft.api.gates.StatementManager;
import buildcraft.api.gates.TriggerParameterItemStack;
import buildcraft.api.transport.IPipe;
import buildcraft.api.transport.PipeWire;
import buildcraft.core.GuiIds;
import buildcraft.core.triggers.ActionRedstoneOutput;
import buildcraft.transport.gates.GateDefinition.GateLogic;
import buildcraft.transport.gates.GateDefinition.GateMaterial;
import buildcraft.transport.gates.ItemGate;
import buildcraft.transport.gui.ContainerGateInterface;
import buildcraft.transport.triggers.ActionRedstoneFaderOutput;
public final class Gate implements IGate {
public static int MAX_STATEMENTS = 8;
public static int MAX_PARAMETERS = 3;
public final Pipe<?> pipe;
public final GateMaterial material;
public final GateLogic logic;
public final BiMap<IGateExpansion, GateExpansionController> expansions = HashBiMap.create();
public ITrigger[] triggers = new ITrigger[MAX_STATEMENTS];
public ITriggerParameter[][] triggerParameters = new ITriggerParameter[8][MAX_PARAMETERS];
public IAction[] actions = new IAction[MAX_STATEMENTS];
public IActionParameter[][] actionParameters = new IActionParameter[8][MAX_PARAMETERS];
public ActionActiveState[] actionsState = new ActionActiveState[MAX_STATEMENTS];
public BitSet broadcastSignal = new BitSet(PipeWire.VALUES.length);
public BitSet prevBroadcastSignal = new BitSet(PipeWire.VALUES.length);
public int redstoneOutput = 0;
/**
* this is the internal pulsing state of the gate. Intended to be managed
* by the server side only, the client is supposed to be referring to the
* state of the renderer, and update moveStage accordingly.
*/
public boolean isPulsing = false;
private float pulseStage = 0;
private ForgeDirection direction;
// / CONSTRUCTOR
public Gate(Pipe<?> pipe, GateMaterial material, GateLogic logic, ForgeDirection direction) {
this.pipe = pipe;
this.material = material;
this.logic = logic;
this.direction = direction;
for (int i = 0; i < actionsState.length; ++i) {
actionsState[i] = ActionActiveState.Deactivated;
}
}
public void setTrigger(int position, ITrigger trigger) {
triggers[position] = trigger;
}
public ITrigger getTrigger(int position) {
return triggers[position];
}
public void setAction(int position, IAction action) {
actions[position] = action;
}
public IAction getAction(int position) {
return actions[position];
}
public void setTriggerParameter(int trigger, int param, ITriggerParameter p) {
triggerParameters[trigger][param] = p;
}
public void setActionParameter(int action, int param, IActionParameter p) {
actionParameters[action][param] = p;
}
public ITriggerParameter getTriggerParameter(int trigger, int param) {
return triggerParameters[trigger][param];
}
public IActionParameter getActionParameter(int action, int param) {
return actionParameters[action][param];
}
public ForgeDirection getDirection() {
return direction;
}
public void setDirection(ForgeDirection direction) {
this.direction = direction;
}
public void addGateExpansion(IGateExpansion expansion) {
if (!expansions.containsKey(expansion)) {
expansions.put(expansion, expansion.makeController(pipe.container));
}
}
// / SAVING & LOADING
public void writeToNBT(NBTTagCompound data) {
data.setString("material", material.name());
data.setString("logic", logic.name());
data.setInteger("direction", direction.ordinal());
NBTTagList exList = new NBTTagList();
for (GateExpansionController con : expansions.values()) {
NBTTagCompound conNBT = new NBTTagCompound();
conNBT.setString("type", con.getType().getUniqueIdentifier());
NBTTagCompound conData = new NBTTagCompound();
con.writeToNBT(conData);
conNBT.setTag("data", conData);
exList.appendTag(conNBT);
}
data.setTag("expansions", exList);
for (int i = 0; i < MAX_STATEMENTS; ++i) {
if (triggers[i] != null) {
data.setString("trigger[" + i + "]", triggers[i].getUniqueTag());
}
if (actions[i] != null) {
data.setString("action[" + i + "]", actions[i].getUniqueTag());
}
for (int j = 0; j < MAX_PARAMETERS; ++j) {
if (triggerParameters[i][j] != null) {
NBTTagCompound cpt = new NBTTagCompound();
cpt.setString("kind", StatementManager.getParameterKind(triggerParameters[i][j]));
triggerParameters[i][j].writeToNBT(cpt);
data.setTag("triggerParameters[" + i + "][" + j + "]", cpt);
}
}
for (int j = 0; j < MAX_PARAMETERS; ++j) {
if (actionParameters[i][j] != null) {
NBTTagCompound cpt = new NBTTagCompound();
cpt.setString("kind", StatementManager.getParameterKind(actionParameters[i][j]));
actionParameters[i][j].writeToNBT(cpt);
data.setTag("actionParameters[" + i + "][" + j + "]", cpt);
}
}
}
for (PipeWire wire : PipeWire.VALUES) {
data.setBoolean("wireState[" + wire.ordinal() + "]", broadcastSignal.get(wire.ordinal()));
}
data.setByte("redstoneOutput", (byte) redstoneOutput);
}
public void readFromNBT(NBTTagCompound data) {
for (int i = 0; i < MAX_STATEMENTS; ++i) {
if (data.hasKey("trigger[" + i + "]")) {
triggers[i] = (ITrigger) StatementManager.statements.get(data.getString("trigger[" + i + "]"));
}
if (data.hasKey("action[" + i + "]")) {
actions[i] = (IAction) StatementManager.statements.get(data.getString("action[" + i + "]"));
}
// This is for legacy trigger loading
if (data.hasKey("triggerParameters[" + i + "]")) {
triggerParameters[i][0] = new TriggerParameterItemStack();
triggerParameters[i][0].readFromNBT(data.getCompoundTag("triggerParameters[" + i + "]"));
}
for (int j = 0; j < MAX_PARAMETERS; ++j) {
if (data.hasKey("triggerParameters[" + i + "][" + j + "]")) {
NBTTagCompound cpt = data.getCompoundTag("triggerParameters[" + i + "][" + j + "]");
triggerParameters[i][j] = (ITriggerParameter) StatementManager.createParameter(cpt
.getString("kind"));
triggerParameters[i][j].readFromNBT(cpt);
}
}
for (int j = 0; j < MAX_PARAMETERS; ++j) {
if (data.hasKey("actionParameters[" + i + "][" + j + "]")) {
NBTTagCompound cpt = data.getCompoundTag("actionParameters[" + i + "][" + j + "]");
actionParameters[i][j] = (IActionParameter) StatementManager.createParameter(cpt.getString("kind"));
actionParameters[i][j].readFromNBT(cpt);
}
}
}
for (PipeWire wire : PipeWire.VALUES) {
broadcastSignal.set(wire.ordinal(), data.getBoolean("wireState[" + wire.ordinal() + "]"));
}
redstoneOutput = data.getByte("redstoneOutput");
}
// GUI
public void openGui(EntityPlayer player) {
if (!player.worldObj.isRemote) {
player.openGui(BuildCraftTransport.instance, GuiIds.GATES, pipe.container.getWorldObj(), pipe.container.xCoord, pipe.container.yCoord, pipe.container.zCoord);
((ContainerGateInterface) player.openContainer).setGate(direction.ordinal());
}
}
/**
* This code is aimed at being active on the client only, and moves
* the internal position of the gate. There's no need to do that
* or to synchronize that with the server as this is only for animation.
*/
public void updatePulse () {
if (pipe.container.renderState.gateMatrix.isGatePulsing(direction) || pulseStage > 0.11F) {
// if it is moving, or is still in a moved state, then complete
// the current movement
pulseStage = (pulseStage + 0.01F) % 1F;
} else {
pulseStage = 0;
}
}
// UPDATING
public void tick() {
for (GateExpansionController expansion : expansions.values()) {
expansion.tick(this);
}
}
public ItemStack getGateItem() {
return ItemGate.makeGateItem(this);
}
public void dropGate() {
pipe.dropItem(getGateItem());
}
public void resetGate() {
if (redstoneOutput != 0) {
redstoneOutput = 0;
pipe.updateNeighbors(true);
}
}
public boolean isGateActive() {
for (ActionActiveState state : actionsState) {
if (state == ActionActiveState.Activated) {
return true;
}
}
return false;
}
public boolean isGatePulsing() {
return isPulsing;
}
public int getRedstoneOutput() {
return redstoneOutput;
}
public void startResolution() {
for (GateExpansionController expansion : expansions.values()) {
expansion.startResolution();
}
}
public void resolveActions() {
int oldRedstoneOutput = redstoneOutput;
redstoneOutput = 0;
BitSet temp = prevBroadcastSignal;
temp.clear();
prevBroadcastSignal = broadcastSignal;
broadcastSignal = temp;
// Tell the gate to prepare for resolving actions. (Disable pulser)
startResolution();
int [] actionGroups = new int [] {0, 1, 2, 3, 4, 5, 6, 7};
for (int i = 0; i < MAX_PARAMETERS; ++i) {
for (int j = i - 1; j >= 0; --j) {
if (actions[i] != null && actions[j] != null
&& actions[i].getUniqueTag().equals(actions[j].getUniqueTag())) {
boolean sameParams = true;
for (int p = 0; p < MAX_PARAMETERS; ++p) {
if ((actionParameters[i][p] != null && actionParameters[j][p] == null)
|| (actionParameters[i][p] == null && actionParameters[j][p] != null)
|| (actionParameters[i][p] != null
&& actionParameters[j][p] != null
&& !actionParameters[i][p].equals(actionParameters[j][p]))) {
sameParams = false;
}
}
if (sameParams) {
actionGroups[i] = j;
}
}
}
}
// Computes the actions depending on the triggers
for (int it = 0; it < MAX_STATEMENTS; ++it) {
actionsState[it] = ActionActiveState.Deactivated;
ITrigger trigger = triggers[it];
ITriggerParameter[] parameter = triggerParameters[it];
if (trigger != null) {
boolean active = isTriggerActive(trigger, parameter);
if (actionGroups[it] == it) {
if (active) {
actionsState[it] = ActionActiveState.Activated;
}
} else {
if (active && actionsState[actionGroups[it]] != ActionActiveState.Activated) {
actionsState[actionGroups[it]] = ActionActiveState.Partial;
} else if (!active && actionsState[actionGroups[it]] == ActionActiveState.Activated) {
actionsState[actionGroups[it]] = ActionActiveState.Partial;
}
}
}
}
// Activate the actions
for (int it = 0; it < MAX_STATEMENTS; ++it) {
if (actions[it] != null && actionGroups[it] == it && actionsState[it] == ActionActiveState.Activated) {
IAction action = actions[it];
action.actionActivate(this, actionParameters[it]);
// TODO: A lot of the code below should be removed in favor
// of calls to actionActivate
// Custom gate actions take precedence over defaults.
if (resolveAction(action)) {
continue;
}
if (action instanceof ActionRedstoneOutput) {
redstoneOutput = 15;
} else if (action instanceof ActionRedstoneFaderOutput) {
redstoneOutput = ((ActionRedstoneFaderOutput) action).level;
} else {
for (ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
TileEntity tile = pipe.container.getTile(side);
if (tile instanceof IActionReceptor) {
IActionReceptor recept = (IActionReceptor) tile;
recept.actionActivated(action);
}
}
}
}
}
LinkedList<IAction> activeActions = new LinkedList<IAction>();
for (int it = 0; it < MAX_STATEMENTS; ++it) {
if (actionGroups[it] == it && actionsState[it] == ActionActiveState.Activated) {
activeActions.add(actions[it]);
}
}
pipe.actionsActivated(activeActions);
if (oldRedstoneOutput != redstoneOutput) {
if (redstoneOutput == 0 ^ oldRedstoneOutput == 0) {
pipe.container.scheduleRenderUpdate();
}
pipe.updateNeighbors(true);
}
if (!prevBroadcastSignal.equals(broadcastSignal)) {
pipe.container.scheduleRenderUpdate();
pipe.updateSignalState();
}
}
public boolean resolveAction(IAction action) {
for (GateExpansionController expansion : expansions.values()) {
if (expansion.resolveAction(action)) {
return true;
}
}
return false;
}
public boolean isTriggerActive(ITrigger trigger, ITriggerParameter[] parameters) {
if (trigger == null) {
return false;
}
if (trigger.isTriggerActive(this, parameters)) {
return true;
}
// TODO: This can probably be refactored with regular triggers instead
// of yet another system.
for (GateExpansionController expansion : expansions.values()) {
if (expansion.isTriggerActive(trigger, parameters[0])) {
return true;
}
}
return false;
}
// TRIGGERS
public void addTrigger(List<ITrigger> list) {
for (PipeWire wire : PipeWire.VALUES) {
if (pipe.wireSet[wire.ordinal()] && material.ordinal() >= wire.ordinal()) {
list.add(BuildCraftTransport.triggerPipeWireActive[wire.ordinal()]);
list.add(BuildCraftTransport.triggerPipeWireInactive[wire.ordinal()]);
}
}
for (GateExpansionController expansion : expansions.values()) {
expansion.addTriggers(list);
}
}
// ACTIONS
public void addActions(List<IAction> list) {
for (PipeWire wire : PipeWire.VALUES) {
if (pipe.wireSet[wire.ordinal()] && material.ordinal() >= wire.ordinal()) {
list.add(BuildCraftTransport.actionPipeWire[wire.ordinal()]);
}
}
for (GateExpansionController expansion : expansions.values()) {
expansion.addActions(list);
}
}
public LinkedList<IAction> getActions() {
LinkedList<IAction> result = new LinkedList<IAction>();
addActions(result);
return result;
}
@Override
public void setPulsing(boolean pulsing) {
if (pulsing != isPulsing) {
isPulsing = pulsing;
pipe.container.scheduleRenderUpdate();
}
}
public float getPulseStage() {
return pulseStage;
}
public void broadcastSignal(PipeWire color) {
broadcastSignal.set(color.ordinal());
}
@Override
public IPipe getPipe() {
return pipe;
}
@Override
public ForgeDirection getSide() {
return direction;
}
}