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