/**
* Copyright (C) 2002-2012 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* FreeCol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.client;
import android.app.Activity;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.freecolandroid.repackaged.java.awt.Dimension;
import org.freecolandroid.repackaged.java.awt.Font;
import org.freecolandroid.repackaged.javax.swing.SwingUtilities;
import net.sf.freecol.FreeCol;
import net.sf.freecol.client.control.ConnectController;
import net.sf.freecol.client.control.InGameController;
import net.sf.freecol.client.control.InGameInputHandler;
import net.sf.freecol.client.control.MapEditorController;
import net.sf.freecol.client.control.PreGameController;
import net.sf.freecol.client.control.PreGameInputHandler;
import net.sf.freecol.client.gui.GUI;
import net.sf.freecol.client.gui.action.ActionManager;
import net.sf.freecol.client.gui.plaf.FreeColLookAndFeel;
import net.sf.freecol.client.networking.Client;
import net.sf.freecol.common.FreeColException;
import net.sf.freecol.common.io.FreeColDataFile;
import net.sf.freecol.common.io.FreeColModFile;
import net.sf.freecol.common.io.FreeColSavegameFile;
import net.sf.freecol.common.io.FreeColTcFile;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.networking.DOMMessage;
import net.sf.freecol.common.networking.ServerAPI;
import net.sf.freecol.common.resources.ResourceManager;
import net.sf.freecol.common.resources.ResourceMapping;
import net.sf.freecol.server.FreeColServer;
import net.sf.freecol.server.FreeColServer.GameState;
/**
* The main control class for the FreeCol client. This class both
* starts and keeps references to the GUI and the control objects.
*/
public final class FreeColClient {
private static final Logger logger = Logger.getLogger(FreeColClient.class.getName());
// Control:
private ConnectController connectController;
private PreGameController preGameController;
private PreGameInputHandler preGameInputHandler;
private InGameController inGameController;
private InGameInputHandler inGameInputHandler;
private MapEditorController mapEditorController;
private ServerAPI serverAPI;
// GUI - this encapsulates the whole gui stuff
private GUI gui;
// Networking:
/**
* The network <code>Client</code> that can be used to send messages to
* the server.
*/
private Client client;
// Model:
private Game game;
/** The player "owning" this client. */
private Player player;
private boolean isRetired = false;
/**
* Indicates if the game has started, has nothing to do with
* whether or not the client is logged in.
*/
private boolean inGame = false;
/** The server that has been started from the client-GUI. */
private FreeColServer freeColServer = null;
private boolean mapEditor;
private boolean singleplayer;
private final ActionManager actionManager;
private ClientOptions clientOptions;
public final Worker worker;
/**
* Indicated whether or not there is an open connection to the
* server. This is not an indication of the existence of a
* Connection Object, but instead it is an indication of an
* approved login to a server.
*/
private boolean loggedIn = false;
/**
* Describe headless here.
*/
private boolean headless;
private Activity activity;
/**
* Creates a new <code>FreeColClient</code>. Creates the control objects
* and starts the GUI.
*
* @param savedGame An optional saved game.
* @param size An optional window size.
* @param sound True if sounds should be played
* @param splashFilename The name of the splash image.
* @param showOpeningVideo Display the opening video.
* @param fontName An optional override of the main font.
* @param activity
*/
public FreeColClient(final File savedGame, Dimension size,
final boolean sound,
final String splashFilename,
final boolean showOpeningVideo,
String fontName,
Activity activity) {
System.out.println("FreeColClient.FreeColClient()");
this.activity = activity;
gui = new GUI(this);
// Look for base data directory. Failure is fatal.
File baseDirectory = new File(FreeCol.getDataDirectory(), "base");
if (!baseDirectory.exists() || !baseDirectory.isDirectory()) {
System.err.println("Could not find base data directory: "
+ baseDirectory.getName());
System.err.println(" The data files could not be found by FreeCol. Please make sure");
System.err.println(" they are present. If FreeCol is looking in the wrong directory");
System.err.println(" then run the game with a command-line parameter:\n");
System.err.println(" --freecol-data <data-directory>\n");
System.exit(1);
}
headless = "true".equals(System.getProperty("java.awt.headless",
"false"));
// TODO: make headless operation work
if (headless) {
System.err.println("Headless operation disabled.\n");
System.exit(1);
}
mapEditor = false;
gui.displaySpashScreen(splashFilename);
// Determine the window size.
if (size != null && size.width < 0) {
size = gui.determineWindowSize();
}
// Control
connectController = new ConnectController(this, gui);
preGameController = new PreGameController(this, gui);
preGameInputHandler = new PreGameInputHandler(this, gui);
inGameController = new InGameController(this, gui);
inGameInputHandler = new InGameInputHandler(this, gui);
mapEditorController = new MapEditorController(this, gui);
actionManager = new ActionManager(this, gui);
worker = new Worker();
worker.start();
// Load resources.
// - base resources
// - resources in the default "classic" ruleset,
// - resources in the default actions
// TODO: probably should not need to load "classic", but there
// are a bunch of things in there (e.g. orderButton) that first
// need to move to base because the action manager requires them.
FreeColDataFile baseData = new FreeColDataFile(baseDirectory);
ResourceManager.setBaseMapping(baseData.getResourceMapping());
try {
FreeColTcFile tcData = new FreeColTcFile("classic");
ResourceManager.setTcMapping(tcData.getResourceMapping());
} catch(IOException e) {
System.out.println("Failed to load resource mapping from rule set 'classic'.");
System.exit(1);
}
actionManager.initializeActions();
// Load the client options, which handle reloading the
// resources specified in the active mods.
loadClientOptions(savedGame);
// Once resources are in place, get preloading started.
ResourceManager.preload(size);
// Work out the main font now that resources are loaded.
Font font = null;
if (fontName != null) {
font = Font.decode(fontName);
if (font == null) {
System.err.println("Font not found: " + fontName);
}
}
if (font == null) font = ResourceManager.getFont("NormalFont");
// Swing system and look-and-feel initialization.
try {
FreeColLookAndFeel fclaf
= new FreeColLookAndFeel(FreeCol.getDataDirectory(), size);
FreeColLookAndFeel.install(fclaf, font);
} catch (FreeColException e) {
System.err.println("Unable to install FreeCol look-and-feel.");
e.printStackTrace();
System.exit(1);
}
// Start the GUI.
final Dimension windowSize = size;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
gui.startGUI(windowSize, sound, showOpeningVideo,
savedGame != null);
}
});
// Load the optional saved game.
if (savedGame != null) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
connectController.loadGame(savedGame);
}
});
}
}
/**
* Meaningfully named access to the ServerAPI.
*
* @return A ServerAPI.
*/
public ServerAPI askServer() {
if (serverAPI == null) serverAPI = new ServerAPI(this);
return serverAPI;
}
/**
* Quits the application. This method uses {@link #showConfirmDialog} in
* order to get a "Are you sure"-confirmation from the user.
*/
public void askToQuit() {
// if (gui.showConfirmDialog("quitDialog.areYouSure.text", "ok", "cancel")) {
// quit();
// }
activity.finish();
}
/**
* Verifies if this client can save the current game
* Clients that do not have the server running, or that have not the required permissions
*cannot save and should have the menu entry disabled
* @return true if this client can save the game in progress, false otherwise
*/
public boolean canSaveCurrentGame(){
if (getFreeColServer() == null) {
return false;
} else if (getMyPlayer() == null) {
return false;
} else if (getFreeColServer().getGameState() == GameState.IN_GAME
&& !getMyPlayer().isAdmin()) {
return false;
}
return true;
}
/**
* Continue playing after winning the game.
*/
public void continuePlaying() {
client.send(DOMMessage.createNewRootElement("continuePlaying"));
}
public boolean currentPlayerIsMyPlayer() {
return inGame && game.getCurrentPlayer().equals(player);
}
public Activity getActivity() {
return activity;
}
/**
* Gets the object responsible for keeping and updating the actions.
*
* @return The <code>ActionManager</code>.
*/
public ActionManager getActionManager() {
return actionManager;
}
/**
* Gets the <code>Client</code> that can be used to send messages to the
* server.
*
* @return the <code>Client</code>
* @see #setClient
*/
public Client getClient() {
return client;
}
/**
* Returns the object keeping the current client options.
*
* @return The <code>ClientOptions</code>.
*/
public ClientOptions getClientOptions() {
return clientOptions;
}
/**
* Gets the controller responsible for starting a server and connecting to
* it.
*
* @return The <code>ConnectController</code>.
*/
public ConnectController getConnectController() {
return connectController;
}
/**
* Gets the <code>FreeColServer</code> started by the client.
*
* @return The <code>FreeColServer</code> or <code>null</code> if no
* server has been started.
*/
public FreeColServer getFreeColServer() {
return freeColServer;
}
/**
* Gets the <code>Game</code> that we are currently playing.
*
* @return The <code>Game</code>.
* @see #setGame
*/
public Game getGame() {
return game;
}
public GUI getGUI() {
return gui;
}
/**
* Gets the controller that will be used when the game has been started.
*
* @return The <code>InGameController</code>.
*/
public InGameController getInGameController() {
return inGameController;
}
/**
* Gets the input handler that will be used when the game has been started.
*
* @return The <code>InGameInputHandler</code>.
*/
public InGameInputHandler getInGameInputHandler() {
return inGameInputHandler;
}
public MapEditorController getMapEditorController() {
return mapEditorController;
}
/**
* Gets the <code>Player</code> that uses this client.
*
* @return The <code>Player</code> made to represent this clients user.
* @see #setMyPlayer(Player)
*/
public Player getMyPlayer() {
return player;
}
/**
* Gets the controller that will be used before the game has been started.
*
* @return The <code>PreGameController</code>.
*/
public PreGameController getPreGameController() {
return preGameController;
}
/**
* Gets the input handler that will be used before the game has been
* started.
*
* @return The <code>PreGameInputHandler</code>.
*/
public PreGameInputHandler getPreGameInputHandler() {
return preGameInputHandler;
}
/**
* Checks if this client is the game admin.
*
* @return <i>true</i> if the client is the game admin and <i>false</i>
* otherwise. <i>false</i> is also returned if a game have not yet
* been started.
*/
public boolean isAdmin() {
if (getMyPlayer() == null) {
return false;
}
return getMyPlayer().isAdmin();
}
/**
* Get the <code>Headless</code> value.
*
* @return a <code>boolean</code> value
*/
public boolean isHeadless() {
return headless;
}
/**
* Checks if the game has started.
* @return <i>true</i> if the game has started.
* @see #setInGame
*/
public boolean isInGame() {
return inGame;
}
/**
* Returns <i>true</i> if this client is logged in to a server or
* <i>false</i> otherwise.
*
* @return <i>true</i> if this client is logged in to a server or
* <i>false</i> otherwise.
*/
public boolean isLoggedIn() {
return loggedIn;
}
public boolean isMapEditor() {
return mapEditor;
}
/**
* Is the user playing in singleplayer mode.
*
* @return <i>true</i> if the user is playing in singleplayer mode and
* <i>false</i> otherwise.
* @see #setSingleplayer
*/
public boolean isSingleplayer() {
return singleplayer;
}
/**
* Displays a quit dialog and, if desired, logs out of the current game and
* shows the new game panel.
*/
public void newGame() {
Specification specification = null;
if (getGame() != null) {
if (isMapEditor()) {
specification = getGame().getSpecification();
} else if (gui.showConfirmDialog("stopCurrentGame.text",
"stopCurrentGame.yes",
"stopCurrentGame.no")) {
getConnectController().quitGame(true);
FreeCol.incrementFreeColSeed();
} else {
return;
}
gui.removeInGameComponents();
}
gui.showNewPanel(specification);
}
/**
* Quits the application without any questions.
*/
public void quit() {
getConnectController().quitGame(isSingleplayer());
exitActions();
gui.quit();
System.exit(0);
}
/**
* Quits the application. This method uses {@link #showConfirmDialog} in
* order to get a "Are you sure"-confirmation from the user.
*/
public void retire() {
if (gui.showConfirmDialog("retireDialog.areYouSure.text",
"ok", "cancel")) {
setIsRetired(true);
if (askServer().retire()) {
// Panel exit calls quit.
gui.showHighScoresPanel(null);
}
quit();
}
}
/**
* Sets the <code>Client</code> that shall be used to send messages to the
* server.
*
* @param client the <code>Client</code>
* @see #getClient
*/
public void setClient(Client client) {
this.client = client;
}
/**
* Sets the <code>FreeColServer</code> which has been started by the
* client gui.
*
* @param freeColServer The <code>FreeColServer</code>.
* @see #getFreeColServer()
*/
public void setFreeColServer(FreeColServer freeColServer) {
this.freeColServer = freeColServer;
}
/**
* Sets the <code>Game</code> that we are currently playing.
*
* @param game The <code>Game</code>.
* @see #getGame
*/
public void setGame(Game game) {
this.game = game;
}
/**
* Set the <code>Headless</code> value.
*
* @param newHeadless The new Headless value.
*/
public void setHeadless(final boolean newHeadless) {
this.headless = newHeadless;
}
/**
* Notifies this GUI that the game has started or ended.
* @param inGame Indicates whether or not the game has started.
*/
public void setInGame(boolean inGame) {
this.inGame = inGame;
}
/**
* Sets whether or not the user has retired the game.
*
* @param isRetired Indicates whether or not the user has retired the game.
*/
public void setIsRetired(boolean isRetired) {
this.isRetired = isRetired;
}
/**
* Sets whether or not this client is logged in to a server.
*
* @param loggedIn An indication of whether or not this client is logged in
* to a server.
*/
public void setLoggedIn(boolean loggedIn) {
this.loggedIn = loggedIn;
}
public void setMapEditor(boolean mapEditor) {
this.mapEditor = mapEditor;
}
/**
* Sets the <code>Player</code> that uses this client.
*
* @param player The <code>Player</code> made to represent this clients
* user.
* @see #getMyPlayer()
*/
public void setMyPlayer(Player player) {
this.player = player;
}
/**
* Sets whether or not this game is a singleplayer game.
*
* @param singleplayer Indicates whether or not this game is a singleplayer
* game.
* @see #isSingleplayer
*/
public void setSingleplayer(boolean singleplayer) {
this.singleplayer = singleplayer;
}
/**
* Start the game skipping turns.
*
* @param turns The number of turns to skip.
*/
public void skipTurns(int turns) {
if (freeColServer == null)
return;
freeColServer.getInGameController().setSkippedTurns(turns);
gui.closeMenus();
askServer().startSkipping();
}
private void exitActions () {
try {
// action: delete outdated autosave files
int validDays = getClientOptions().getInteger(ClientOptions.AUTOSAVE_VALIDITY);
long validPeriod = (long)validDays * 86400 * 1000; // millisecond equivalent of valid days
long timeNow = System.currentTimeMillis();
File autosaveDir = FreeCol.getAutosaveDirectory();
if (validPeriod != 0) {
// analyse all files in autosave directory
String[] flist = autosaveDir.list();
for ( int i = 0; flist != null && i < flist.length; i++ ) {
String filename = flist[i];
// delete files which are older than valid period set by user option
if (filename.endsWith(".fsg")) {
File saveGameFile = new File(autosaveDir, filename);
if (saveGameFile.lastModified() + validPeriod < timeNow) {
saveGameFile.delete();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Loads the client options.
* There are several sources:
* 1) Base options (set in the ClientOptions constructor with
* ClientOptions.addDefaultOptions())
* 2) Standard action manager actions
* 3) Saved game
* 4) User options
*
* @param savedGame An optional <code>File</code> to load options from.
*/
private void loadClientOptions(File savedGame) {
clientOptions = new ClientOptions();
logger.info("Loaded default client options.");
if (actionManager != null) {
clientOptions.add(actionManager);
logger.info("Loaded client options from the action manager.");
}
if (savedGame != null) {
try {
FreeColSavegameFile save = new FreeColSavegameFile(savedGame);
String fileName = FreeColSavegameFile.CLIENT_OPTIONS;
clientOptions.loadOptions(save.getInputStream(fileName));
logger.info("Loaded client options from saved game:"
+ savedGame.getPath() + "(" + fileName + ")");
} catch (Exception e) {
logger.warning("Unable to read client options from: "
+ savedGame.getPath());
}
}
File userOptions = FreeCol.getClientOptionsFile();
if (userOptions != null && userOptions.exists()) {
clientOptions.updateOptions(userOptions);
logger.info("Updated client options from user options file: "
+ userOptions.getPath());
} else {
logger.warning("User options file not present.");
}
// Reset the mod resources as a result of the client option update.
List<ResourceMapping> modMappings = new ArrayList<ResourceMapping>();
for (FreeColModFile f : clientOptions.getActiveMods()) {
modMappings.add(f.getResourceMapping());
}
ResourceManager.setModMappings(modMappings);
// Update the actions, resources may have changed.
if (actionManager != null)
actionManager.update();
}
}