buildcraft/common/buildcraft/transport/Gate.java
2017-11-10 19:30:36 +01:00

716 lines
20 KiB
Java

/**
* Copyright (c) 2011-2017, 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.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.HashMultiset;
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.IGate;
import buildcraft.api.gates.IGateExpansion;
import buildcraft.api.statements.IActionExternal;
import buildcraft.api.statements.IActionInternal;
import buildcraft.api.statements.IActionReceptor;
import buildcraft.api.statements.IStatement;
import buildcraft.api.statements.IStatementParameter;
import buildcraft.api.statements.ITriggerExternal;
import buildcraft.api.statements.ITriggerExternalOverride;
import buildcraft.api.statements.ITriggerInternal;
import buildcraft.api.statements.StatementManager;
import buildcraft.api.statements.StatementParameterItemStack;
import buildcraft.api.statements.StatementSlot;
import buildcraft.api.statements.containers.IRedstoneStatementContainer;
import buildcraft.api.statements.containers.ISidedStatementContainer;
import buildcraft.api.transport.IPipe;
import buildcraft.api.transport.PipeWire;
import buildcraft.core.GuiIds;
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.statements.ActionValve;
public final class Gate implements IGate, ISidedStatementContainer, IRedstoneStatementContainer {
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 IStatement[] triggers = new IStatement[MAX_STATEMENTS];
public IStatementParameter[][] triggerParameters = new IStatementParameter[MAX_STATEMENTS][MAX_PARAMETERS];
public IStatement[] actions = new IStatement[MAX_STATEMENTS];
public IStatementParameter[][] actionParameters = new IStatementParameter[MAX_STATEMENTS][MAX_PARAMETERS];
public ActionActiveState[] actionsState = new ActionActiveState[MAX_STATEMENTS];
public ArrayList<StatementSlot> activeActions = new ArrayList<StatementSlot>();
public byte broadcastSignal, prevBroadcastSignal;
public int redstoneOutput = 0;
public int redstoneOutputSide = 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 ForgeDirection direction;
private HashMultiset<IStatement> statementCounts = HashMultiset.create();
private int[] actionGroups = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
// / 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, IStatement trigger) {
if (trigger != triggers[position]) {
for (int i = 0; i < triggerParameters[position].length; i++) {
triggerParameters[position][i] = null;
}
}
triggers[position] = trigger;
}
public IStatement getTrigger(int position) {
return triggers[position];
}
public void setAction(int position, IStatement action) {
// HUGE HACK! TODO - Remove in an API rewrite by adding
// ways for actions to fix their state on removal.
if (actions[position] instanceof ActionValve && pipe != null && pipe.transport != null) {
for (ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
pipe.transport.allowInput(side, true);
pipe.transport.allowOutput(side, true);
}
}
if (action != actions[position]) {
for (int i = 0; i < actionParameters[position].length; i++) {
actionParameters[position][i] = null;
}
}
actions[position] = action;
recalculateActionGroups();
}
public IStatement getAction(int position) {
return actions[position];
}
public void setTriggerParameter(int trigger, int param, IStatementParameter p) {
triggerParameters[trigger][param] = p;
}
public void setActionParameter(int action, int param, IStatementParameter p) {
actionParameters[action][param] = p;
recalculateActionGroups();
}
public IStatementParameter getTriggerParameter(int trigger, int param) {
return triggerParameters[trigger][param];
}
public IStatementParameter 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 != null ? pipe.container : null));
}
}
public void writeStatementsToNBT(NBTTagCompound data) {
for (int i = 0; i < material.numSlots; ++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 < material.numTriggerParameters; ++j) {
if (triggerParameters[i][j] != null) {
NBTTagCompound cpt = new NBTTagCompound();
cpt.setString("kind", triggerParameters[i][j].getUniqueTag());
triggerParameters[i][j].writeToNBT(cpt);
data.setTag("triggerParameters[" + i + "][" + j + "]", cpt);
}
}
for (int j = 0; j < material.numActionParameters; ++j) {
if (actionParameters[i][j] != null) {
NBTTagCompound cpt = new NBTTagCompound();
cpt.setString("kind", actionParameters[i][j].getUniqueTag());
actionParameters[i][j].writeToNBT(cpt);
data.setTag("actionParameters[" + i + "][" + j + "]", cpt);
}
}
}
}
// / 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);
writeStatementsToNBT(data);
data.setByte("wireState", broadcastSignal);
data.setByte("redstoneOutput", (byte) redstoneOutput);
}
public void readStatementsFromNBT(NBTTagCompound data) {
// Clear
for (int i = 0; i < material.numSlots; ++i) {
triggers[i] = null;
actions[i] = null;
for (int j = 0; j < material.numTriggerParameters; j++) {
triggerParameters[i][j] = null;
}
for (int j = 0; j < material.numActionParameters; j++) {
actionParameters[i][j] = null;
}
}
// Read
for (int i = 0; i < material.numSlots; ++i) {
if (data.hasKey("trigger[" + i + "]")) {
triggers[i] = StatementManager.statements.get(data.getString("trigger[" + i + "]"));
}
if (data.hasKey("action[" + i + "]")) {
actions[i] = StatementManager.statements.get(data.getString("action[" + i + "]"));
}
// This is for legacy trigger loading
if (data.hasKey("triggerParameters[" + i + "]")) {
triggerParameters[i][0] = new StatementParameterItemStack();
triggerParameters[i][0].readFromNBT(data.getCompoundTag("triggerParameters[" + i + "]"));
}
for (int j = 0; j < material.numTriggerParameters; ++j) {
if (data.hasKey("triggerParameters[" + i + "][" + j + "]")) {
NBTTagCompound cpt = data.getCompoundTag("triggerParameters[" + i + "][" + j + "]");
triggerParameters[i][j] = StatementManager.createParameter(cpt.getString("kind"));
triggerParameters[i][j].readFromNBT(cpt);
}
}
for (int j = 0; j < material.numActionParameters; ++j) {
if (data.hasKey("actionParameters[" + i + "][" + j + "]")) {
NBTTagCompound cpt = data.getCompoundTag("actionParameters[" + i + "][" + j + "]");
actionParameters[i][j] = StatementManager.createParameter(cpt.getString("kind"));
actionParameters[i][j].readFromNBT(cpt);
}
}
}
recalculateActionGroups();
}
public boolean verifyGateStatements() {
List<IStatement> triggerList = getAllValidTriggers();
List<IStatement> actionList = getAllValidActions();
boolean warning = false;
for (int i = 0; i < MAX_STATEMENTS; ++i) {
if ((triggers[i] != null || actions[i] != null) && i >= material.numSlots) {
triggers[i] = null;
actions[i] = null;
warning = true;
continue;
}
if (triggers[i] != null) {
if (!triggerList.contains(triggers[i]) ||
triggers[i].minParameters() > material.numTriggerParameters) {
triggers[i] = null;
warning = true;
}
}
if (actions[i] != null) {
if (!actionList.contains(actions[i]) ||
actions[i].minParameters() > material.numActionParameters) {
actions[i] = null;
warning = true;
}
}
}
if (warning) {
recalculateActionGroups();
}
return !warning;
}
public void readFromNBT(NBTTagCompound data) {
readStatementsFromNBT(data);
if (data.hasKey("wireState[0]")) {
for (PipeWire wire : PipeWire.VALUES) {
if (data.getBoolean("wireState[" + wire.ordinal() + "]")) {
broadcastSignal |= 1 << wire.ordinal();
}
}
} else {
broadcastSignal = data.getByte("wireState");
}
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);
// ThermalExpansion Autonomous Activator crash fix
if (player.openContainer instanceof ContainerGateInterface) {
((ContainerGateInterface) player.openContainer).setGate(direction.ordinal());
}
}
}
// 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 int getSidedRedstoneOutput() {
return redstoneOutputSide;
}
public void setRedstoneOutput(boolean sideOnly, int value) {
redstoneOutputSide = value;
if (!sideOnly) {
redstoneOutput = value;
}
}
public void startResolution() {
for (GateExpansionController expansion : expansions.values()) {
expansion.startResolution();
}
}
public void resolveActions() {
int oldRedstoneOutput = redstoneOutput;
redstoneOutput = 0;
int oldRedstoneOutputSide = redstoneOutputSide;
redstoneOutputSide = 0;
boolean wasActive = activeActions.size() > 0;
prevBroadcastSignal = broadcastSignal;
broadcastSignal = 0;
// Tell the gate to prepare for resolving actions. (Disable pulser)
startResolution();
// Computes the actions depending on the triggers
for (int it = 0; it < MAX_STATEMENTS; ++it) {
actionsState[it] = ActionActiveState.Deactivated;
IStatement trigger = triggers[it];
IStatementParameter[] parameter = triggerParameters[it];
if (trigger != null) {
if (isTriggerActive(trigger, parameter)) {
actionsState[it] = ActionActiveState.Partial;
}
}
}
activeActions.clear();
for (int it = 0; it < MAX_STATEMENTS; ++it) {
boolean allActive = true;
boolean oneActive = false;
if (actions[it] == null) {
continue;
}
for (int j = 0; j < MAX_STATEMENTS; ++j) {
if (actionGroups[j] == it) {
if (actionsState[j] != ActionActiveState.Partial) {
allActive = false;
} else {
oneActive = true;
}
}
}
if ((logic == GateLogic.AND && allActive && oneActive) || (logic == GateLogic.OR && oneActive)) {
if (logic == GateLogic.AND) {
for (int j = 0; j < MAX_STATEMENTS; ++j) {
if (actionGroups[j] == it) {
actionsState[j] = ActionActiveState.Activated;
}
}
}
StatementSlot slot = new StatementSlot();
slot.statement = actions[it];
slot.parameters = actionParameters[it];
activeActions.add(slot);
}
if (logic == GateLogic.OR && actionsState[it] == ActionActiveState.Partial) {
actionsState[it] = ActionActiveState.Activated;
}
}
statementCounts.clear();
for (int it = 0; it < MAX_STATEMENTS; ++it) {
if (actionsState[it] == ActionActiveState.Activated) {
statementCounts.add(actions[it], 1);
}
}
// Activate the actions
for (StatementSlot slot : activeActions) {
IStatement action = slot.statement;
if (action instanceof IActionInternal) {
((IActionInternal) action).actionActivate(this, slot.parameters);
} else if (action instanceof IActionExternal) {
for (ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
TileEntity tile = this.getPipe().getTile().getNeighborTile(side);
if (tile != null) {
((IActionExternal) action).actionActivate(tile, side, this, slot.parameters);
}
}
} else {
continue;
}
// Custom gate actions take precedence over defaults.
if (resolveAction(action)) {
continue;
}
for (ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
TileEntity tile = pipe.container.getTile(side);
if (tile instanceof IActionReceptor) {
IActionReceptor recept = (IActionReceptor) tile;
recept.actionActivated(action, slot.parameters);
}
}
}
pipe.actionsActivated(activeActions);
if (oldRedstoneOutput != redstoneOutput || oldRedstoneOutputSide != redstoneOutputSide) {
pipe.updateNeighbors(true);
}
if (prevBroadcastSignal != broadcastSignal) {
pipe.scheduleWireUpdate();
}
boolean isActive = activeActions.size() > 0;
if (wasActive != isActive) {
pipe.container.scheduleRenderUpdate();
}
}
public boolean resolveAction(IStatement action) {
for (GateExpansionController expansion : expansions.values()) {
if (expansion.resolveAction(action, statementCounts.count(action))) {
return true;
}
}
return false;
}
public boolean isTriggerActive(IStatement trigger, IStatementParameter[] parameters) {
if (trigger == null) {
return false;
}
if (trigger instanceof ITriggerInternal) {
if (((ITriggerInternal) trigger).isTriggerActive(this, parameters)) {
return true;
}
} else if (trigger instanceof ITriggerExternal) {
for (ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
TileEntity tile = this.getPipe().getTile().getNeighborTile(side);
if (tile != null) {
if (tile instanceof ITriggerExternalOverride) {
ITriggerExternalOverride.Result result = ((ITriggerExternalOverride) tile).override(side, this, parameters);
if (result == ITriggerExternalOverride.Result.TRUE) {
return true;
} else if (result == ITriggerExternalOverride.Result.FALSE) {
continue;
}
}
if (((ITriggerExternal) trigger).isTriggerActive(tile, side, 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)) {
return true;
}
}
return false;
}
// TRIGGERS
public void addTriggers(List<ITriggerInternal> list) {
for (PipeWire wire : PipeWire.VALUES) {
if (pipe.wireSet[wire.ordinal()] && wire.ordinal() < material.maxWireColor) {
list.add(BuildCraftTransport.triggerPipeWireActive[wire.ordinal()]);
list.add(BuildCraftTransport.triggerPipeWireInactive[wire.ordinal()]);
}
}
for (GateExpansionController expansion : expansions.values()) {
expansion.addTriggers(list);
}
}
public List<IStatement> getAllValidTriggers() {
ArrayList<IStatement> allTriggers = new ArrayList<IStatement>(64);
allTriggers.addAll(StatementManager.getInternalTriggers(this));
for (ForgeDirection o : ForgeDirection.VALID_DIRECTIONS) {
TileEntity tile = pipe.container.getTile(o);
allTriggers.addAll(StatementManager.getExternalTriggers(o, tile));
}
return allTriggers;
}
// ACTIONS
public void addActions(List<IActionInternal> list) {
for (PipeWire wire : PipeWire.VALUES) {
if (pipe.wireSet[wire.ordinal()] && wire.ordinal() < material.maxWireColor) {
list.add(BuildCraftTransport.actionPipeWire[wire.ordinal()]);
}
}
for (GateExpansionController expansion : expansions.values()) {
expansion.addActions(list);
}
}
public List<IStatement> getAllValidActions() {
ArrayList<IStatement> allActions = new ArrayList<IStatement>(64);
allActions.addAll(StatementManager.getInternalActions(this));
for (ForgeDirection o : ForgeDirection.VALID_DIRECTIONS) {
TileEntity tile = pipe.container.getTile(o);
allActions.addAll(StatementManager.getExternalActions(o, tile));
}
return allActions;
}
//@Override TODO
public void setPulsing(boolean pulsing) {
if (pulsing != isPulsing) {
isPulsing = pulsing;
pipe.container.scheduleRenderUpdate();
}
}
private void recalculateActionGroups() {
for (int i = 0; i < MAX_STATEMENTS; ++i) {
actionGroups[i] = 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;
break;
}
}
if (sameParams) {
actionGroups[i] = j;
}
}
}
}
}
public void broadcastSignal(PipeWire color) {
broadcastSignal |= 1 << color.ordinal();
}
public IPipe getPipe() {
return pipe;
}
@Override
public ForgeDirection getSide() {
return direction;
}
@Override
public TileEntity getTile() {
return pipe.container;
}
@Override
public List<IStatement> getTriggers() {
return Arrays.asList(triggers).subList(0, material.numSlots);
}
@Override
public List<IStatement> getActions() {
return Arrays.asList(actions).subList(0, material.numSlots);
}
@Override
public List<StatementSlot> getActiveActions() {
return activeActions;
}
@Override
public List<IStatementParameter> getTriggerParameters(int index) {
if (index < 0 || index >= material.numSlots) {
return null;
}
return Arrays.asList(triggerParameters[index]).subList(0, material.numTriggerParameters);
}
@Override
public List<IStatementParameter> getActionParameters(int index) {
if (index < 0 || index >= material.numSlots) {
return null;
}
return Arrays.asList(actionParameters[index]).subList(0, material.numActionParameters);
}
@Override
public int getRedstoneInput(ForgeDirection side) {
return side == ForgeDirection.UNKNOWN ? pipe.container.redstoneInput : pipe.container.redstoneInputSide[side.ordinal()];
}
@Override
public boolean setRedstoneOutput(ForgeDirection side, int value) {
if (side != this.getSide() && side != ForgeDirection.UNKNOWN) {
return false;
}
setRedstoneOutput(side != ForgeDirection.UNKNOWN, value);
return true;
}
}