package net.sf.colossus.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.GraphicsDevice;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.undo.UndoManager;
import net.sf.colossus.client.Client;
import net.sf.colossus.client.GameClientSide;
import net.sf.colossus.client.IClientGUI;
import net.sf.colossus.client.IOracle;
import net.sf.colossus.client.InactivityWatchdog;
import net.sf.colossus.client.LegionClientSide;
import net.sf.colossus.common.Constants;
import net.sf.colossus.common.IOptions;
import net.sf.colossus.common.Options;
import net.sf.colossus.common.WhatNextManager;
import net.sf.colossus.common.WhatNextManager.WhatToDoNext;
import net.sf.colossus.game.BattleCritter;
import net.sf.colossus.game.BattleUnit;
import net.sf.colossus.game.EntrySide;
import net.sf.colossus.game.Game;
import net.sf.colossus.game.Legion;
import net.sf.colossus.game.Phase;
import net.sf.colossus.game.Player;
import net.sf.colossus.game.PlayerColor;
import net.sf.colossus.game.Proposal;
import net.sf.colossus.guiutil.KDialog;
import net.sf.colossus.util.CollectionHelper;
import net.sf.colossus.util.Predicate;
import net.sf.colossus.variant.BattleHex;
import net.sf.colossus.variant.CreatureType;
import net.sf.colossus.variant.MasterHex;
import net.sf.colossus.variant.Variant;
import net.sf.colossus.webclient.WebClient;
@SuppressWarnings("serial")
public class ClientGUI implements IClientGUI, GUICallbacks
{
private static final Logger LOGGER = Logger.getLogger(ClientGUI.class
.getName());
private final boolean LOG_CG = false;
/* This is a number of seconds to wait for connection check
* confirmation message before assuming connection is broken and
* displaying a message telling so.
*/
private final static int CONN_CHECK_TIMEOUT = 5;
private final static String IA_CLOSE_BUTTON_TEXT = "Close";
private final Object connectionCheckMutex = new Object();
private Timer connectionCheckTimer;
private long lastConnectionCheckPackageSent = -1;
// TODO the naming of these classes is confusing, they should be clearly named
// as dialogs
private MasterBoard board;
private StatusScreen statusScreen;
private CreatureCollectionView caretakerDisplay;
private MovementDie movementDie;
private EngagementResults engagementResults;
private AutoInspector autoInspector;
private EventViewer eventViewer;
private PreferencesWindow preferencesWindow;
private LogWindow logWindow;
private ConnectionLogWindow connectionLogWindow;
private PickCarry pickCarryDialog = null;
// For negotiation. (And AI battle.)
private Negotiate negotiate;
private ReplyToProposal replyToProposal;
private BattleBoard battleBoard;
private WebClient webClient = null;
private boolean startedByWebClient = false;
private String gameId = null;
// for the case if we have to restore the webclient
private String wcUsername = null;
private String wcPassword = null;
/**
* The object which handles what to do next when a game is going to end
*/
private final WhatNextManager whatNextManager;
/**
* Stack of legion marker ID's, to allow multiple levels of undo for
* splits, moves, and recruits.
* (for battle actions, the Strings are not actually marker ID's,
* it's battle hex ID's there instead).
*
* TODO it would probably be good to have a full Command pattern here, similar
* to Swing's {@link UndoManager} stuff. In the GUI client we could/should
* probably just use that. A list of objects (which are mostly the string
* identifiers of something) isn't that safe.
*/
private final LinkedList<Object> undoStack = new LinkedList<Object>();
private final LinkedList<PendingMove> pendingMoves = new LinkedList<PendingMove>();
private final Set<MasterHex> pendingMoveHexes = new HashSet<MasterHex>();
private boolean recoveredFromMoveNak = false;
private final List<GUIBattleChit> battleChits = new ArrayList<GUIBattleChit>();
/** Information on the current moving legion. */
private Legion mover;
/** the parent frame for secondary windows */
private JFrame secondaryParent = null;
private int replayLastTurn = -1;
private int replayMaxTurn = 0;
// TODO change to enums...
private int viewMode;
private int recruitChitMode;
private int legionMoveConfirmationMode;
private int nextSplitClickMode;
private boolean gameOverMessageAlreadyShown = false;
protected final Client client;
private InactivityWatchdog watchdog = null;
private int inactivityWarningInterval = -1;
// for things other GUI components need to inquire,
// use the Oracle (on the long run, I guess there will be the
// GameClientSide class behind it...)
protected final IOracle oracle;
// Per-client and per-player options.
private final Options options;
public ClientGUI(Client client, Options options,
WhatNextManager whatNextMgr)
{
this.client = client;
this.oracle = client;
this.options = options;
this.whatNextManager = whatNextMgr;
}
public void setStartedByWebClient(boolean byWebClient)
{
this.startedByWebClient = byWebClient;
}
public boolean getStartedByWebClient()
{
return this.startedByWebClient;
}
public void setWebClient(WebClient wc, int inactivityWarningInterval,
String gameId, String username, String password)
{
this.webClient = wc;
this.inactivityWarningInterval = inactivityWarningInterval;
this.gameId = gameId;
this.wcUsername = username;
this.wcPassword = password;
}
public String getGameId()
{
return this.gameId;
}
public void clearWebClient()
{
this.webClient = null;
}
public void setClientInWebClientNull()
{
if (webClient != null)
{
webClient.setGameClient(null);
webClient = null;
}
}
// TODO still needed?
public MasterBoard getBoard()
{
return board;
}
public boolean hasBoard()
{
return board != null;
}
public Client getClient()
{
return client;
}
public IOracle getOracle()
{
return client;
}
public Game getGame()
{
return client.getGame();
}
public GameClientSide getGameClientSide()
{
return (GameClientSide)client.getGame();
}
public IOptions getOptions()
{
return options;
}
boolean isReplayOngoing()
{
return getClient().isReplayOngoing();
}
boolean isRedoOngoing()
{
return getClient().isRedoOngoing();
}
/*
* If webclient is just hidden, bring it back;
* if it had been used, ask whether to restore;
* Otherwise just do nothing
*/
public void handleWebClientRestore()
{
if (this.webClient != null)
{
// was only Hidden, so bring it up without asking
this.webClient.setVisible(true);
}
else
{
// webclient never used (local game), or explicitly closed
// - don't bother user with it
// If he now said quit -- he probably wants quit.
// if he now used close or new game, he can get to web client
// from GetPlayers dialog.
}
}
public void showWebClient()
{
if (this.webClient == null)
{
this.webClient = new WebClient(whatNextManager, null, -1,
this.wcUsername, this.wcPassword);
this.webClient.setGameClient(client);
}
else
{
this.webClient.setVisible(true);
this.webClient.toFront();
}
}
public void logPerhaps(String message)
{
if (LOG_CG)
{
LOGGER.fine("CG: " + message);
}
}
public void initBoard()
{
logPerhaps("initBoard()");
String viewModeName = options.getStringOption(Options.viewMode);
viewMode = options.getNumberForViewMode(viewModeName);
String rcMode = options
.getStringOption(Options.showRecruitChitsSubmenu);
// TODO this can probably be dropped by now.
if (rcMode == null || rcMode.equals(""))
{
// not set: convert from old "showAllRecruitChits" option
boolean showAll = options.getOption(Options.showAllRecruitChits);
if (showAll)
{
rcMode = Options.showRecruitChitsAll;
}
else
{
rcMode = Options.showRecruitChitsStrongest;
}
// initialize new option
options.setOption(Options.showRecruitChitsSubmenu, rcMode);
// clean up obsolete option from cfg file
options.removeOption(Options.showAllRecruitChits);
}
recruitChitMode = options.getNumberForRecruitChitSelection(rcMode);
String mcMode = options
.getStringOption(Options.legionMoveConfirmationSubMenu);
if (mcMode == null || mcMode.equals(""))
{
mcMode = Options.legionMoveConfirmationNoUnvisitedMove;
// initialize new option
options.setOption(Options.legionMoveConfirmationSubMenu, mcMode);
// clean up obsolete option from cfg file
options.removeOption(Options.confirmNoMove);
}
legionMoveConfirmationMode = options
.getNumberForLegionMoveConfirmation(mcMode);
String nextSplitMode = options
.getStringOption(Options.nextSplitSubMenu);
if (nextSplitMode == null || nextSplitMode.equals(""))
{
nextSplitMode = Options.nextSplitLeftClick;
// initialize new option
options.setOption(Options.nextSplitSubMenu, nextSplitMode);
// clean up obsolete option from cfg file
}
nextSplitClickMode = options.getNumberForNextSplit(nextSplitMode);
ensureEdtSetupClientGUI();
if (startedByWebClient)
{
if (webClient != null)
{
webClient.notifyComingUp(true);
}
}
}
public void actOnGameStartingFailed()
{
logPerhaps("actOnGameStartingFailed()");
if (webClient != null)
{
webClient.notifyComingUp(false);
}
}
/**
* Ensure that setupClientGUI() is run inside the EDT
*/
private void ensureEdtSetupClientGUI()
{
logPerhaps("ensureEdtSetupClientGUI()");
if (SwingUtilities.isEventDispatchThread())
{
setupClientGUI();
}
else
{
try
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
setupClientGUI();
}
});
}
catch (InvocationTargetException e)
{
LOGGER.log(Level.SEVERE,
"Failed to run setupClientGUI with invokeAndWait(): ", e);
}
catch (InterruptedException e2)
{
LOGGER.log(Level.SEVERE,
"Failed to run setupClientGUI with invokeAndWait(): ", e2);
}
}
}
/**
* Called via ensureEdtSetupClientGUI() when server sends all clients
* the initBoard command.
*/
public void setupClientGUI()
{
logPerhaps("setupClientGUI()");
/*
disposeEventViewer();
disposePreferencesWindow();
disposeEngagementResults();
disposeInspector();
disposeCaretakerDisplay();
disposeLogWindow();
disposeMasterBoard();
*/
int scale = options.getIntOption(Options.scale);
if (scale == -1)
{
scale = 15;
options.setOption(Options.scale, scale);
options.saveOptions();
}
Scale.set(scale);
board = new MasterBoard(client, this);
if (options.getOption(Options.inactivityTimeout))
{
inactivityWarningInterval = InactivityWatchdog.DEFAULT_INTERVAL;
}
// TODO: remove this when debug/development phase is over
if (getOwningPlayer().getName().equals("localwatchdogtest"))
{
this.inactivityWarningInterval = InactivityWatchdog.DEBUG_INTERVAL;
}
// by default (local game) those inactivity values are all -1,
// they get a value only from webclient; see setWebClient(...)
if (client.needsWatchdog() && this.inactivityWarningInterval > 0)
{
LOGGER.fine("Creating an inactivityWatchdog with interval "
+ this.inactivityWarningInterval + " for player "
+ getOwningPlayerName());
watchdog = new InactivityWatchdog(client,
this.inactivityWarningInterval);
watchdog.start();
}
else
{
LOGGER
.fine("No watchdog; interval = " + inactivityWarningInterval);
}
initEventViewer();
initShowEngagementResults();
initPreferencesWindow();
showOrHideAutoInspector(options.getOption(Options.showAutoInspector));
logWindow = new LogWindow(options, Logger.getLogger(""),
options.getOption(Options.showLogWindow));
// showOrHideLogWindow(options.getOption(Options.showLogWindow));
showOrHideConnectionLogWindow(options
.getOption(Options.showConnectionLogWindow));
showOrHideCaretaker(options.getOption(Options.showCaretaker));
setupGUIOptionListeners();
syncCheckboxes();
board.maybeRequestFocusAndToFront();
}
public void setChosenDevice(GraphicsDevice chosen)
{
logPerhaps("setChosenDevice(GraphicsDevice chosen)");
if (chosen != null)
{
secondaryParent = new JFrame(chosen.getDefaultConfiguration());
disposeStatusScreen();
updateStatusScreen();
disposeCaretakerDisplay();
boolean bval = options.getOption(Options.showCaretaker);
showOrHideCaretaker(bval);
}
}
private boolean ensureEdtNewBattleBoard()
{
logPerhaps("ensureEdtNewBattleBoard()");
if (SwingUtilities.isEventDispatchThread())
{
battleBoard = doNewBattleBoard();
}
else
{
// @TODO: use invokeLater() instead of invokeAndWait() ?
//
// Right now I don't dare to use invokeLater() - this way here
// it preserves the execution order as it was without EDT,
// but GUI stuff is one in EDT so we are safe from exceptions.
Exception e = null;
try
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
battleBoard = doNewBattleBoard();
}
});
}
/*
catch (InvocationTargetException e1)
{
e = e1;
}
catch (InterruptedException e2)
{
e = e2;
}
*/
catch (Exception e3)
{
client.logMsgToServer("E",
"invokeAndWait caused unexpected exception "
+ e3.getClass().toString());
e = e3;
}
if (e != null)
{
String errorMessage = "Caught exception for doNewBattleBoard in "
+ "invokeAndWait(): ";
client.logMsgToServer("E", errorMessage);
LOGGER.log(Level.SEVERE, errorMessage, e);
}
}
return battleBoard != null ? true : false;
}
public void actOnInitBattle()
{
logPerhaps("actOnInitBattle()");
if (ensureEdtNewBattleBoard())
{
client.logMsgToServer("I", "Creating BattleBoard completed.");
}
else
{
if (ensureEdtNewBattleBoard())
{
client.logMsgToServer("W",
"Creating BattleBoard completed on 2nd attempt.");
}
else
{
client.logMsgToServer("E",
"Creating BattleBoard failed also in 2nd attempt.");
JOptionPane
.showMessageDialog(
board,
""
+ "Creating the BattleBoard failed 2 times, I am giving up! Please inform admin about this."
+ "\n\n"
+ "The application will probably not recover from this situation; try a suspend and resume,\n"
+ "but it would be best if you totally restart Colossus in between that.",
"Bringing up BattleBoard failed!",
JOptionPane.INFORMATION_MESSAGE);
}
}
}
private BattleBoard doNewBattleBoard()
{
logPerhaps("doNewBattleBoard()");
if (battleBoard != null)
{
LOGGER.warning("Old BattleBoard still in place? Disposing it.");
battleBoard.dispose();
battleBoard = null;
}
try
{
battleBoard = new BattleBoard(this, getGame().getEngagement());
return battleBoard;
}
catch (Throwable e)
{
LOGGER.log(Level.SEVERE, "Caught throwable "
+ e.getClass().toString(), e);
return null;
}
}
private KDialog lastDialog = null;
private void disposeLastInactivityDialog()
{
if (lastDialog != null)
{
lastDialog.dispose();
lastDialog = null;
}
}
/* must be called inside EDT */
private void showInactivityDialog(String title, String text, Color color)
{
getMapOrBoardFrame().requestFocus();
getMapOrBoardFrame().toFront();
lastDialog = new KDialog(getMapOrBoardFrame(), title, true);
Container dialogPanel = lastDialog.getContentPane();
dialogPanel.setLayout(new BorderLayout());
dialogPanel.setBackground(Color.yellow);
final JTextArea textPanel = new JTextArea(text, 7, 40);
textPanel.setEditable(false);
textPanel.setBackground(color);
dialogPanel.add(textPanel, BorderLayout.CENTER);
JLabel warningLabel = new JLabel("");
warningLabel.setHorizontalAlignment(JLabel.CENTER);
dialogPanel.add(warningLabel, BorderLayout.NORTH);
final JButton inactivityOkButton = new JButton(
"OK, I continue playing.");
inactivityOkButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
// All this is relevant only for "AI took over for you" dialog
if (inactivityOkButton.getText().equals(IA_CLOSE_BUTTON_TEXT))
{
disposeLastInactivityDialog();
}
else
{
boolean canClose = watchdog.userRequestsControlBack();
if (canClose)
{
disposeLastInactivityDialog();
}
else
{
textPanel.append("\n\nAll right. Please wait...");
inactivityOkButton.setText(IA_CLOSE_BUTTON_TEXT);
textPanel.setBackground(Color.gray);
}
}
}
});
dialogPanel.add(inactivityOkButton, BorderLayout.SOUTH);
lastDialog.pack();
lastDialog.centerOnScreen();
lastDialog.setVisible(true);
getMapOrBoardFrame().requestFocus();
getMapOrBoardFrame().toFront();
}
public void displayInactivityDialogEnsureEDT(final String title,
final String text, final Color color)
{
if (SwingUtilities.isEventDispatchThread())
{
disposeLastInactivityDialog();
showInactivityDialog(title, text, color);
}
else
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
disposeLastInactivityDialog();
showInactivityDialog(title, text, color);
}
});
}
WhatNextManager.sleepFor(2000);
}
public void setStrikeNumbers(BattleUnit striker, Set<BattleHex> targetHexes)
{
logPerhaps("setStrikeNumbers(BattleUnit striker, "
+ "Set<BattleHex> targetHexes)");
for (BattleHex targetHex : targetHexes)
{
GUIBattleChit targetChit = getGUIBattleChit(targetHex);
BattleUnit target = targetChit.getBattleUnit();
int strikeNr = getGame().getBattleStrike().getStrikeNumber(
striker, target);
targetChit.setStrikeNumber(strikeNr);
}
}
/** reset all strike numbers on chits */
public void resetStrikeNumbers()
{
logPerhaps("resetStrikeNumbers()");
for (GUIBattleChit battleChit : getGUIBattleChits())
{
battleChit.setStrikeNumber(0);
battleChit.setStrikeDice(0);
}
}
// TODO create/dispose status screen only on request, in this call here
// only call the update (if it exists)
public void updateStatusScreen()
{
logPerhaps("updateStatusScreen()");
if (client.getNumPlayers() < 1)
{
// Called too early.
return;
}
if (options.getOption(Options.showStatusScreen))
{
if (statusScreen != null)
{
statusScreen.updateStatusScreen();
}
else
{
statusScreen = new StatusScreen(getPreferredParent(), this,
options);
}
}
else
{
// It seems board might be null in one AI client when reloading
// a game and forceViewBoard set.... so, added null check again
if (board != null)
{
board.adjustCheckboxIfNeeded(Options.showStatusScreen, false);
if (statusScreen != null)
{
statusScreen.dispose();
}
this.statusScreen = null;
}
}
// XXX Should be called somewhere else, just once.
setupPlayerLabel();
}
boolean quitAlreadyTried = false;
public void menuCloseBoard()
{
logPerhaps("menuCloseBoard()");
clearUndoStack();
doSetWhatToDoNext(WhatToDoNext.GET_PLAYERS_DIALOG, false);
client.disposeClientOriginated();
}
public void menuQuitGame()
{
logPerhaps("menuQuitGame()");
// Note that if this called from webclient, webclient has already
// beforehand called client to set webclient to null :)
if (webClient != null)
{
webClient.dispose();
webClient = null;
}
// as a fallback/safety: if the close/dispose chain does not work,
// on 2nd attempt directly do System.exit() so that user can somehow
// get rid of the game "cleanly"...
if (quitAlreadyTried)
{
JOptionPane.showMessageDialog(getMapOrBoardFrame(),
"Arggh!! Seems the standard Quit procedure does not work.\n"
+ "Doing System.exit() now the hard way.",
"Proper quitting failed!", JOptionPane.INFORMATION_MESSAGE);
System.exit(1);
}
quitAlreadyTried = true;
doSetWhatToDoNext(WhatToDoNext.QUIT_ALL, true);
client.notifyServer();
}
// Used now only by MasterBoard
void askNewCloseQuitCancel(JFrame frame, boolean fromBattleBoard)
{
logPerhaps("askNewCloseQuitCancel(JFrame frame, "
+ "boolean fromBattleBoard)");
String[] dialogOptions = new String[4];
dialogOptions[0] = "New Game";
dialogOptions[1] = "Quit";
dialogOptions[2] = "Close";
dialogOptions[3] = "Cancel";
int answer = JOptionPane
.showOptionDialog(
frame,
"Choose one of: Play another game, Quit, Close just this board, or Cancel",
"Play another game?", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE, null, dialogOptions,
dialogOptions[3]);
frame = null;
if (answer == -1 || answer == 3)
{
return;
}
else
{
if (fromBattleBoard)
{
client.concede();
return;
}
}
if (answer == 0)
{
menuNewGame();
}
else if (answer == 1)
{
menuQuitGame();
}
else if (answer == 2)
{
client.disposeClientOriginated();
}
}
/** When user requests it from File menu, this method here
* requests the server to send us a confirmation package,
* to confirm that connection is still alive and ok.
*/
void checkServerConnection()
{
logPerhaps("checkServerConnection()");
if (client.isSctAlreadyDown())
{
JOptionPane.showMessageDialog(getMapOrBoardFrame(),
"No point to send check message - we know already that "
+ " the socket connection is in 'down' state!",
"Useless attempt to check connection",
JOptionPane.INFORMATION_MESSAGE);
return;
}
Runnable checker = new Runnable()
{
public void run()
{
synchronized (connectionCheckMutex)
{
initiateConnectionCheck();
}
}
};
new Thread(checker).start();
}
private void initiateConnectionCheck()
{
logPerhaps("initiateConnectionCheck()");
connectionCheckTimer = new Timer(1000 * CONN_CHECK_TIMEOUT,
new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
timeoutAbortsConnectionCheck();
}
});
lastConnectionCheckPackageSent = new Date().getTime();
connectionCheckTimer.start();
LOGGER.info("Client for player " + getOwningPlayer().getName()
+ " checking server connection (sending request)");
client.doCheckServerConnection();
}
public void serverConfirmsConnection()
{
logPerhaps("serverConfirmsConnection()");
synchronized (connectionCheckMutex)
{
LOGGER.info("Client for player " + getOwningPlayer().getName()
+ " received confirmation that connection is OK.");
finishServerConnectionCheck(true);
}
}
public void timeoutAbortsConnectionCheck()
{
logPerhaps("timeoutAbortsConnectionCheck()");
synchronized (connectionCheckMutex)
{
LOGGER.info("Client for player " + getOwningPlayer().getName()
+ " - timeout reached, stopping now to wait for the reply.");
finishServerConnectionCheck(false);
}
}
/** Cleanup everything related to the serverConnectionCheck timer,
* and show a message telling whether it went ok or not.
*
* Called by either serverConfirmsConnection() or
* timeoutAbortsConnectionCheck(), which both synchronize on the
* connectionCheckMutex.
*
*/
private void finishServerConnectionCheck(boolean success)
{
logPerhaps("finishServerConnectionCheck(boolean success)");
if (connectionCheckTimer == null)
{
// race - the other one came nearly same time, and comes now
// after the first one left this synchronize'd method.
return;
}
if (connectionCheckTimer.isRunning())
{
connectionCheckTimer.stop();
}
connectionCheckTimer = null;
if (success)
{
long responseReceivedTime = new Date().getTime();
long roundTripTime = responseReceivedTime
- lastConnectionCheckPackageSent;
JOptionPane
.showMessageDialog(getMapOrBoardFrame(),
"Received confirmation from server after " + roundTripTime
+ " ms - connection to " + "server is ok!",
"Connection check succeeded.",
JOptionPane.INFORMATION_MESSAGE);
}
else
{
JOptionPane.showMessageDialog(getMapOrBoardFrame(),
"Did not receive confirmation message from server within "
+ CONN_CHECK_TIMEOUT + " seconds - connection to "
+ "server is probably broken, or something hangs.",
"Connection check failed!", JOptionPane.ERROR_MESSAGE);
}
}
/** When user requests it from File menu, this method here
* requests the server to send requests to all clients, and
* returns their responses to us.
*/
void checkAllConnections()
{
logPerhaps("checkAllServerConnections()");
if (client.isSctAlreadyDown())
{
JOptionPane.showMessageDialog(getMapOrBoardFrame(),
"No point to request 'all connections check' - we know already that "
+ " the socket connection is in 'down' state!",
"Useless attempt to check connection",
JOptionPane.INFORMATION_MESSAGE);
return;
}
Runnable checker = new Runnable()
{
public void run()
{
synchronized (connectionCheckMutex)
{
initiateAllConnectionsCheck();
}
}
};
new Thread(checker).start();
}
private void initiateAllConnectionsCheck()
{
logPerhaps("initiateAllConnectionsCheck()");
/*
connectionCheckTimer = new Timer(1000 * CONN_CHECK_TIMEOUT,
new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
timeoutAbortsConnectionCheck();
}
});
lastConnectionCheckPackageSent = new Date().getTime();
connectionCheckTimer.start();
*/
LOGGER.info("Client for player " + getOwningPlayer().getName()
+ " requesting response from all clients (sending request)");
client.doCheckAllConnections(getOwningPlayerName());
}
private void doSetWhatToDoNext(WhatToDoNext whatToDoNext,
boolean triggerQuitTimer)
{
logPerhaps("doSetWhatToDoNext(WhatToDoNext whatToDoNext, "
+ "boolean triggerQuitTimer)");
whatNextManager.setWhatToDoNext(whatToDoNext, triggerQuitTimer);
}
private void doSetWhatToDoNext(WhatToDoNext whatToDoNext, String loadFile)
{
logPerhaps("doSetWhatToDoNext(WhatToDoNext whatToDoNext, "
+ "String loadFile)");
whatNextManager.setWhatToDoNext(whatToDoNext, loadFile, true);
}
// Used by File=>Close and Window closing
private void setWhatToDoNextForClose()
{
logPerhaps("setWhatToDoNextForClose()");
if (startedByWebClient)
{
doSetWhatToDoNext(WhatToDoNext.START_WEB_CLIENT, false);
}
else if (client.isRemote())
{
// Remote clients get back to Network Client dialog
doSetWhatToDoNext(WhatToDoNext.NET_CLIENT_DIALOG, false);
}
else
{
doSetWhatToDoNext(WhatToDoNext.GET_PLAYERS_DIALOG, false);
}
}
public void menuNewGame()
{
logPerhaps("menuNewGame()");
if (webClient != null)
{
webClient.dispose();
webClient = null;
}
setWhatToDoNextForClose();
client.notifyServer();
}
public void menuLoadGame(String filename)
{
logPerhaps("menuLoadGame(String filename)");
if (webClient != null)
{
webClient.dispose();
webClient = null;
}
doSetWhatToDoNext(WhatToDoNext.LOAD_GAME, filename);
client.notifyServer();
}
void menuSaveGame(String filename)
{
if (client.isRemote())
{
// In practice this should never happen, because in remote
// clients the File=>Save menu actions should not be visible
// at all. But who knows...
JOptionPane
.showMessageDialog(
getMapOrBoardFrame(),
"The variable 'localServer' is null, which means you are "
+ "playing with a remote client. How on earth did you manage "
+ "to trigger a File=>Save action?\nAnyway, from a remote "
+ "client I can't do File=>Save for you...",
"Save Game not available on remote client",
JOptionPane.ERROR_MESSAGE);
}
else
{
client.locallyInitiateSaveGame(filename);
}
}
void menuSuspendGame(boolean save)
{
client.initiateSuspend(save);
}
private void setupPlayerLabel()
{
logPerhaps("setupPlayerLabel()");
if (board != null)
{
board.setupPlayerLabel();
}
}
public void highlightEngagements()
{
logPerhaps("highlightEngagements()");
if (isMyTurn())
{
board.maybeRequestFocusAndToFront();
}
board.highlightEngagements();
}
private JFrame getPreferredParent()
{
if ((secondaryParent == null) && (board != null))
{
return board.getFrame();
}
return secondaryParent;
}
/**
* The view mode as set in the game options, without applying
* 'locally only own' client option
* @return
*/
public int getRawViewMode()
{
return viewMode;
}
/**
* The view mode after applying the 'locally only own' setting
*/
public int getEffectiveViewMode()
{
if (options.getOption(Options.localOnlyOwn))
{
return Options.viewableOwnNum;
}
return viewMode;
}
public int getRecruitChitMode()
{
return recruitChitMode;
}
public int getLegionMoveConfirmationMode()
{
return legionMoveConfirmationMode;
}
public int getNextSplitClickMode()
{
return nextSplitClickMode;
}
/*
* Trigger side effects after changing an option value.
*
* TODO now that there are listeners, many of the other classes could listen to the
* options relevant to them instead of dispatching it all through the Client class.
*/
private void setupGUIOptionListeners()
{
logPerhaps("setupGUIOptionListeners()");
GUIHex.setAntialias(options.getOption(Options.antialias));
options.addListener(Options.antialias, new IOptions.Listener()
{
@Override
public void booleanOptionChanged(String optname, boolean oldValue,
boolean newValue)
{
GUIHex.setAntialias(newValue);
options.setOption(Options.antialias, newValue);
repaintAllWindows();
}
});
GUIHex.setOverlay(options.getOption(Options.useOverlay));
options.addListener(Options.useOverlay, new IOptions.Listener()
{
@Override
public void booleanOptionChanged(String optname, boolean oldValue,
boolean newValue)
{
GUIHex.setOverlay(newValue);
options.setOption(Options.useOverlay, newValue);
if (board != null)
{
board.repaintAfterOverlayChanged();
}
}
});
options.addListener(Options.showRecruitChitsSubmenu,
new IOptions.Listener()
{
@Override
public void stringOptionChanged(String optname,
String oldValue, String newValue)
{
recruitChitMode = options
.getNumberForRecruitChitSelection(newValue);
}
});
options.addListener(Options.legionMoveConfirmationSubMenu,
new IOptions.Listener()
{
@Override
public void stringOptionChanged(String optname,
String oldValue, String newValue)
{
legionMoveConfirmationMode = options
.getNumberForLegionMoveConfirmation(newValue);
}
});
options.addListener(Options.nextSplitSubMenu, new IOptions.Listener()
{
@Override
public void stringOptionChanged(String optname, String oldValue,
String newValue)
{
nextSplitClickMode = options.getNumberForNextSplit(newValue);
}
});
CreatureType.setNoBaseColor(options.getOption(Options.noBaseColor));
options.addListener(Options.noBaseColor, new IOptions.Listener()
{
@Override
public void booleanOptionChanged(String optname, boolean oldValue,
boolean newValue)
{
CreatureType.setNoBaseColor(newValue);
options.setOption(Options.noBaseColor, newValue);
net.sf.colossus.util.StaticResourceLoader.purgeImageCache();
repaintAllWindows();
}
});
GUIBattleChit.setUseColoredBorders(options
.getOption(Options.useColoredBorders));
options.addListener(Options.useColoredBorders, new IOptions.Listener()
{
@Override
public void booleanOptionChanged(String optname, boolean oldValue,
boolean newValue)
{
GUIBattleChit.setUseColoredBorders(newValue);
options.setOption(Options.useColoredBorders, newValue);
repaintAllWindows();
}
});
options.addListener(Options.showCaretaker, new IOptions.Listener()
{
@Override
public void booleanOptionChanged(String optname, boolean oldValue,
boolean newValue)
{
showOrHideCaretaker(newValue);
syncCheckboxes();
}
});
options.addListener(Options.showLogWindow, new IOptions.Listener()
{
@Override
public void booleanOptionChanged(String optname, boolean oldValue,
boolean newValue)
{
showOrHideLogWindow(newValue);
syncCheckboxes();
}
});
options.addListener(Options.showConnectionLogWindow,
new IOptions.Listener()
{
@Override
public void booleanOptionChanged(String optname,
boolean oldValue, boolean newValue)
{
showOrHideConnectionLogWindow(newValue);
syncCheckboxes();
}
});
options.addListener(Options.showStatusScreen, new IOptions.Listener()
{
@Override
public void booleanOptionChanged(String optname, boolean oldValue,
boolean newValue)
{
updateStatusScreen();
}
});
options.addListener(Options.showAutoInspector, new IOptions.Listener()
{
@Override
public void booleanOptionChanged(String optname, boolean oldValue,
boolean newValue)
{
showOrHideAutoInspector(newValue);
syncCheckboxes();
}
});
options.addListener(Options.showEventViewer, new IOptions.Listener()
{
@Override
public void booleanOptionChanged(String optname, boolean oldValue,
boolean newValue)
{
eventViewerSetVisibleMaybe();
syncCheckboxes();
}
});
options.addListener(Options.viewMode, new IOptions.Listener()
{
@Override
public void stringOptionChanged(String optname, String oldValue,
String newValue)
{
viewMode = options.getNumberForViewMode(newValue);
}
});
options.addListener(Options.dubiousAsBlanks, new IOptions.Listener()
{
@Override
public void booleanOptionChanged(String optname, boolean oldValue,
boolean newValue)
{
autoInspectorSetDubiousAsBlanks(newValue);
}
});
options.addListener(Options.showEngagementResults,
new IOptions.Listener()
{
@Override
public void booleanOptionChanged(String optname,
boolean oldValue, boolean newValue)
{
engagementResultsMaybeShow();
}
});
options.addListener(Options.favoriteLookFeel, new IOptions.Listener()
{
@Override
public void stringOptionChanged(String optname, String oldValue,
String newValue)
{
setLookAndFeel(newValue);
}
});
options.addListener(Options.scale, new IOptions.Listener()
{
// TODO check if we could use the intOptionChanged callback here
@Override
public void stringOptionChanged(String optname, String oldValue,
String newValue)
{
int scale = Integer.parseInt(newValue);
if (scale > 0)
{
Scale.set(scale);
rescaleAllWindows();
}
}
});
}
/* Create the event viewer, so that it can collect data from beginning on.
* EventViewer shows itself depending on whether the option for it is set.
*/
private void initEventViewer()
{
logPerhaps("initEventViewer()");
if (eventViewer == null)
{
JFrame parent = getPreferredParent();
eventViewer = new EventViewer(parent, options, client);
}
}
public void eventViewerSetVisibleMaybe()
{
logPerhaps("eventViewerSetVisibleMaybe()");
// if null: no board (not yet, or not at all) => no eventviewer
if (eventViewer != null)
{
// Eventviewer takes care of showing/hiding itself
eventViewer.setVisibleMaybe();
}
}
public void autoInspectorSetDubiousAsBlanks(boolean newValue)
{
logPerhaps("autoInspectorSetDubiousAsBlanks(boolean newValue)");
if (autoInspector != null)
{
autoInspector.setDubiousAsBlanks(newValue);
}
}
public void engagementResultsMaybeShow()
{
logPerhaps("engagementResultsMaybeShow()");
// maybeShow decides by itself based on the current value
// of the option whether to hide or show.
// null if called too early by optionListener when loading options
// TODO try change order: first create, then listeners/load options?
if (engagementResults != null)
{
engagementResults.maybeShow();
}
}
public void actOnTellLegionLocation(Legion legion, MasterHex hex)
{
LOGGER
.fine("CG: actOnTellLegionLocation(Legion legion, MasterHex hex)");
// @TODO: this creates it every time, not only when necessary ?
Marker marker = new Marker(legion, 3 * Scale.get(),
legion.getLongMarkerId(), client, (client != null));
setMarker(legion, marker);
if (!isReplayOngoing())
{
board.alignLegions(hex);
}
}
/** Add the marker to the end of the list and to the LegionInfo.
If it's already in the list, remove the earlier entry. */
void setMarker(Legion legion, Marker marker)
{
board.setMarkerForLegion(legion, marker);
}
/**
* EventViewer, Turn change actions, clear recruited chits,
* push to undo stack, reset to default cursor if no splits pending
*
* Update marker count text, align and highlight legions,
* and for first turn now enable done
* (for all others it was enabled at begin of the phase)
*/
/**
* EventViewer, Turn change actions, clear recruited chits,
* push to undo stack, reset to default cursor if no splits pending
*
* Update marker count text, align and highlight legions,
* and for first turn now enable done
* (for all others it was enabled at begin of the phase)
*/
public void actOnDidSplit(int turn, Legion parent, Legion child,
MasterHex hex)
{
LOGGER
.fine("CG: actOnDidSplit(int turn, Legion parent, Legion child,");
// TODO move if block to eventviewer itself?
// Not during replay, but during redo:
if (!isReplayOngoing() || isRedoOngoing())
{
eventViewerNewSplitEvent(turn, parent, child);
}
Marker marker = new Marker(child, 3 * Scale.get(),
child.getLongMarkerId(), client, (client != null));
setMarker(child, marker);
if (isReplayOngoing())
{
replayTurnChange(turn);
}
if (isMyTurn())
{
board.clearRecruitedChits();
board.clearPossibleRecruitChits();
pushUndoStack(child.getMarkerId());
if (client.getTurnNumber() == 1)
{
board.enableDoneAction();
}
// After doing a split, refresh the number of markers available
board.setMarkerCount(client.getOwningPlayer()
.getMarkersAvailable().size());
updatePendingSplitsText();
if (client.getOwningPlayer().countPendingSplits() == 0)
{
defaultCursor();
}
}
if (!isReplayOngoing())
{
board.alignLegions(hex);
board.highlightTallLegions();
}
}
public void actOnDoneWithMoves()
{
logPerhaps("actOnDoneWithMoves()");
board.clearRecruitedChits();
board.clearPossibleRecruitChits();
}
public void actOnDoneWithSplits()
{
logPerhaps("actOnDoneWithSplits()");
board.clearRecruitedChits();
}
public void actOnDidRecruit(Legion legion, CreatureType recruit,
List<CreatureType> recruiters, String reason)
{
LOGGER
.fine("CG: actOnDidRecruit(Legion legion, CreatureType recruit,");
postRecruitStuff(legion);
board.addRecruitedChit(legion);
board.highlightPossibleRecruitLegionHexes();
if (eventViewer != null)
{
eventViewer.recruitEvent(legion, recruit, recruiters, reason);
}
}
/**
* Do what is needed after recruit (or mark as skip recruit):
* push to undo stack, update legions left to muster, hightlight remaining ones,
*
* @param legion
*/
private void postRecruitStuff(Legion legion)
{
logPerhaps("postRecruitStuff(Legion legion)");
if (client.isMyLegion(legion))
{
pushUndoStack(legion.getMarkerId());
if (!getGameClientSide().isBattleOngoing())
{
board.updateLegionsLeftToMusterText();
}
}
}
public void actOnRemoveCreature(Legion legion, CreatureType type,
String reason)
{
LOGGER
.fine("CG: actOnRemoveCreature(Legion legion, CreatureType type,");
if (reason.equals(Constants.reasonUndidReinforce))
{
LOGGER
.warning("removeCreature should not be called for undidReinforce!");
}
else if (reason.equals(Constants.reasonUndidRecruit))
{
// normal undidRecruit does not use this, but during loading a game
// they appear as add- and removeCreature calls.
LOGGER.info("removeCreature called for undidRecruit - ignored");
}
else
{
eventViewer.removeCreature(legion, type, reason);
}
}
public void actOnRemoveCreaturePart2(Legion legion)
{
logPerhaps("actOnRemoveCreaturePart2(Legion legion)");
if (!isReplayOngoing())
{
GUIMasterHex hex = board.getGUIHexByMasterHex(legion
.getCurrentHex());
hex.repaint();
}
board.actOnEditLegionMaybe(legion);
}
public void actOnAddCreature(Legion legion, CreatureType creature,
String reason)
{
LOGGER
.fine("CG: actOnAddCreature(Legion legion, CreatureType creature,");
if (!isReplayOngoing())
{
GUIMasterHex hex = board.getGUIHexByMasterHex(legion
.getCurrentHex());
hex.repaint();
}
board.actOnEditLegionMaybe(legion);
eventViewer.addCreature(legion, creature, reason);
}
public void actOnUndidSplit(Legion survivor, int turn)
{
logPerhaps("actOnUndidSplit(Legion survivor, int turn)");
if (client.isReplayBeforeRedo())
{
replayTurnChange(turn);
}
else
{
// After undoing a split, refresh the number of markers available
if (isMyTurn())
{
if (survivor.getPlayer().countPendingUndoSplits() == 0)
{
defaultCursor();
}
board.setMarkerCount(client.getOwningPlayer()
.getMarkersAvailable().size());
updatePendingSplitsText();
}
board.alignLegions(survivor.getCurrentHex());
board.highlightTallLegions();
board.repaint();
}
}
public void actOnUndidRecruitPart(Legion legion, boolean wasReinforcement,
int turnNumber)
{
LOGGER
.fine("CG: actOnUndidRecruitPart(Legion legion, boolean wasReinforcement,");
board.cleanRecruitedChit((LegionClientSide)legion);
board.highlightPossibleRecruitLegionHexes();
if (client.isMyLegion(legion))
{
defaultCursor();
if (!wasReinforcement)
{
board.updateLegionsLeftToMusterText();
}
}
int eventType = wasReinforcement ? RevealEvent.eventReinforce
: RevealEvent.eventRecruit;
eventViewer.undoEvent(eventType, legion, null, turnNumber);
}
public boolean chooseWhetherToTeleport()
{
String[] dialogOptions = new String[2];
dialogOptions[0] = "Teleport";
dialogOptions[1] = "Move Normally";
int answer = JOptionPane.showOptionDialog(board, "Teleport?",
"Teleport?", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE, null, dialogOptions,
dialogOptions[1]);
return (answer == JOptionPane.YES_OPTION);
}
public void actOnDidMove(Legion legion, MasterHex startingHex,
MasterHex currentHex, boolean teleport, CreatureType teleportingLord,
boolean splitLegionHasForcedMove)
{
logPerhaps("actOnDidMove(Legion legion, MasterHex startingHex,");
if (teleport)
{
eventViewer.newCreatureRevealEvent(RevealEvent.eventTeleport,
legion, teleportingLord, null);
}
if (client.isMyLegion(legion))
{
setMoveCompleted(legion, startingHex, currentHex);
updatePendingMovesText();
}
board.clearPossibleRecruitChits();
board.alignLegions(startingHex);
board.alignLegions(currentHex);
board.highlightUnmovedLegions();
board.repaint();
if (client.isMyLegion(legion))
{
pushUndoStack(legion.getMarkerId());
if (splitLegionHasForcedMove)
{
board.disableDoneAction("Split legion needs to move");
}
else
{
board.enableDoneAction();
}
board.updateLegionsLeftToMoveText(true);
}
}
public void actOnUndidMove(Legion legion, MasterHex formerHex,
MasterHex currentHex, boolean splitLegionHasForcedMove,
boolean didTeleport)
{
logPerhaps("actOnUndidMove(Legion legion, MasterHex formerHex,");
board.clearPossibleRecruitChits();
board.alignLegions(formerHex);
board.alignLegions(currentHex);
board.highlightUnmovedLegions();
board.repaint();
if (isMyTurn())
{
if (isUndoStackEmpty())
{
board.disableDoneAction("At least one legion must move");
}
else if (splitLegionHasForcedMove)
{
board.disableDoneAction("Split legion needs to move");
}
else
{
board.enableDoneAction();
}
board.updateLegionsLeftToMoveText(true);
}
if (didTeleport)
{
eventViewer.undoEvent(RevealEvent.eventTeleport, legion, null,
client.getTurnNumber());
}
}
public void actOnNoMoreEngagements()
{
logPerhaps("actOnNoMoreEngagements()");
board.setPhaseInfo("Press \"Done\" to end the engagements phase");
board.enableDoneAction();
}
public void alignLegionsMaybe(Legion legion)
{
logPerhaps("alignLegionsMaybe(Legion legion)");
if (!isReplayOngoing())
{
board.alignLegions(legion.getCurrentHex());
}
}
public void actOnRemoveLegion(Legion legion)
{
logPerhaps("actOnRemoveLegion(Legion legion)");
board.removeMarkerForLegion(legion);
}
public void actOnDoSummon()
{
logPerhaps("actOnDoSummon()");
highlightEngagements();
board.repaint();
}
public void setLookAndFeel(String lfName)
{
logPerhaps("setLookAndFeel(String lfName)");
try
{
UIManager.setLookAndFeel(lfName);
UIManager.LookAndFeelInfo[] lnfInfos = UIManager
.getInstalledLookAndFeels();
boolean exist = false;
for (LookAndFeelInfo lnfInfo : lnfInfos)
{
exist = exist || lnfInfo.getClassName().equals(lfName);
}
if (!exist)
{
UIManager.installLookAndFeel(new UIManager.LookAndFeelInfo(
UIManager.getLookAndFeel().getName(), lfName));
}
updateEverything();
LOGGER.log(Level.FINEST, "Switched to Look & Feel: " + lfName);
options.setOption(Options.favoriteLookFeel, lfName);
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE, "Look & Feel " + lfName + " not usable",
e);
}
}
public void updateEverything()
{
logPerhaps("updateEverything()");
board.updateComponentTreeUI();
board.pack();
updateTreeAndPack(statusScreen);
updateTreeAndPack(caretakerDisplay);
updateTreeAndPack(preferencesWindow);
repaintAllWindows();
}
private void updateTreeAndPack(Window window)
{
logPerhaps("updateTreeAndPack(Window window)");
if (window != null)
{
SwingUtilities.updateComponentTreeUI(window);
window.pack();
}
}
public void replayTurnChange(int nowTurn)
{
logPerhaps("replayTurnChange(int nowTurn)");
assert board != null : "board is null in replayTurnChange!";
if (board != null)
{
if (nowTurn != replayLastTurn)
{
board.updateReplayText(nowTurn, replayMaxTurn);
replayLastTurn = nowTurn;
}
}
}
public void prn(String text)
{
client.prn(text);
}
public void actOnTellReplay(int maxTurn)
{
logPerhaps("actOnTellReplay(int maxTurn)");
if (isReplayOngoing())
{
// Switching to replay mode
replayMaxTurn = maxTurn;
if (board != null)
{
board.setReplayMode();
board.updateReplayText(0, replayMaxTurn);
}
}
else
{
repaintAllAfterReplay();
}
}
private void repaintAllAfterReplay()
{
// Replay mode now over
if (board != null)
{
try
{
LOGGER.info("before invokeAndWait for recreateMarkers()");
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
makeBoardRecreateMarkers();
}
});
}
catch (InvocationTargetException e)
{
LOGGER.log(Level.SEVERE, "Failed to run makeBoard"
+ "RecreateMarkers() with invokeAndWait(): ", e);
}
catch (InterruptedException e2)
{
LOGGER.log(Level.SEVERE, "Failed to run makeBoard"
+ "RecreateMarkers() with invokeAndWait(): ", e2);
}
LOGGER.info("after invokeAndWait for recreateMarkers()");
}
}
public void makeBoardRecreateMarkers()
{
logPerhaps("makeBoardRecreateMarkers()");
board.recreateMarkers();
}
public void actOnTellRedoChange()
{
logPerhaps("actOnTellRedoChange()");
if (isRedoOngoing())
{
// Redo switched on: make all visible.
//repaintAllAfterReplay();
//board.repaint();
}
else
{
if (client.getGame().getPhase().equals(Phase.MUSTER))
{
board.unselectAllHexes();
board.clearRecruitedChits();
board.clearPossibleRecruitChits();
board.alignAllLegions();
// board.paintRecruitedChits(g);
board.getFrame().repaint();
// repaintAfterOverlayChanged();
}
}
}
private void clearUndoStack()
{
logPerhaps("clearUndoStack()");
undoStack.clear();
}
private Object popUndoStack()
{
return undoStack.removeFirst();
}
void pushUndoStack(Object object)
{
undoStack.addFirst(object);
}
public boolean isUndoStackEmpty()
{
return undoStack.isEmpty();
}
public void eventViewerCancelReinforcement(CreatureType recruit, int turnNr)
{
eventViewer.cancelReinforcement(recruit, turnNr);
}
public void eventViewerSetCreatureDead(BattleUnit battleUnit)
{
eventViewer.setCreatureDead(battleUnit);
}
public void eventViewerNewSplitEvent(int turn, Legion parent, Legion child)
{
eventViewer.newSplitEvent(turn, parent, null, child);
}
public void eventViewerUndoEvent(Legion splitoff, Legion survivor, int turn)
{
eventViewer
.undoEvent(RevealEvent.eventSplit, survivor, splitoff, turn);
}
public void setPreferencesCheckBoxValue(String name, boolean value)
{
preferencesWindow.setCheckBoxValue(name, value);
}
public void setPreferencesRadioButtonValue(String name, boolean value)
{
preferencesWindow.setRadioButtonValue(name, value);
}
private void initPreferencesWindow()
{
if (preferencesWindow == null)
{
preferencesWindow = new PreferencesWindow(options, this);
}
}
public void setPreferencesWindowVisible(boolean val)
{
if (preferencesWindow != null)
{
preferencesWindow.setVisible(val);
}
}
public void showMarker(Marker marker)
{
if (autoInspector != null)
{
String markerId = marker.getId();
Legion legion = client.getLegion(markerId);
autoInspector.showLegion((LegionClientSide)legion,
client.isMyLegion(legion));
}
}
private void showOrHideCaretaker(boolean bval)
{
if (board == null)
{
LOGGER.warning("showOrHideCaretaker attempted but no board yet!");
// No board yet, or no board at all - nothing to do.
// Initial show will be done in initBoard.
return;
}
if (bval)
{
if (caretakerDisplay == null)
{
caretakerDisplay = new CreatureCollectionView(
getPreferredParent(), this);
}
}
else
{
disposeCaretakerDisplay();
}
}
private void showOrHideAutoInspector(boolean bval)
{
JFrame parent = getPreferredParent();
if (parent == null)
{
// No board yet, or no board at all - nothing to do.
// Initial show will be done in initBoard.
return;
}
if (bval)
{
if (autoInspector == null)
{
Variant variant = getGame().getVariant();
autoInspector = new AutoInspector(parent, options, options.getOption(Options.dubiousAsBlanks),
variant, this);
}
}
else
{
disposeInspector();
}
}
public void showHexRecruitTree(GUIMasterHex hex)
{
if (autoInspector != null)
{
autoInspector.showHexRecruitTree(hex);
}
}
public void didSummon(Legion summoner, Legion donor, CreatureType summon)
{
eventViewer.newCreatureRevealEvent(RevealEvent.eventSummon, donor,
summon, summoner);
}
public void repaintBattleBoard()
{
logPerhaps("repaintBattleBoard()");
if (battleBoard != null)
{
battleBoard.repaint();
}
}
public void repaintAllWindows()
{
logPerhaps("repaintAllWindows()");
if (statusScreen != null)
{
statusScreen.repaint();
}
if (caretakerDisplay != null)
{
caretakerDisplay.repaint();
}
board.getFrame().repaint();
if (battleBoard != null)
{
battleBoard.repaint();
}
}
public void rescaleAllWindows()
{
logPerhaps("rescaleAllWindows()");
if (statusScreen != null)
{
statusScreen.rescale();
}
board.clearRecruitedChits();
board.clearPossibleRecruitChits();
board.rescale();
if (battleBoard != null)
{
battleBoard.rescale();
}
repaintAllWindows();
// highlight legions that could do something,
// and e.g. in movephase, set mover to null
board.actOnMisclick();
}
public void disposeInspector()
{
if (autoInspector != null)
{
autoInspector.setVisible(false);
autoInspector.dispose();
autoInspector = null;
}
}
public void updateCreatureCountDisplay()
{
if (caretakerDisplay != null && !isReplayOngoing())
{
caretakerDisplay.update();
}
}
private void disposeMasterBoard()
{
logPerhaps("disposeMasterBoard()");
if (board != null)
{
board.dispose();
board = null;
}
else
{
LOGGER.warning("attempt to dispose board but board is null!");
}
}
private void disposeBattleBoard()
{
logPerhaps("disposeBattleBoard()");
if (battleBoard != null)
{
battleBoard.dispose();
battleBoard = null;
}
}
/** Dispose the PickCarryDialog, make sure that that is done inside the
* EDT (caused GUI to hang in 1.6.0_39 whereas in 1.6.0_38 it worked )-;
*/
public void disposePickCarryDialog()
{
logPerhaps("disposePickCarryDialog()");
if (SwingUtilities.isEventDispatchThread())
{
actualDisposePickCarryDialog();
}
else
{
try
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
actualDisposePickCarryDialog();
}
});
}
catch (Exception e)
{
LOGGER.warning("When trying to run disposePickCarryDialog "
+ "in invokeAndWait, caught exception: " + e);
}
}
}
private void actualDisposePickCarryDialog()
{
logPerhaps("actualDisposePickCarryDialog()");
if (pickCarryDialog != null)
{
if (battleBoard != null)
{
battleBoard.unselectAllHexes();
}
pickCarryDialog.dispose();
pickCarryDialog = null;
}
}
private void disposeStatusScreen()
{
if (statusScreen != null)
{
statusScreen.dispose();
statusScreen = null;
}
}
private void disposeLogWindow()
{
if (logWindow != null)
{
logWindow.setVisible(false);
logWindow.dispose();
logWindow = null;
}
}
private void disposeConnectionLogWindow()
{
if (connectionLogWindow != null)
{
connectionLogWindow.setVisible(false);
connectionLogWindow.dispose();
connectionLogWindow = null;
}
}
private void disposeEventViewer()
{
if (eventViewer != null)
{
eventViewer.dispose();
eventViewer = null;
}
}
private void disposePreferencesWindow()
{
if (preferencesWindow != null)
{
preferencesWindow.dispose();
preferencesWindow = null;
}
}
void disposeEngagementResults()
{
engagementResults.dispose();
engagementResults = null;
}
private void disposeCaretakerDisplay()
{
if (caretakerDisplay != null)
{
caretakerDisplay.dispose();
caretakerDisplay = null;
}
}
public void showNegotiate(Legion attacker, Legion defender)
{
dueOrNotChangesActions(false, "showNegotiate");
logPerhaps("showNegotiate(Legion attacker, Legion defender)");
board.clearDefenderFlee();
negotiate = new Negotiate(this, attacker, defender);
}
public void respawnNegotiate()
{
logPerhaps("respawnNegotiate()");
if (negotiate != null)
{
negotiate.dispose();
}
negotiate = new Negotiate(this, client.getAttacker(),
client.getDefender());
}
public void showConcede(Client client, Legion ally, Legion enemy)
{
LOGGER.fine("CG: showConcede(client, ally " + ally.getMarkerId()
+ ", enemy " + enemy.getMarkerId());
dueOrNotChangesActions(true, "showConcede");
if (getGame().getDefender().equals(ally))
{
myTurnStartsWindowActions();
}
else
{
LOGGER.finest("showConcede(): not-ally-case");
}
board.clearDefenderFlee();
Concede.concede(this, board.getFrame(), ally, enemy);
}
public void showFlee(Client client, Legion ally, Legion enemy)
{
LOGGER.fine("CG: showFlee(client, ally " + ally.getMarkerId()
+ ", enemy " + enemy.getMarkerId());
dueOrNotChangesActions(true, "showFlee");
if (getGame().getDefender().equals(ally))
{
myTurnStartsWindowActions();
}
else
{
LOGGER.finest("showFlee(): not-ally-case");
}
Concede.flee(this, board.getFrame(), ally, enemy);
}
public void inactivityAutoFleeOrConcede(boolean reply)
{
Concede.inactivityAutoFleeOrConcede(reply);
}
public void askExtraRollApproval(String requestorName, boolean ourself,
final int requestId)
{
final String message = "Player " + requestorName
+ " asks for an extra roll. Pleave approve or deny.";
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
showExtraRollApprovalDialog(message, requestId);
}
});
}
private void showExtraRollApprovalDialog(final String message,
int requestId)
{
String[] options = new String[2];
options[0] = new String("Approve");
options[1] = new String("Deny");
int response = JOptionPane.showOptionDialog(board, message,
"Approval for extra roll request", 0,
JOptionPane.INFORMATION_MESSAGE, null, options, null);
boolean approved = (response == 0 ? true : false);
client.sendExtraRollRequestResponse(approved, requestId);
}
public boolean isPointSideMovementDie(Point point)
{
return movementDie != null && movementDie.getBounds().contains(point);
}
public void askSuspendConfirmation(final String requestorName, int timeout)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
askSuspendConfirmationInEDT(requestorName);
}
});
LOGGER.info("askSuspendConfirmationDialog invokeLater");
}
public void askSuspendConfirmationInEDT(String requestorName)
{
String[] options = new String[2];
options[0] = new String("Approve");
options[1] = new String("Deny");
int response = JOptionPane.showOptionDialog(board, "Player "
+ requestorName + " requested to suspend the game. OK?",
"Suspend Game?", 0,
JOptionPane.QUESTION_MESSAGE, null, options, null);
boolean approved = (response == 0 ? true : false);
suspendResponse(approved);
}
public void suspendResponse(boolean approve)
{
client.suspendResponse(approve);
}
public void initShowEngagementResults()
{
logPerhaps("initShowEngagementResults()");
JFrame parent = getPreferredParent();
// no board at all, e.g. AI - nothing to do.
if (parent == null)
{
return;
}
engagementResults = new EngagementResults(parent, client, options);
engagementResults.maybeShow();
}
public void tellEngagement(Legion attacker, Legion defender, int turnNumber)
{
LOGGER
.fine("CG: tellEngagement(Legion attacker, Legion defender, int turnNumber)");
// remember for end of battle.
tellEngagementResultsAttackerStartingContents = client
.getLegionImageNames(attacker);
tellEngagementResultsDefenderStartingContents = client
.getLegionImageNames(defender);
// TODO: I have the feeling that getLegionCertainties()
// does not work here.
// I always seem to get either ALL true or ALL false.
tellEngagementResultsAttackerLegionCertainities = client
.getLegionCreatureCertainties(attacker);
tellEngagementResultsDefenderLegionCertainities = client
.getLegionCreatureCertainties(defender);
highlightBattleSite(client.getBattleSite());
eventViewer.tellEngagement(attacker, defender, turnNumber);
board.updateEngagementsLeftText();
client.logMsgToServer("I", "In tellEngagement, battleSite is "
+ client.getBattleSite());
board.clearEngagingPending();
}
public boolean hasWatchdog()
{
return watchdog != null;
}
private void dueOrNotChangesActions(boolean isDue, String reason)
{
// if (client.getOwningPlayer().getName().equals("localwatchdogtest")
// || client.getOwningPlayer().getName().equals("Blue"))
// {
// LOGGER.finest("Due: " + (isDue ? "yes" : "no ") + "; reason: "
// + reason);
// }
dueOrNotChangesYellowActions(isDue);
if (hasWatchdog())
{
updateClockTicking(isDue, reason);
}
}
/* This sets or stops inactivityWatchdog ticking, based on the shouldRun
* argument. If the state is already as requested, does nothing.
*/
private void updateClockTicking(boolean shouldRun, String reason)
{
boolean isTicking = watchdog.isClockTicking();
LOGGER.finest("UPDATE CLOCK (reason " + reason
+ "): ticking: shouldrun=" + shouldRun
+ ", isTicking=" + isTicking);
if (shouldRun
&& client.getEventExecutor().getRetriggeredEventOngoing())
{
LOGGER
.finest("was asked to update clock, but this is the retriggered event.");
}
else if (shouldRun
&& client.getAutoplay().isInactivityAutoplayActive())
{
LOGGER
.finest("was asked to update clock, but inactivityAutoplay is active anyway.");
}
else if (isTicking && !shouldRun)
{
watchdog.stopClockTicking();
}
else if (!isTicking && shouldRun)
{
watchdog.setClockTicking();
}
}
void highlightBattleSite(MasterHex battleSite)
{
logPerhaps("highlightBattleSite(MasterHex battleSite)");
if (battleSite != null)
{
board.unselectAllHexes();
board.selectHex(battleSite);
}
}
public void actOnTellEngagementResults(Legion winner, String method,
int points, int turns)
{
LOGGER
.fine("CG: actOnTellEngagementResults(Legion winner, String method,");
JFrame frame = getMapOrBoardFrame();
if (frame == null)
{
return;
}
// If battle was resolved by negotiation, close remaining
// dialogs (also on the one which did NOT press Accept)
// as soon as possible, don't let them open until
// user presses "Done" in Resolve Engagements or picks next
// engagement. If it stays open and user picks something there,
// it would cause trouble.
cleanupNegotiationDialogs();
// just in case, make sure this get cleared. Might still be set
// if opponent decided to flee.
board.clearDefenderFlee();
eventViewer.tellEngagementResults(winner, method, turns);
engagementResults
.addData(winner, method, points, turns,
tellEngagementResultsAttackerStartingContents,
tellEngagementResultsDefenderStartingContents,
tellEngagementResultsAttackerLegionCertainities,
tellEngagementResultsDefenderLegionCertainities,
client.isMyTurn());
}
public void actOnEngagementCompleted()
{
dueOrNotChangesActions(isMyTurn(), "engagement over");
logPerhaps("actOnEngagementCompleted()");
board.updateEngagementsLeftText();
}
public void tellWhatsHappening(String message)
{
logPerhaps("tellWhatsHappening(String message)");
board.setPhaseInfo(message);
}
public void actOnTellMovementRoll(int roll, String reason)
{
if (client.isReplayBeforeRedo())
{
return;
}
logPerhaps("actOnTellMovementRoll(" + roll + ", " + reason + ")");
// TODO move if block to eventviewer itself?
// Not during replay, but during redo:
if (!isReplayOngoing() || isRedoOngoing())
{
eventViewer.tellMovementRoll(roll, reason);
}
// No point to recreate and repaint if it's same as before
if (movementDie == null || roll != movementDie.getDisplayedRoll())
{
movementDie = new MovementDie(4 * Scale.get(),
MovementDie.getDieImageName(roll));
// TODO why do we not repaint if iconified?
if (board.getFrame().getExtendedState() != JFrame.ICONIFIED)
{
board.repaint();
}
}
}
private List<String> tellEngagementResultsAttackerStartingContents = null;
private List<String> tellEngagementResultsDefenderStartingContents = null;
private List<Boolean> tellEngagementResultsAttackerLegionCertainities = null;
private List<Boolean> tellEngagementResultsDefenderLegionCertainities = null;
/* pass revealed info to EventViewer and
* additionally remember the images list for later, the engagement report
*/
public void revealEngagedCreatures(Legion legion,
final List<CreatureType> creatures, boolean isAttacker, String reason)
{
logPerhaps("revealEngagedCreatures(Legion legion,");
// in engagement we need to update the remembered list, too.
if (isAttacker)
{
tellEngagementResultsAttackerStartingContents = client
.getLegionImageNames(legion);
// towi: should return a list of trues, right?
// TODO if comment above is right: add assertion
tellEngagementResultsAttackerLegionCertainities = client
.getLegionCreatureCertainties(legion);
}
else
{
tellEngagementResultsDefenderStartingContents = client
.getLegionImageNames(legion);
// towi: should return a list of trues, right?
// TODO if comment above is right: add assertion
tellEngagementResultsDefenderLegionCertainities = client
.getLegionCreatureCertainties(legion);
}
eventViewer.revealEngagedCreatures(creatures, isAttacker, reason);
}
public void eventViewerRevealCreatures(Legion legion,
final List<CreatureType> creatures, String reason)
{
eventViewer.revealCreatures(legion, creatures, reason);
}
private void showOrHideLogWindow(boolean show)
{
if (show)
{
if (logWindow == null)
{
// after recent changes (14.10.2013) we should never
// actually get here any more...
// the logger with the empty name is parent to all loggers
// and thus catches all messages
logWindow = new LogWindow(options, Logger.getLogger(""), show);
}
else
{
logWindow.setVisible(true);
}
}
else
{
if (logWindow != null)
{
logWindow.setVisible(false);
}
}
}
private void showOrHideConnectionLogWindow(boolean show)
{
show = false;
if (show)
{
if (connectionLogWindow == null)
{
// the logger with the empty name is parent to all loggers
// and thus catches all messages
connectionLogWindow = new ConnectionLogWindow(options);
}
else
{
connectionLogWindow.setVisible(true);
}
}
else
{
if (connectionLogWindow != null)
{
connectionLogWindow.setVisible(false);
}
}
}
public void appendToConnectionLog(String s)
{
// Creates or make visible, if needed:
// options.setOption(Options.showConnectionLogWindow, true);
// connectionLogWindow.append(s);
}
public void actOnReconnectCompleted()
{
if (!isMyTurn())
{
LOGGER.info("");
return;
}
if (getGame().isPhase(Phase.MOVE))
{
pendingMoves.clear();
board.highlightUnmovedLegions();
}
}
// TODO why is syncCheckBoxes is called to sync *all* checkboxes in each
// listener, and not only for the one changed option?
/**
* Ensure that Player menu checkboxes reflect the correct state.
* Copied the TODO below from the interface where it's now removed...
*
* TODO let the checkboxes have their own listeners instead. Or even
* better: use a binding framework.
*/
public void syncCheckboxes()
{
Enumeration<String> en = options.propertyNames();
while (en.hasMoreElements())
{
String name = en.nextElement();
boolean value = options.getOption(name);
board.adjustCheckboxIfNeeded(name, value);
}
}
public void doAcquireAngel(Legion legion, List<CreatureType> recruits)
{
LOGGER
.fine("CG: doAcquireAngel(Legion legion, List<CreatureType> recruits)");
board.deiconify();
new AcquireAngel(board.getFrame(), this, legion, recruits);
dueOrNotChangesActions(true, "doaquire angel");
}
public void setBoardActive(boolean val)
{
LOGGER.finer("setBoardActive(boolean val)");
board.setBoardActive(val);
}
public void doPickSummonAngel(Legion legion, List<Legion> possibleDonors)
{
dueOrNotChangesActions(true, "dopicksummonangel");
logPerhaps("doPickSummonAngel(Legion legion,");
new SummonAngel(this, legion, possibleDonors);
}
public List<CreatureType> doPickSplitLegion(Legion parent,
String childMarker)
{
List<CreatureType> creaturesToSplit = SplitLegion.splitLegion(this,
parent, childMarker);
// null means cancel, empty list to signal "mark as skip".
if (creaturesToSplit != null && creaturesToSplit.isEmpty())
{
markLegionAsSkipSplit(parent);
// give back null to client to make client do nothing any more.
creaturesToSplit = null;
}
return creaturesToSplit;
}
private void markLegionAsSkipSplit(Legion legion)
{
logPerhaps("markLegionAsSkipSplit(Legion legion)");
legion.setSkipThisTime(true);
pushUndoStack(legion.getMarkerId());
board.clearPossibleRecruitChits();
board.highlightTallLegions();
}
public void resetAllLegionFlags()
{
logPerhaps("resetAllLegionFlags()");
for (Legion l : getOwningPlayer().getLegions())
{
l.setSkipThisTime(false);
l.setVisitedThisPhase(false);
}
}
public boolean isPickCarryOngoing()
{
return pickCarryDialog != null;
}
public void doPickCarries(Client client, int carryDamage,
Set<String> carryTargetDescriptions)
{
logPerhaps("doPickCarries(Client client, int carryDamage,");
Set<BattleHex> carryTargetHexes = new HashSet<BattleHex>();
for (String desc : carryTargetDescriptions)
{
carryTargetHexes.add(battleBoard.getBattleHexByLabel(desc
.substring(desc.length() - 2)));
}
battleBoard.highlightPossibleCarries(carryTargetHexes);
pickCarryDialog = new PickCarry(battleBoard, this, carryDamage,
carryTargetDescriptions);
}
public PickCarry getPickCarryDialog()
{
return pickCarryDialog;
}
public void handlePickCarry(GUIBattleHex hex)
{
logPerhaps("handlePickCarry(GUIBattleHex hex)");
String hexLabel = "";
if (hex != null)
{
hexLabel = hex.getHexModel().getLabel();
}
String choiceDesc = pickCarryDialog.findCarryChoiceForHex(hexLabel);
// clicked on possible carry target
if (choiceDesc != null)
{
pickCarryDialog.handleCarryToDescription(choiceDesc);
}
else
{
// enemy but not carryable to there
}
}
public void doPickColor(final String playerName,
final List<PlayerColor> colorsLeft)
{
dueOrNotChangesActions(true, "dopickcolor");
logPerhaps("doPickColor(final String playerName,");
if (SwingUtilities.isEventDispatchThread())
{
bringUpPickColorDialog(playerName, colorsLeft);
}
else
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
bringUpPickColorDialog(playerName, colorsLeft);
}
});
}
}
public void bringUpPickColorDialog(String playerName,
List<PlayerColor> colorsLeft)
{
board.setPhaseInfo("Pick a color!");
PickColor.PickColorCallback callback = new PickColor.PickColorCallback()
{
@Override
public void tellPickedColor(PlayerColor color)
{
// method that passes it on to client
answerPickColor(color);
}
};
// Do not allow null: for pick initial color keep asking if one chosen
new PickColor(board.getFrame(), playerName, colorsLeft, options,
callback, false);
}
public void doPickSplitMarker(Legion parent, Set<String> markersAvailable)
{
createPickMarkerDialog(this, markersAvailable, parent);
}
public void doPickInitialMarker(Set<String> markersAvailable)
{
dueOrNotChangesActions(true, "dopickinitialmarker");
board.setPhaseInfo("Pick initial marker!");
createPickMarkerDialog(this, markersAvailable, null);
}
public void createPickMarkerDialog(final ClientGUI gui,
final Set<String> markerIds, final Legion parent)
{
if (SwingUtilities.isEventDispatchThread())
{
new PickMarker(gui, markerIds, parent);
}
else
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new PickMarker(gui, markerIds, parent);
}
});
}
}
public CreatureType doPickRecruit(Legion legion, String hexDescription)
{
List<CreatureType> recruits = client.findEligibleRecruits(legion,
legion.getCurrentHex());
return PickRecruit.pickRecruit(board.getFrame(), recruits,
hexDescription, legion, this);
}
/**
* TODO This is just a HACK.
* PickRecruit calls this to mark a legion as that user wants to not
* recruit anything this turn.
* Better would be, if that dialog could return a "NONE" CreatureType
* and the caller does the work cleanly...
* (postponed for now because the NONE-CreatureType would be so much
* work right now...)
* @param legion
*/
public void markLegionAsSkipRecruit(Legion legion)
{
legion.setSkipThisTime(true);
postRecruitStuff(legion);
// TODO : if we one day handle skip recruit as a special
// "NONE"-CreatureType, show recruit chit and highlight could
// both be part of the postRecruit call, highlight not needed
// here separately.
board.highlightPossibleRecruitLegionHexes();
}
public String doPickRecruiter(List<String> recruiters,
String hexDescription, Legion legion)
{
String recruiterName = null;
// Even if PickRecruiter dialog is modal, this only prevents mouse
// and keyboard into; but with pressing "D" one could still end the
// recruiting phase which leaves game in inconsisten state...
// So, forcibly really disable the Done action for that time.
board.disableDoneAction("Finish 'Pick Recruiter' first");
recruiterName = PickRecruiter.pickRecruiter(board.getFrame(),
recruiters, hexDescription, legion, this);
board.enableDoneAction();
return recruiterName;
}
public EntrySide doPickEntrySide(MasterHex hex, Set<EntrySide> entrySides)
{
return PickEntrySide.pickEntrySide(board.getFrame(), hex, entrySides);
}
public CreatureType doPickLord(List<CreatureType> lords)
{
return PickLord.pickLord(options, board.getFrame(), lords);
}
public void doPickStrikePenalty(Client client, List<String> choices)
{
new PickStrikePenalty(battleBoard, this, choices);
}
public void tellProposal(String proposalString)
{
logPerhaps("tellProposal(String proposalString)");
Proposal proposal = Proposal.makeFromString(proposalString,
client.getGameClientSide());
if (replyToProposal != null)
{
replyToProposal.dispose();
}
replyToProposal = new ReplyToProposal(board.getFrame(), this,
getOwningPlayer().getName(), options, proposal);
}
public void cleanupNegotiationDialogs()
{
logPerhaps("cleanupNegotiationDialogs()");
if (negotiate != null)
{
negotiate.dispose();
negotiate = null;
}
if (replyToProposal != null)
{
replyToProposal.dispose();
replyToProposal = null;
}
}
public void actOnTurnOrPlayerChange(Client client, int turnNr,
Player player)
{
logPerhaps("actOnTurnOrPlayerChange(Client client, int turnNr,");
cleanupNegotiationDialogs();
if (isMyTurn())
{
myTurnStartsWindowActions();
}
dueOrNotChangesActions(isMyTurn(), "actonturnorplayerchange");
eventViewer.turnOrPlayerChange(turnNr, player);
if (watchdog != null)
{
if (client.getEventExecutor().getRetriggeredEventOngoing())
{
client.kickPhase();
}
}
else
{
LOGGER.finest("(ClientGUI of client " + getOwningPlayerName()
+ ": no watchdog needed)");
}
}
private void myTurnStartsWindowActions()
{
if (getOptions().getOption(Options.turnStartBeep))
{
board.getToolkit().beep();
}
if (options.getOption(Options.turnStartToFront))
{
board.getFrame().toFront();
if ((board.getFrame().getExtendedState() & JFrame.ICONIFIED) != 0)
{
board.getFrame().setExtendedState(JFrame.NORMAL);
}
}
}
private void dueOrNotChangesYellowActions(boolean isDue)
{
if (options.getOption(Options.turnStartBottomBarYellow, true))
{
board.myTurnBottomBarActions(isDue);
}
if (options.getOption(Options.turnStartChatYellow, true))
{
notifyWebClientItsPlayersTurn(isDue);
}
}
// might also be called be PreferencesWindow
void notifyWebClientItsPlayersTurn(boolean isDue)
{
if (webClient != null)
{
webClient.notifyItsPlayersTurn(isDue);
}
}
public void markThatSomethingHappened()
{
if (watchdog != null)
{
watchdog.markThatSomethingHappened();
}
}
public void actOnGameStarting()
{
logPerhaps("actOnGameStarting()");
if (!client.isRemote())
{
board.enableSaveActions();
}
}
public void actOnSetupSplit()
{
logPerhaps("actOnSetupSplit()");
// TODO probably this can be removed?
if (isMyTurn())
{
// for debug purposes. We had a bug where legions remain
// on the board even if player is dead. So, let's check
// for this once per turn and clean up.
validateLegions();
}
disposeMovementDie();
clearUndoStack();
board.setupSplitMenu();
board.fullRepaint(); // Ensure that movement die goes away
if (isMyTurn())
{
// TODO replace all those "when xxx happens then set label to yyy"
// with proper "set game state to state xxx and trigger redisplaying
// of labels and setting actions dis-/enabled accordingly"...
if (client.getTurnNumber() == 1)
{
board.disableDoneAction("Split legions in first round");
}
board.maybeRequestFocusAndToFront();
defaultCursor();
// TODO I believe the code below is meant for the purpose:
// "If no legions can be split, directly be done with Split
// phase, except if that is the result of the autoSplit"
// - so that one can review and undo.
// But that does not make so much sense, as this is in the
// "setupSplit" call, so the AI can't have done anything yet?
//
// 2009 June, Clemens: added "&& !isUndoStackEmpty()". If one did
// split all possible legions just before save and load it again,
// then this one here would otherwise automatically do the
// "doneWithSplit()".
if ((getOwningPlayer().getMarkersAvailable().size() < 1 || client
.findTallLegionHexes(4, true).isEmpty())
&& !client.getAutoSplit() && isUndoStackEmpty())
{
client.doneWithSplits();
}
}
else
{
waitCursor();
}
updateStatusScreen();
}
private void validateLegions()
{
logPerhaps("validateLegions()");
boolean foundProblem = false;
for (Player p : client.getGameClientSide().getPlayers())
{
if (p.isDead())
{
for (Legion l : p.getLegions())
{
LOGGER
.warning("Dead player " + p.getName() + " has "
+ "still legion " + l.getMarkerId()
+ ". Removing it.");
p.removeLegion(l);
foundProblem = true;
}
}
}
if (foundProblem)
{
LOGGER.info("Found legion(s) for dead player "
+ "- recreating markers");
board.recreateMarkers();
}
}
public void actOnSetupMuster()
{
logPerhaps("actOnSetupMuster()");
clearUndoStack();
cleanupNegotiationDialogs();
board.setupMusterMenu();
if (isMyTurn())
{
board.maybeRequestFocusAndToFront();
defaultCursor();
}
updateStatusScreen();
}
public void actOnSetupMove()
{
logPerhaps("actOnSetupMove()");
clearUndoStack();
pendingMoves.clear();
pendingMoveHexes.clear();
recoveredFromMoveNak = false;
board.setupMoveMenu();
// Force showing the updated movement die.
// taken repaint away because actOnTellMovementRoll does it anyway
// board.repaint();
if (isMyTurn())
{
defaultCursor();
}
updateStatusScreen();
}
public void actOnSetupFight()
{
logPerhaps("actOnSetupFight()");
clearUndoStack();
board.setupFightMenu();
updateStatusScreen();
}
public void actOnSetupBattleFight()
{
logPerhaps("actOnSetupBattleFight()");
if (battleBoard != null)
{
battleBoard.updatePhaseAndTurn();
if (client.isMyBattlePhase())
{
battleBoard.reqFocus();
defaultCursor();
}
else
{
waitCursor();
}
battleBoard.setupFightMenu();
}
updateStatusScreen();
dueOrNotChangesActions(client.isMyBattlePhase(), client
.getBattlePhase().toString());
}
public void actOnSetupBattleMove()
{
logPerhaps("actOnSetupBattleMove()");
// needed/better to do here, than when finishing a battle turn,
// for two reasons: 1) done with moves quickly after a move (before
// server response received and handled) move would/might get into
// stack again, and worse, 2) concede during battle undoStack remained
// with content as well, causing "Undo battle move errors" (#3497085)
clearUndoStack();
if (battleBoard != null)
{
battleBoard.updatePhaseAndTurn();
if (client.isMyBattlePhase())
{
battleBoard.reqFocus();
defaultCursor();
battleBoard.setupMoveMenu();
}
}
updateStatusScreen();
dueOrNotChangesActions(client.isMyBattlePhase(), "actonbattlemove");
}
public void actOnTellBattleMove(BattleHex startingHex,
BattleHex endingHex, boolean rememberForUndo)
{
logPerhaps("actOnTellBattleMove(BattleHex startingHex,");
if (rememberForUndo)
{
pushUndoStack(endingHex.getLabel());
}
if (battleBoard != null)
{
battleBoard.alignChits(startingHex);
battleBoard.alignChits(endingHex);
battleBoard.repaint();
actOnPendingBattleMoveOver();
}
}
public void actOnPendingBattleMoveOver()
{
logPerhaps("actOnPendingBattleMoveOver()");
battleBoard.actOnPendingBattleMoveOver();
}
public void actOnDoneWithBattleMoves()
{
logPerhaps("actOnDoneWithBattleMoves()");
clearUndoStack();
}
public void actOnSetupBattleRecruit()
{
logPerhaps("actOnSetupBattleRecruit()");
if (battleBoard != null)
{
battleBoard.updatePhaseAndTurn();
if (client.isMyBattlePhase())
{
battleBoard.reqFocus();
battleBoard.setupRecruitMenu();
}
}
updateStatusScreen();
}
public void actOnSetupBattleSummon()
{
logPerhaps("actOnSetupBattleSummon()");
if (battleBoard != null)
{
battleBoard.updatePhaseAndTurn();
if (client.isMyBattlePhase())
{
battleBoard.reqFocus();
battleBoard.setupSummonMenu();
defaultCursor();
}
else
{
waitCursor();
}
}
updateStatusScreen();
}
private void addBattleChit(GUIBattleChit battleChit)
{
logPerhaps("addBattleChit(GUIBattleChit battleChit)");
battleChits.add(battleChit);
}
/**
* Get a list of all GUIBattleChits (on the current BattleMap)
* @return The list of GUIBattleChits
*/
public List<GUIBattleChit> getGUIBattleChits()
{
return Collections.unmodifiableList(battleChits);
}
/**
* Find all GUIBattleChits that occupy a specified hex
* Note that this can be several for the offboard position(s)
*
* @param hex The hex to give Chits for
* @return A List of GUIBattleChits
*/
public List<GUIBattleChit> getGUIBattleChitsInHex(final BattleHex hex)
{
return CollectionHelper.selectAsList(battleChits,
new Predicate<GUIBattleChit>()
{
public boolean matches(GUIBattleChit battleChit)
{
return hex.equals(battleChit.getBattleUnit()
.getCurrentHex());
}
});
}
public GUIBattleChit getGUIBattleChit(BattleHex hex)
{
for (GUIBattleChit battleChit : getGUIBattleChits())
{
if (hex.equals(battleChit.getBattleUnit().getCurrentHex()))
{
return battleChit;
}
}
return null;
}
public void actOnPlaceNewChit(String imageName, BattleUnit battleUnit,
BattleHex hex)
{
LOGGER
.fine("CG: actOnPlaceNewChit(String imageName, BattleUnit battleUnit,");
Legion legion = battleUnit.getLegion();
PlayerColor playerColor = legion.getPlayer().getColor();
GUIBattleChit battleChit = new GUIBattleChit(5 * Scale.get(),
imageName, battleUnit.isDefender(), playerColor, getClient(),
battleUnit);
addBattleChit(battleChit);
// TODO is the "if ( != null)" still needed?
if (battleBoard != null)
{
battleBoard.alignChits(hex);
// Make sure BattleBoard is visible after summon or muster.
battleBoard.reqFocus();
}
}
private String getBattleUnitDescription(BattleCritter battleUnit)
{
if (battleUnit == null)
{
return "";
}
BattleHex hex = battleUnit.getCurrentHex();
return battleUnit.getType().getName() + " in " + hex.getDescription();
}
public void actOnTellStrikeResults(boolean wasCarry, int strikeNumber,
List<String> rolls, BattleCritter striker, BattleCritter target)
{
LOGGER
.fine("CG: actOnTellStrikeResults(boolean wasCarry, int strikeNumber,");
if (battleBoard != null)
{
if (!wasCarry)
{
battleBoard.addDiceResults(getBattleUnitDescription(striker),
getBattleUnitDescription(target), strikeNumber, rolls);
}
battleBoard.unselectAllHexes();
}
}
public void actOnHitsSet(BattleUnit target)
{
logPerhaps("actOnHitsSet(BattleUnit target)");
battleBoard.actOnHitsSet(target.getCurrentHex());
}
public void highlightCrittersWithTargets()
{
logPerhaps("highlightCrittersWithTargets()");
if (battleBoard != null)
{
battleBoard.highlightCrittersWithTargets();
}
}
public void indicateStrikesDone(boolean auto)
{
logPerhaps("markStrikesDone()");
if (battleBoard != null)
{
battleBoard.indicateStrikesDone(auto);
}
}
public void revertDoneIndicator()
{
logPerhaps("revertDoneIndicator()");
if (battleBoard != null)
{
battleBoard.revertDoneIndicator();
}
}
public void actOnApplyCarries(BattleHex hex)
{
logPerhaps("actOnApplyCarries(BattleHex hex)");
if (battleBoard != null)
{
battleBoard.unselectHex(hex);
battleBoard.repaint();
}
}
public void actOnCleanupBattle()
{
logPerhaps("actOnCleanupBattle()");
if (battleBoard != null)
{
battleBoard.dispose();
battleBoard = null;
}
battleChits.clear();
}
/**
* For the topmost item on undo stack, undo the done recruit,
* or reset the skipThisTime flag if set.
*/
void undoLastRecruit()
{
if (!isUndoStackEmpty())
{
String markerId = (String)popUndoStack();
Legion legion = client.getLegion(markerId);
handleUndoRecruit(legion);
}
}
/**
* For a specific clicked legion, undo the done recruit,
* or reset the skipThisTime flag if set.
* @param legion The legion for which to undo the recruit
*/
public void undoRecruit(Legion legion)
{
logPerhaps("undoRecruit(Legion legion)");
if (undoStack.contains(legion.getMarkerId()))
{
undoStack.remove(legion.getMarkerId());
}
handleUndoRecruit(legion);
}
/**
* This does the actual work for undoing a recruit
* @param legion The legion for which to undo the recruit
*/
private void handleUndoRecruit(Legion legion)
{
logPerhaps("handleUndoRecruit(Legion legion)");
if (legion.hasRecruited())
{
waitCursor();
getClient().undoRecruit(legion);
}
else
{
legion.setSkipThisTime(false);
if (client.isMyLegion(legion))
{
board.updateLegionsLeftToMusterText();
}
}
}
public void actOnSplitRelatedRequestSent()
{
board.highlightTallLegions();
updatePendingSplitsText();
waitCursor();
}
void undoAllSplits()
{
while (!isUndoStackEmpty())
{
undoLastSplit();
}
}
void undoLastSplit()
{
if (client.getOwningPlayer().countPendingSplits() != 0)
{
LOGGER.finer("undoLastSplit(): split request pending");
showMessageDialogAndWait("Split request still pending\n(waiting for server response)");
return;
}
if (!isUndoStackEmpty())
{
String splitoffId = (String)popUndoStack();
Legion legion = client.getLegion(splitoffId);
if (legion.getSkipThisTime())
{
legion.setSkipThisTime(false);
board.alignLegions(legion.getCurrentHex());
board.highlightTallLegions();
}
else
{
client.undoSplit(legion);
// to highlight the unsplitting one in blue
board.highlightTallLegions();
board.repaint();
updatePendingSplitsText();
}
}
}
public void informSplitRequiredFirstRound()
{
logPerhaps("informSplitRequiredFirstRound()");
// must split in first turn - Done not allowed now
if (board != null && isMyTurn())
{
board.disableDoneAction("Split required in first round");
}
}
public void informSplitDoneFirstRound()
{
logPerhaps("informSplitDoneFirstRound()");
if (board != null && isMyTurn())
{
board.enableDoneAction();
}
}
void undoLastMove()
{
if (pendingMoves.size() > 0)
{
displayNoUndoWhilePendingMovesInfo();
return;
}
if (!isUndoStackEmpty())
{
String markerId = (String)popUndoStack();
Legion legion = client.getLegion(markerId);
if (legion.hasMoved())
{
getClient().undoMove(legion);
}
else
{
legion.setSkipThisTime(false);
}
board.updateLegionsLeftToMoveText(true);
}
}
public void undoLastBattleMove()
{
logPerhaps("undoLastBattleMove()");
if (!isUndoStackEmpty())
{
String hexLabel = (String)popUndoStack();
BattleHex hex = battleBoard.getBattleHexByLabel(hexLabel);
undoBattleMove(hex);
}
}
public void undoAllBattleMoves()
{
logPerhaps("undoAllBattleMoves()");
while (!isUndoStackEmpty())
{
undoLastBattleMove();
}
}
public void undoAllMoves()
{
logPerhaps("undoAllMoves()");
if (pendingMoves.size() > 0)
{
displayNoUndoWhilePendingMovesInfo();
return;
}
board.clearRecruitedChits();
board.clearPossibleRecruitChits();
while (!isUndoStackEmpty())
{
undoLastMove();
}
}
public void undoAllRecruits()
{
logPerhaps("undoAllRecruits()");
while (!isUndoStackEmpty())
{
undoLastRecruit();
}
}
private void displayNoUndoWhilePendingMovesInfo()
{
logPerhaps("displayNoUndoWhilePendingMovesInfo()");
JOptionPane.showMessageDialog(getMapOrBoardFrame(),
"For some moves is still the confirmation from server and "
+ "screen update missing!\n"
+ "Undo can't be done beofre all moves are completed "
+ "(see message beside the Done button).", "Pending Moves!",
JOptionPane.INFORMATION_MESSAGE);
}
public void defaultCursor()
{
logPerhaps("defaultCursor()");
board.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
public void waitCursor()
{
logPerhaps("waitCursor()");
board.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
public void doCleanupGUI()
{
logPerhaps("doCleanupGUI()");
if (SwingUtilities.isEventDispatchThread())
{
cleanupGUI();
}
else
{
try
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
cleanupGUI();
}
});
}
catch (InterruptedException e)
{/* ignore */
}
catch (InvocationTargetException e2)
{/* ignore */
}
}
}
private void disposeMovementDie()
{
logPerhaps("disposeMovementDie()");
movementDie = null;
}
MovementDie getMovementDie()
{
return movementDie;
}
private void cleanupGUI()
{
logPerhaps("cleanupGUI()");
try
{
disposeInspector();
disposeCaretakerDisplay();
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE,
"During disposal of Inspector and Caretaker: " + e.toString(),
e);
}
client.cleanupBattle();
if (watchdog != null)
{
watchdog.finish();
watchdog = null;
}
disposeLogWindow();
// For now, that one shall NOT be reopened by default on next start
options.setOption(Options.showConnectionLogWindow, false);
disposeConnectionLogWindow();
disposeLastInactivityDialog();
disposeMovementDie();
disposeStatusScreen();
disposeEventViewer();
disposePreferencesWindow();
disposeEngagementResults();
disposeBattleBoard();
disposeMasterBoard();
// Options must be saved after all satellites are disposed,
// because they store their location and state (enabled or not, e.g.)
// in disposal.
options.saveOptions();
this.secondaryParent = null;
client.doAdditionalCleanup();
}
/**
* Update Board and Status screen to reflect the new game over state.
* Show the game over message, or store it to be shown later.
* If dispose will follow soon, don't show message immediately
* (to avoid having the user to have click two boxes), instead store
* it for later to be shown then together with the dispose dialog.
*
* @param message The message ("XXXX wins", or "Draw")
* @param disposeFollows If true, server will send a dispose message soon
*/
public void actOnTellGameOver(String message, boolean disposeFollows, boolean suspended)
{
LOGGER.fine("CG: actOnTellGameOver('" + message + "', dispose="
+ disposeFollows + ", suspended=" + suspended);
if (webClient != null)
{
webClient.notifyItsPlayersTurn(false);
webClient.tellGameEnds();
}
if (statusScreen != null)
{
statusScreen.repaint();
}
defaultCursor();
if (watchdog != null)
{
LOGGER.finer("Game over: stopping the watchdog for player: "
+ client.getOwningPlayer().getName());
watchdog.finish();
}
board.setGameOverState(message);
dueOrNotChangesYellowActions(false);
if (disposeFollows)
{
// AutoQuit active, application will terminate immediately
// If this here is local client, we don't want to wait this
// client here to answer; if it's a remote client, it will
// be displayed together with the connection lost message.
}
else
{
// show right away. Connection closed might come or not.
showMessageDialogAndWait(message);
}
}
public void showMessageDialogAndWait(final String message)
{
logPerhaps("showMessageDialogAndWait(String message)");
// Don't bother showing messages to AI players.
if (getOwningPlayer().isAI())
{
LOGGER.info("Message for AI player " + getOwningPlayer().getName()
+ ": " + message);
return;
}
if (SwingUtilities.isEventDispatchThread())
{
doShowMessageDialog(message);
}
else
{
try
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
doShowMessageDialog(message);
}
});
}
catch (InterruptedException e)
{/* ignore */
}
catch (InvocationTargetException e2)
{/* ignore */
}
}
}
void doShowMessageDialog(String message)
{
logPerhaps("doShowMessageDialog(String message)");
// For humans in autoplay do not show messages...
if (client.isAutoplayActive())
{
// ... suppress any other messages than the game over message ...
String goMessage = getClient().getGame().getGameOverMessage();
if ((goMessage != null && message.contains(goMessage))
// but suppress even that if autoQuit is on
// (=> remote stresstest)
&& !options.getOption(Options.autoQuit))
{
// go on to showing
}
else
{
// do not show it, return instead.
return;
}
}
JFrame frame = getMapOrBoardFrame();
if (frame != null)
{
JOptionPane.showMessageDialog(frame, message);
}
}
// called by WebClient
public void doConfirmAndQuit()
{
// In odd situations, e.g. web game did not start properly, the
// game client is set in webclient, but board is still null
if (board != null)
{
// Board does the "Really Quit?" confirmation and initiates
// then (if user confirmed) the disposal of everything.
board.doQuitGameAction();
}
else
{
menuQuitGame();
}
}
/**
* This is for permanent, non-reversible closed connections
*/
public void showConnectionClosedMessage()
{
defaultCursor();
board.setServerClosedMessage(client.getGame().isGameOver());
String dialogMessage = null;
String dialogTitle = null;
if (client.getGame().isGameOver())
{
if (client.getGame().isSuspended())
{
if (startedByWebClient)
{
showWebClient();
}
if (webClient != null) // should be set now!
{
webClient.notifyGameSuspended();
return;
}
else
{
// we should not get here, but just in case...
dialogMessage = "Game was suspended and server closed connection.\n"
+ " Your board will close now.";
dialogTitle = "Game suspended";
}
}
else if (!gameOverMessageAlreadyShown)
{
// don't show again!
gameOverMessageAlreadyShown = true;
dialogMessage = "Game over: "
+ client.getGame().getGameOverMessage() + "!\n\n"
+ "(connection closed from server side)";
dialogTitle = "Game Over: Server closed connection";
}
else
{
dialogMessage = "Connection now closed from server side.";
dialogTitle = "Game Over: server closed connection";
}
}
else
{
dialogMessage = "Connection to server unexpectedly lost?";
dialogTitle = "Server closed connection";
}
JOptionPane.showMessageDialog(getMapOrBoardFrame(), dialogMessage,
dialogTitle, JOptionPane.INFORMATION_MESSAGE);
}
private JFrame getMapOrBoardFrame()
{
JFrame frame = null;
if (battleBoard != null)
{
frame = battleBoard;
}
else if (board != null)
{
frame = board.getFrame();
}
return frame;
}
// From other GUI components:
void negotiateCallback(Proposal proposal, boolean respawn)
{
LOGGER
.fine("CG: negotiateCallback(Proposal proposal, boolean respawn)");
getClient().negotiateCallback(proposal, respawn);
}
// All kind of other GUI components might need this, too.
public Player getOwningPlayer()
{
return client.getOwningPlayer();
}
public String getOwningPlayerName()
{
return getOwningPlayer().getName();
}
public boolean isMyTurn()
{
return client.isMyTurn();
}
public Legion getMover()
{
return mover;
}
public void setMover(Legion legion)
{
logPerhaps("setMover(Legion legion)");
this.mover = legion;
}
public boolean doMove(MasterHex hex)
{
for (PendingMove pendingMove : pendingMoves)
{
if (pendingMove.getLegion().equals(mover))
{
JOptionPane.showMessageDialog(getMapOrBoardFrame(),
"Legion already moved, but screen update happens only "
+ "after confirmation from server was received.",
"Already moved!", JOptionPane.INFORMATION_MESSAGE);
return false;
}
}
return client.doMove(mover, hex);
}
public void actOnMoveNak()
{
logPerhaps("actOnMoveNak()");
defaultCursor();
pendingMoves.clear();
pendingMoveHexes.clear();
recoveredFromMoveNak = true;
}
// GUI keeps track for which doMove()'s server has not ackknowledged yet,
// so that we can catch cases when user attempts to click/move them again
// (e.g. when server response is slow)
private class PendingMove
{
public Legion mover;
public MasterHex currentHex;
public MasterHex targetHex;
public PendingMove(Legion mover, MasterHex current, MasterHex target)
{
this.mover = mover;
this.currentHex = current;
this.targetHex = target;
}
public boolean matches(Legion mover, MasterHex current,
MasterHex target)
{
return this.mover == mover && this.currentHex.equals(current)
&& this.targetHex.equals(target);
}
public Legion getLegion()
{
return mover;
}
}
// doMove was sent to server, store it in list
public void setMovePending(Legion mover, MasterHex currentHex,
MasterHex targetHex)
{
logPerhaps("setMovePending(Legion mover, MasterHex currentHex,");
// board.visualizeMoveTarget(mover, currentHex, targetHex);
PendingMove move = new PendingMove(mover, currentHex, targetHex);
synchronized (pendingMoves)
{
pendingMoves.add(move);
pendingMoveHexes.add(targetHex);
waitCursor();
}
updatePendingMovesText();
}
private void updatePendingMovesText()
{
logPerhaps("updatePendingText()");
int count;
synchronized (pendingMoves)
{
count = pendingMoves.size();
}
if (count > 0)
{
String movesPendingText = " (" + count + " move"
+ (count != 1 ? "s" : "") + " pending)";
board.setPendingText(movesPendingText);
}
else
{
board.setMovementPhase();
}
}
private void updatePendingSplitsText()
{
logPerhaps("updatePendingSplitsText()");
int pendingSplits = getOwningPlayer().countPendingSplits();
int pendingUndos = getOwningPlayer().countPendingUndoSplits();
if (pendingUndos > 0)
{
String splitsPendingText = " (" + pendingUndos + " undo-split"
+ (pendingUndos != 1 ? "s" : "") + " pending)";
board.setPendingText(splitsPendingText);
}
else if (pendingSplits > 0)
{
String splitsPendingText = " (" + pendingSplits + " split"
+ (pendingSplits != 1 ? "s" : "") + " pending)";
board.setPendingText(splitsPendingText);
}
else if (getGame().getTurnNumber() == 1)
{
if (client.getNumSplitsThisTurn() == 0)
{
informSplitRequiredFirstRound();
}
else if (client.getNumSplitsThisTurn() == 1)
{
informSplitDoneFirstRound();
}
board.setPendingText("");
}
else
{
board.updateSplitPendingText("Split stacks");
board.setMarkerCount(client.getOwningPlayer()
.getMarkersAvailable().size());
}
}
public Set<MasterHex> getPendingMoveHexes()
{
Set<MasterHex> hexes = new HashSet<MasterHex>();
synchronized (pendingMoves)
{
hexes.addAll(pendingMoveHexes);
}
return hexes;
}
public Set<MasterHex> getStillToMoveHexes()
{
HashSet<Legion> pendingLegions = new HashSet<Legion>();
synchronized (pendingMoves)
{
for (PendingMove move : pendingMoves)
{
pendingLegions.add(move.getLegion());
}
}
return client.findUnmovedLegionHexes(true, pendingLegions);
}
// Search and remove this pendingMove from list
public void setMoveCompleted(Legion mover, MasterHex current,
MasterHex target)
{
logPerhaps("setMoveCompleted(Legion mover, MasterHex current,");
pendingMoveHexes.remove(target);
PendingMove foundMove = null;
for (PendingMove move : pendingMoves)
{
if (move.matches(mover, current, target))
{
foundMove = move;
continue;
}
}
if (foundMove != null)
{
// ok, remove later
}
else if (isRedoOngoing())
{
// move was probably done before saving
}
else if (recoveredFromMoveNak)
{
// OK, recover from Nak wiped out pendingMoves list (because one
// move was sent to server but did not really happen, and it's
// hard to figure out which).
// This way we loose some "safety checking", but at least no
// legal moves are prevented.
}
else
{
LOGGER.warning("Could not find pending move for legion " + mover
+ " from hex " + current + " to hex " + target + "; redo="
+ isRedoOngoing() + ", replay=" + isReplayOngoing());
}
synchronized (pendingMoves)
{
pendingMoves.remove(foundMove);
if (pendingMoves.isEmpty())
{
defaultCursor();
}
}
}
public void removeBattleChit(BattleUnit battleUnit)
{
logPerhaps("removeBattleChit(BattleUnit battleUnit)");
for (Iterator<GUIBattleChit> iterator = battleChits.iterator(); iterator
.hasNext();)
{
GUIBattleChit chit = iterator.next();
if (chit.getBattleUnit().equals(battleUnit))
{
iterator.remove();
}
}
}
public GUICallbacks getCallbackHandler()
{
return this;
}
public void answerPickColor(PlayerColor color)
{
logPerhaps("answerPickColor(PlayerColor color)");
getClient().answerPickColor(color);
}
public void leaveCarryMode()
{
logPerhaps("leaveCarryMode()");
getClient().leaveCarryMode();
}
public void applyCarries(BattleHex hex)
{
logPerhaps("applyCarries(BattleHex hex)");
getClient().applyCarries(hex);
}
public void acquireAngelCallback(Legion legion, CreatureType angelType)
{
LOGGER
.fine("CG: acquireAngelCallback(Legion legion, CreatureType angelType)");
getClient().acquireAngelCallback(legion, angelType);
}
public void answerFlee(Legion ally, boolean answer)
{
logPerhaps("answerFlee(Legion ally, boolean answer)");
if (getGame().getDefender().equals(ally))
{
dueOrNotChangesActions(false, "Flee answered (" + answer + ")");
}
getClient().answerFlee(ally, answer);
}
public void answerConcede(Legion legion, boolean answer)
{
logPerhaps("answerConcede(Legion legion, boolean answer)");
dueOrNotChangesActions(false, "Concede answered (" + answer + ")");
getClient().answerConcede(legion, answer);
}
public void doBattleMove(int tag, BattleHex hex)
{
logPerhaps("doBattleMove(int tag, BattleHex hex)");
getClient().doBattleMove(tag, hex);
}
public void undoBattleMove(BattleHex hex)
{
logPerhaps("undoBattleMove(BattleHex hex)");
getClient().undoBattleMove(hex);
}
public void strike(int tag, BattleHex hex)
{
logPerhaps("strike(int tag, BattleHex hex)");
getClient().strike(tag, hex);
}
public void doneWithBattleMoves()
{
logPerhaps("doneWithBattleMoves()");
getClient().doneWithBattleMoves();
}
public void doneWithStrikes()
{
logPerhaps("doneWithStrikes()");
getClient().doneWithStrikes(false);
}
public void concede()
{
logPerhaps("concede()");
getClient().concede();
}
}