package net.sf.colossus.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.GridLayout; import java.awt.Image; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.SwingConstants; import javax.swing.WindowConstants; import net.sf.colossus.client.Client; import net.sf.colossus.client.GameClientSide; import net.sf.colossus.common.Constants; import net.sf.colossus.common.Options; import net.sf.colossus.game.BattleCritter; import net.sf.colossus.game.BattlePhase; import net.sf.colossus.game.BattleUnit; import net.sf.colossus.game.Engagement; import net.sf.colossus.game.Legion; import net.sf.colossus.game.PlayerColor; import net.sf.colossus.guiutil.KFrame; import net.sf.colossus.guiutil.SaveWindow; import net.sf.colossus.server.LegionServerSide; import net.sf.colossus.util.StaticResourceLoader; import net.sf.colossus.variant.BattleHex; /** * A GUI representation of a battle in the game. * * TODO this is split of the former BattleMap which did everything by itself. The * split is not really completed, there is still code which potentially belongs into * the other class. */ @SuppressWarnings("serial") public final class BattleBoard extends KFrame { private static final Logger LOGGER = Logger.getLogger(BattleMap.class .getName()); private static int count = 1; private JMenuBar menuBar; private JMenu phaseMenu; private JMenu helpMenu; private final InfoPanel infoPanel; private final DicePanel dicePanel; private final ClientGUI gui; private final String infoText; /** tag of the selected critter, or -1 if no critter is selected. */ private int selectedCritterTag = -1; private static final String undoLast = "Undo Last"; private static final String undoAll = "Undo All"; private static final String doneWithPhase = "Done"; private static final String concedeBattle = "Concede Battle"; private static final String showTerrainHazard = "Show Terrain"; private AbstractAction undoLastAction; private AbstractAction undoAllAction; private AbstractAction doneWithPhaseAction; private AbstractAction concedeBattleAction; private AbstractAction showTerrainHazardAction; private final SaveWindow saveWindow; private final BattleMap battleMap; private boolean alreadyConceded = false; private static class DicePanel extends JPanel { private final BattleDice battleDice; private final JScrollBar scrollBar; DicePanel() { setLayout(new BorderLayout()); battleDice = new BattleDice(); add(battleDice, BorderLayout.CENTER); scrollBar = new JScrollBar(JScrollBar.VERTICAL); scrollBar.setVisibleAmount(1); scrollBar.addAdjustmentListener(new AdjustmentListener() { public void adjustmentValueChanged(AdjustmentEvent pArg0) { if (pArg0.getAdjustmentType() == AdjustmentEvent.TRACK) { battleDice.setCurrentRoll(pArg0.getValue() + 1); } } }); add(scrollBar, BorderLayout.EAST); } void rescale() { battleDice.rescale(); } void addValues(String pBattlePhaseDesc, String pAttckerDesc, String pStrikerDesc, String pTargetDesc, int pTargetNumber, List<String> pRolls) { int max = battleDice.getHistoryLength(); battleDice.addValues(pBattlePhaseDesc, pAttckerDesc, pStrikerDesc, pTargetDesc, pTargetNumber, pRolls); scrollBar.setMaximum(max); scrollBar.setValue(max); } void showLastRoll() { battleDice.showLastRoll(); } } public BattleBoard(final ClientGUI gui, final Engagement engagement) { super(); // title will be set later this.gui = gui; String attackerMarkerId = engagement.getAttackingLegion() .getMarkerId(); String defenderMarkerId = engagement.getDefendingLegion() .getMarkerId(); setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); JPanel contentPane = new JPanel(); setContentPane(contentPane); contentPane.setLayout(new BorderLayout()); setupIcon(); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { actOnWindowClosingAttempt(); } /* Bruno reported the following issue: * If one switches to another desktop which game (battle) is * ongoing, updates to BattleBoard do not happen and there is no * easy way to refresh the BattleBoard when needed. * Now... just minimize and restore, that forces a repaint, * that should solve the problem... I hope ;-) __Clemens. */ @Override public void windowDeiconified(WindowEvent e) { repaint(); } }); setupActions(); setupTopMenu(); setupHelpMenu(); battleMap = new BattleMap(getClient(), engagement.getLocation(), engagement.getAttackingLegion(), engagement.getDefendingLegion(), gui); contentPane.add(new JScrollPane(battleMap), BorderLayout.CENTER); battleMap.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { // Only the active player can click on stuff. if (!isMyBattleTurn()) { return; } Point point = e.getPoint(); GUIBattleChit battleChit = getBattleChitAtPoint(point); GUIBattleHex hex = battleMap.getHexContainingPoint(point); handleMousePressed(battleChit, hex); } }); infoPanel = new InfoPanel(); contentPane.add(infoPanel, BorderLayout.NORTH); PlayerColor color = getClient().getColor(); if (color != null) { Color bgColor = color.getBackgroundColor(); contentPane.setBorder(BorderFactory.createLineBorder(bgColor)); } dicePanel = new DicePanel(); getContentPane().add(dicePanel, BorderLayout.SOUTH); // TODO using here the static method getMarkerName from // LegionServerSide is really not the cleanest way... infoText = gui.getOwningPlayerName() + ": " + LegionServerSide.getMarkerName(attackerMarkerId) + " (" + attackerMarkerId + ") attacks " + LegionServerSide.getMarkerName(defenderMarkerId) + " (" + defenderMarkerId + ") in " + engagement.getLocationLabel(); setTitle(getInfoText()); String instanceId = gui.getOwningPlayerName() + ": " + attackerMarkerId + "/" + defenderMarkerId + " (" + count + ")"; count++; net.sf.colossus.util.InstanceTracker.setId(this, instanceId); pack(); // saveWindow stuff must be called AFTER pack()... saveWindow = new SaveWindow(gui.getOptions(), "BattleBoard"); saveWindow.restore(this, new Point(0, 4 * Scale.get())); setVisible(true); // @TODO: perhaps those could be done earlier, but in previous code // (still in Client) they were done after BattleBoard instantiation, // so I keep them like that, for now. updatePhaseAndTurn(); setBattleMarkerLocation(false, "X" + engagement.getAttackingLegion().getEntrySide().ordinal()); setBattleMarkerLocation(true, "X" + engagement.getDefendingLegion().getEntrySide().ordinal()); reqFocus(); } private void handleMousePressed(GUIBattleChit battleChit, GUIBattleHex hex) { gui.resetStrikeNumbers(); // This checks only if its a chit of the active player // But that's ok, this here is called only if it's own phase boolean ownChit = (battleChit != null && getGame().getPlayerByTag( battleChit.getTag()).equals(getGame().getBattleActivePlayer())); boolean ownChit2 = (battleChit != null && battleChit.getBattleUnit() .getLegion().getPlayer().equals(getGame().getBattleActivePlayer())); assert ownChit == ownChit2 : "checks for 'is own chit' return different result!"; boolean isPickCarryOngoing = gui.isPickCarryOngoing(); if (isPickCarryOngoing) { if (battleChit != null && !ownChit) { gui.handlePickCarry(hex); } else { // not a chit, or at least not own chit } } else if (battleChit != null && ownChit) { actOnCritter(battleChit); } // No hits on friendly chits, so check map. else if (hex != null && hex.isSelected()) { actOnHex(hex.getHexModel()); } // No hits on selected hexes, so clean up. else { actOnMisclick(); } } private void actOnWindowClosingAttempt() { if (gui.getClient().isRemote() && gui.getGame().isGameOver()) { // silently close gui.getClient().cleanupBattle(); return; } if (alreadyConceded) { JOptionPane.showMessageDialog(this, "You did alrady concede, but it was not your battle phase!\n" + "Game waits for your opponent to finish his turn.", "Already conceded!", JOptionPane.INFORMATION_MESSAGE); return; } Legion myLegion = gui.getClient().getMyEngagedLegion(); // for not-involved players, closing battle map/board should not // effect anything, in particular not withdraw the player / close // the whole game etc. // For now, just set board invisible, so that whole cleanup stuff // can work as before. if (myLegion == null) { this.setVisible(false); return; } // okay, either attacker or defender legion is ours. boolean hasMyTitan = myLegion.hasTitan(); String buttonText = hasMyTitan ? "Concede and Withdraw" : "Concede"; String titleText = hasMyTitan ? "Really concede and withdraw?" : "Really concede battle?"; String messageText = "Do you want to concede this battle?"; if (hasMyTitan) { messageText = messageText + "\n" + "NOTE: Since your legion contains your Titan, conceding " + "this battle means you are eliminated from the game,\n" + "and attacker anyway will get same score has if he " + "had killed you!"; } if (!gui.getClient().isMyBattlePhase()) { messageText = messageText + "\n\nNote: " + "According to the rules, you can concede only during your " + "own battle turn,\ntherefore the active player needs to " + "finish his turn first before your concede takes effect!"; } String[] dialogOptions = new String[2]; dialogOptions[0] = buttonText; dialogOptions[1] = "Cancel"; int answer = JOptionPane.showOptionDialog(this, messageText, titleText, JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, dialogOptions, dialogOptions[1]); if (answer == -1 || answer == 1) { return; } if (!gui.getClient().isMyBattlePhase()) { alreadyConceded = true; } gui.getClient().concede(); } private void setBattleMarkerLocation(boolean isDefender, String hexLabel) { BattleHex hex = battleMap.getHexByLabel(hexLabel); battleMap.setBattleMarkerLocation(isDefender, hex); } private Client getClient() { return gui.getClient(); } private GameClientSide getGame() { return (GameClientSide)gui.getGame(); } // Handy shortcut because it's used frequently private boolean isFightPhase() { return getGame().getBattlePhase().isFightPhase(); } // Handy shortcut because it's used frequently private boolean isMovePhase() { return getGame().isBattlePhase(BattlePhase.MOVE); } private boolean isMyBattleTurn() { return gui.getOwningPlayer().equals(getGame().getBattleActivePlayer()); } private String getInfoText() { return infoText; } private void setupActions() { showTerrainHazardAction = new AbstractAction(showTerrainHazard) { public void actionPerformed(ActionEvent e) { new BattleTerrainHazardWindow(BattleBoard.this, gui, battleMap.getMasterHex()); } }; undoLastAction = new AbstractAction(undoLast) { public void actionPerformed(ActionEvent e) { if (!isMyBattleTurn()) { return; } if (isMovePhase()) { selectedCritterTag = -1; setWaitCursor(); gui.undoLastBattleMove(); } } }; undoAllAction = new AbstractAction(undoAll) { public void actionPerformed(ActionEvent e) { if (!isMyBattleTurn()) { return; } if (isMovePhase()) { selectedCritterTag = -1; setWaitCursor(); gui.undoAllBattleMoves(); } } }; doneWithPhaseAction = new AbstractAction(doneWithPhase) { public void actionPerformed(ActionEvent e) { if (!isMyBattleTurn()) { return; } if (isMovePhase()) { if (!gui.getClient().isAutoplayActive() && getGame().getBattleCS().anyOffboardCreatures() && !confirmLeavingCreaturesOffboard()) { return; } unselectAllHexes(); battleMap.unselectEntranceHexes(); String phase = getGame().getBattlePhase().toString(); infoPanel.setOwnPhaseDone(phase, false); setWaitCursor(); gui.getCallbackHandler().doneWithBattleMoves(); } else if (isFightPhase()) { unselectAllHexes(); /* // now called by client's doneWithStrike method: String phase = getGame().getBattlePhase().toString(); infoPanel.setOwnPhaseDone(phase); setWaitCursor(); */ battleMap.unselectEntranceHexes(); gui.resetStrikeNumbers(); gui.getCallbackHandler().doneWithStrikes(); } else { LOGGER.log(Level.SEVERE, "Bogus phase"); } } }; concedeBattleAction = new AbstractAction(concedeBattle) { public void actionPerformed(ActionEvent e) { String[] options = new String[2]; options[0] = "Yes"; options[1] = "No"; int answer = JOptionPane.showOptionDialog(BattleBoard.this, "Are you sure you wish to concede the battle?", "Confirm Concession?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[1]); if (answer == JOptionPane.YES_OPTION) { LOGGER.log(Level.INFO, gui.getOwningPlayerName() + " concedes the battle"); gui.getCallbackHandler().concede(); } } }; } private void setupTopMenu() { menuBar = new JMenuBar(); setJMenuBar(menuBar); // Phase menu items change by phase and will be set up later. phaseMenu = new JMenu("Phase"); phaseMenu.setMnemonic(KeyEvent.VK_P); helpMenu = new JMenu("Help"); helpMenu.setMnemonic(KeyEvent.VK_H); menuBar.add(phaseMenu); menuBar.add(helpMenu); } private void setupHelpMenu() { JMenuItem mi; mi = helpMenu.add(showTerrainHazardAction); mi.setMnemonic(KeyEvent.VK_T); mi.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0)); reqFocus(); } public void setupSummonMenu() { phaseMenu.removeAll(); reqFocus(); } public void setupRecruitMenu() { if (phaseMenu != null) { phaseMenu.removeAll(); } reqFocus(); } public void setupMoveMenu() { phaseMenu.removeAll(); JMenuItem mi; mi = phaseMenu.add(undoLastAction); mi.setMnemonic(KeyEvent.VK_U); mi.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, 0)); mi = phaseMenu.add(undoAllAction); mi.setMnemonic(KeyEvent.VK_A); mi.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0)); mi = phaseMenu.add(doneWithPhaseAction); mi.setMnemonic(KeyEvent.VK_D); mi.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0)); phaseMenu.addSeparator(); mi = phaseMenu.add(concedeBattleAction); mi.setMnemonic(KeyEvent.VK_C); if (isMyBattleTurn()) { enableDoneButton(); setDefaultCursor(); highlightMobileCritters(); reqFocus(); } else { disableDoneButton(); setWaitCursor(); } } void setupFightMenu() { phaseMenu.removeAll(); if (getClient().getMyEngagedLegion() == null) { // We are not involved - we can't do concede or done return; } JMenuItem mi; mi = phaseMenu.add(doneWithPhaseAction); mi.setMnemonic(KeyEvent.VK_D); mi.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0)); mi.setEnabled(isMyBattleTurn()); phaseMenu.addSeparator(); mi = phaseMenu.add(concedeBattleAction); mi.setMnemonic(KeyEvent.VK_C); // Let's keep this away until better solution (option) // to avoid accidental concede while chatting... // mi.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0)); if (isMyBattleTurn()) { enableDoneButton(); setDefaultCursor(); highlightCrittersWithTargets(); reqFocus(); } else { disableDoneButton(); setWaitCursor(); } } public void updatePhaseAndTurn() { String newPhaseName = getGame().getBattlePhase().toString(); if (isMyBattleTurn()) { enableDoneButton(); setDefaultCursor(); infoPanel.setOwnPhase(newPhaseName); } else { disableDoneButton(); setWaitCursor(); String doesWhat = getGame().getBattlePhase().getDoesWhat(); infoPanel.setForeignPhase(doesWhat); } infoPanel.turnPanel.advTurn(getGame().getBattleTurnNumber()); } private void setupIcon() { List<String> directories = new ArrayList<String>(); directories.add(Constants.defaultDirName + StaticResourceLoader.getPathSeparator() + Constants.imagesDirName); String[] iconNames = { Constants.battlemapIconImage, Constants.battlemapIconText + "-Name-" + Constants.battlemapIconTextColor, Constants.battlemapIconSubscript + "-Subscript-" + Constants.battlemapIconTextColor }; Image image = StaticResourceLoader.getCompositeImage(iconNames, directories, 60, 60); if (image == null) { LOGGER.log(Level.SEVERE, "ERROR: Couldn't find Colossus icon"); dispose(); } else { setIconImage(image); } } /** Return the BattleChit containing the given point, * or null if none does. */ private GUIBattleChit getBattleChitAtPoint(Point point) { // iterate through list backwards, so the topmost chit is found List<GUIBattleChit> chits = gui.getGUIBattleChits(); ListIterator<GUIBattleChit> it = chits.listIterator(chits.size()); while (it.hasPrevious()) { GUIBattleChit battleChit = it.previous(); if (battleChit.contains(point)) { return battleChit; } } return null; } public void alignChits(BattleHex battleHex) { GUIBattleHex hex = battleMap.getGUIHexByModelHex(battleHex); List<GUIBattleChit> battleChits = gui .getGUIBattleChitsInHex(battleHex); int numChits = battleChits.size(); if (numChits < 1) { hex.repaint(); return; } GUIBattleChit battleChit = battleChits.get(0); int chitscale = battleChit.getBounds().width; int chitscale4 = chitscale / 4; Point point = new Point(hex.findCenter()); // Cascade chits diagonally. int offset = ((chitscale * (1 + numChits))) / 4; point.x -= offset; point.y -= offset; battleMap.add(battleChit); battleChit.setLocation(point); for (int i = 1; i < numChits; i++) { point.x += chitscale4; point.y += chitscale4; battleChit = battleChits.get(i); battleChit.setLocation(point); } hex.repaint(); } private void alignChits(Set<BattleHex> battleHexes) { for (BattleHex battleHex : battleHexes) { alignChits(battleHex); } } /** Select all hexes containing critters eligible to move. */ public void highlightMobileCritters() { Set<BattleHex> set = getClient().findMobileCritterHexes(); unselectAllHexes(); battleMap.unselectEntranceHexes(); battleMap.selectHexes(set); battleMap.selectEntranceHexes(set); } private void highlightMoves(BattleCritter critter) { Set<BattleHex> set = getClient().showBattleMoves(critter); battleMap.unselectAllHexes(); battleMap.unselectEntranceHexes(); battleMap.selectHexes(set); } /** Select hexes containing critters that have valid strike targets. */ public void highlightCrittersWithTargets() { Set<BattleHex> set = getClient().findCrittersWithTargets(); unselectAllHexes(); battleMap.selectHexes(set); // XXX Needed? repaint(); } /** Highlight all hexes with targets that the critter can strike. */ private void highlightStrikes(BattleUnit battleUnit) { Set<BattleHex> set = getClient().findStrikes(battleUnit.getTag()); unselectAllHexes(); gui.resetStrikeNumbers(); battleMap.selectHexes(set); gui.setStrikeNumbers(battleUnit, set); // XXX Needed? repaint(); } /** Highlight all hexes to which carries could be applied */ public void highlightPossibleCarries(Set<BattleHex> set) { unselectAllHexes(); gui.resetStrikeNumbers(); battleMap.selectHexes(set); // XXX Needed? repaint(); } private void setWaitCursor() { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); } private void setDefaultCursor() { setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } /* Update infoPanel to make it visible that "Done" was sent to server * (otherwise, with long delays, users clicked the Done again, and again.. * This is also called by client by aiDoneWithStrikes, because that covers * the case where client "automagically" makes "Done" for the cases * where there is no strike or strikeback to do (in particular first * battle turn, when there is no opponent yet). * * @auto True: triggered by "no strikes to do", false: user clicked "Done" */ public void indicateStrikesDone(boolean auto) { String phase = getGame().getBattlePhase().toString(); infoPanel.setOwnPhaseDone(phase, auto); setWaitCursor(); } public void revertDoneIndicator() { // Re-set the phase label to be without the "(done)" part, // and re-enable the done button: updatePhaseAndTurn(); } private boolean confirmLeavingCreaturesOffboard() { String warnTitan = ""; if (isMyTitanOffboard()) { warnTitan = "\n\nWARNING: If you leave your Titan offboard, " + " you are eliminated from the game!"; } String[] options = new String[2]; options[0] = "Yes"; options[1] = "No"; int answer = JOptionPane.showOptionDialog(this, "Are you sure you want to leave creatures offboard?" + warnTitan, "Confirm Leaving Creatures Offboard?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[1]); return (answer == JOptionPane.YES_OPTION); } private boolean isMyTitanOffboard() { return gui.getGameClientSide().getBattleCS() .isTitanOffboard(gui.getOwningPlayer()); } private void actOnCritter(GUIBattleChit battleChit) { selectedCritterTag = battleChit.getTag(); // XXX Put selected chit at the top of the z-order. // Then getGUIHexByLabel(hexLabel).repaint(); if (isMovePhase()) { highlightMoves(battleChit.getBattleUnit()); } else if (isFightPhase()) { if (gui.isPickCarryOngoing()) { gui.getCallbackHandler().leaveCarryMode(); } highlightStrikes(battleChit.getBattleUnit()); } } private void actOnHex(BattleHex hex) { if (isMovePhase()) { if (selectedCritterTag != -1) { setWaitCursor(); gui.getCallbackHandler().doBattleMove(selectedCritterTag, hex); selectedCritterTag = -1; } } else if (isFightPhase()) { if (selectedCritterTag != -1) { gui.getCallbackHandler().strike(selectedCritterTag, hex); selectedCritterTag = -1; } } } private void actOnMisclick() { if (isMovePhase()) { selectedCritterTag = -1; highlightMobileCritters(); } else if (isFightPhase()) { selectedCritterTag = -1; if (gui.isPickCarryOngoing()) { gui.getCallbackHandler().leaveCarryMode(); } highlightCrittersWithTargets(); } } public void actOnPendingBattleMoveOver() { if (isMyBattleTurn()) { setDefaultCursor(); } else { setWaitCursor(); } highlightMobileCritters(); } public void rescale() { battleMap.setupHexes(); int chitScale = 4 * Scale.get(); for (GUIBattleChit battleChit : gui.getGUIBattleChits()) { battleChit.rescale(chitScale); } alignChits(battleMap.getAllHexes()); dicePanel.rescale(); setSize(getPreferredSize()); pack(); repaint(); } public void reqFocus() { if (gui.getOptions().getOption(Options.stealFocus)) { requestFocus(); toFront(); } } private class TurnPanel extends JPanel { private final JLabel[] turn; private int turnNumber; private TurnPanel() { this(getGame().getVariant().getMaxBattleTurns()); } private TurnPanel(int MAXBATTLETURNS) { super(new GridLayout((MAXBATTLETURNS + 1) % 8 + 1, 0)); turn = new JLabel[MAXBATTLETURNS + 1]; // Create Special labels for Recruitment turns int[] REINFORCEMENTTURNS = getGame().getVariant() .getReinforcementTurns(); for (int j : REINFORCEMENTTURNS) { turn[j - 1] = new JLabel((j) + "+", SwingConstants.CENTER); resetTurn(j); // Set thin Border } // make the final "extra" turn label to show "time loss" turn[turn.length - 1] = new JLabel("Loss", SwingConstants.CENTER); resetTurn(turn.length); // Create remaining labels for (int i = 0; i < turn.length; i++) { if (turn[i] == null) { turn[i] = new JLabel(Integer.toString(i + 1), SwingConstants.CENTER); resetTurn(i + 1); // Set thin Borders } } turnNumber = 0; for (JLabel label : turn) { this.add(label); } } private void advTurn(int newturn) { if (turnNumber > 0) { resetTurn(turnNumber); } setTurn(newturn); } private void resetTurn(int newTurn) { setBorder(turn[newTurn - 1], 1); } private void setTurn(int newTurn) { if (isMyBattleTurn()) { setBorder(turn[newTurn - 1], 5); } else { setBorder(turn[newTurn - 1], 3); } turnNumber = newTurn; } private void setBorder(JLabel turn, int thick) { if (thick == 3) { turn.setBorder(BorderFactory.createLineBorder(Color.GRAY, 3)); } else { if (thick == 5) { turn.setBorder(BorderFactory .createLineBorder(Color.RED, 5)); } else { turn.setBorder(BorderFactory.createLineBorder(Color.BLACK)); } } } } // class TurnPanel private class InfoPanel extends JPanel { private final TurnPanel turnPanel; private final JButton doneButton; private final JLabel phaseLabel; // since inner class most methods can be private. private InfoPanel() { super(); setLayout(new java.awt.BorderLayout()); Box westBox = new Box(BoxLayout.X_AXIS); doneButton = new JButton(doneWithPhaseAction); phaseLabel = new JLabel("- phase -"); westBox.add(Box.createHorizontalStrut(20)); westBox.add(phaseLabel); westBox.add(Box.createHorizontalStrut(20)); westBox.add(doneButton); add(westBox, BorderLayout.WEST); turnPanel = new TurnPanel(); add(turnPanel, BorderLayout.CENTER); } private void setOwnPhase(String s) { phaseLabel.setText(s); doneButton.setEnabled(true); } private void setOwnPhaseDone(String s, boolean auto) { phaseLabel.setText(s + " (" + (auto ? "auto-" : "") + "done)"); doneButton.setEnabled(false); } private void setForeignPhase(String doesWhat) { String name = getGame().getBattleActivePlayer().getName(); phaseLabel.setText(name + " " + doesWhat); doneButton.setEnabled(false); } private void disableDoneButton() { doneButton.setEnabled(false); } private void enableDoneButton() { doneButton.setEnabled(true); } } private void enableDoneButton() { infoPanel.enableDoneButton(); } private void disableDoneButton() { infoPanel.disableDoneButton(); } public void unselectAllHexes() { battleMap.unselectAllHexes(); } public void unselectHex(BattleHex hex) { battleMap.unselectHex(hex); } public void actOnHitsSet(BattleHex battleHex) { // on the active client it will happen anyway due to highlight changes if (!isMyBattleTurn()) { repaintHex(battleHex); } } private void repaintHex(BattleHex battleHex) { GUIBattleHex hex = battleMap.getGUIHexByModelHex(battleHex); hex.repaint(); } public void addDiceResults(String strikerDesc, String targetDesc, int targetNumber, List<String> rolls) { if (rolls.size() == 0) { return; } dicePanel.addValues("Battle Phase " + getGame().getBattleTurnNumber(), getGame().getBattleActivePlayer().getName(), strikerDesc, targetDesc, targetNumber, rolls); dicePanel.showLastRoll(); } // TODO get rid of this, is is only introduced for refactoring purposes public BattleHex getBattleHexByLabel(String hexLabel) { return battleMap.getHexByLabel(hexLabel); } @Override public String toString() { return "BattleBoard for: " + infoText; } @Override public void dispose() { if (saveWindow != null) { saveWindow.save(this); } super.dispose(); } }