/** * 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.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); ((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; } }