package games.strategy.engine.framework; import java.awt.AWTEvent; import java.awt.Container; import java.awt.EventQueue; import java.awt.Frame; import java.awt.Image; import java.awt.MediaTracker; import java.awt.Toolkit; import java.awt.Window; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.Map.Entry; import java.util.Properties; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import games.strategy.debug.ClientLogger; import games.strategy.debug.ErrorConsole; import games.strategy.engine.ClientContext; import games.strategy.engine.ClientFileSystemHelper; import games.strategy.engine.framework.lookandfeel.LookAndFeel; import games.strategy.engine.framework.map.download.DownloadMapsWindow; import games.strategy.engine.framework.map.download.MapDownloadController; import games.strategy.engine.framework.startup.ui.MainFrame; import games.strategy.engine.framework.system.HttpProxy; import games.strategy.engine.framework.system.Memory; import games.strategy.engine.lobby.server.GameDescription; import games.strategy.engine.lobby.server.LobbyServer; import games.strategy.net.Messengers; import games.strategy.triplea.settings.SystemPreferenceKey; import games.strategy.triplea.settings.SystemPreferences; import games.strategy.ui.SwingComponents; import games.strategy.util.CountDownLatchHandler; import games.strategy.util.EventThreadJOptionPane; import games.strategy.util.Util; import games.strategy.util.Version; /** * GameRunner - The entrance class with the main method. * In this class commonly used constants are getting defined and the Game is being launched */ public class GameRunner { public enum GameMode { SWING_CLIENT, HEADLESS_BOT } public static final String TRIPLEA_HEADLESS = "triplea.headless"; public static final String TRIPLEA_GAME_HOST_CONSOLE_PROPERTY = "triplea.game.host.console"; public static final int LOBBY_RECONNECTION_REFRESH_SECONDS_MINIMUM = 21600; public static final int LOBBY_RECONNECTION_REFRESH_SECONDS_DEFAULT = 2 * LOBBY_RECONNECTION_REFRESH_SECONDS_MINIMUM; public static final String NO_REMOTE_REQUESTS_ALLOWED = "noRemoteRequestsAllowed"; // not arguments: public static final int PORT = 3300; private static final String DELAYED_PARSING = "DelayedParsing"; // do not include this in the getProperties list. they are only for loading an old savegame. public static final String OLD_EXTENSION = ".old"; // argument options below: public static final String TRIPLEA_GAME_PROPERTY = "triplea.game"; public static final String TRIPLEA_SERVER_PROPERTY = "triplea.server"; public static final String TRIPLEA_CLIENT_PROPERTY = "triplea.client"; public static final String TRIPLEA_HOST_PROPERTY = "triplea.host"; public static final String TRIPLEA_PORT_PROPERTY = "triplea.port"; public static final String TRIPLEA_NAME_PROPERTY = "triplea.name"; public static final String TRIPLEA_SERVER_PASSWORD_PROPERTY = "triplea.server.password"; public static final String TRIPLEA_STARTED = "triplea.started"; public static final String LOBBY_HOST = "triplea.lobby.host"; public static final String LOBBY_GAME_COMMENTS = "triplea.lobby.game.comments"; public static final String LOBBY_GAME_HOSTED_BY = "triplea.lobby.game.hostedBy"; public static final String LOBBY_GAME_SUPPORT_EMAIL = "triplea.lobby.game.supportEmail"; public static final String LOBBY_GAME_SUPPORT_PASSWORD = "triplea.lobby.game.supportPassword"; public static final String LOBBY_GAME_RECONNECTION = "triplea.lobby.game.reconnection"; public static final String TRIPLEA_ENGINE_VERSION_BIN = "triplea.engine.version.bin"; private static final String TRIPLEA_DO_NOT_CHECK_FOR_UPDATES = "triplea.doNotCheckForUpdates"; public static final String TRIPLEA_SERVER_START_GAME_SYNC_WAIT_TIME = "triplea.server.startGameSyncWaitTime"; public static final String TRIPLEA_SERVER_OBSERVER_JOIN_WAIT_TIME = "triplea.server.observerJoinWaitTime"; // non-commandline-argument-properties (for preferences) // first time we've run this version of triplea? private static final String SYSTEM_INI = "system.ini"; public static final int MINIMUM_CLIENT_GAMEDATA_LOAD_GRACE_TIME = 20; private static final int DEFAULT_CLIENT_GAMEDATA_LOAD_GRACE_TIME = Math.max(MINIMUM_CLIENT_GAMEDATA_LOAD_GRACE_TIME, 25); // need time for network transmission of a large game data public static final int MINIMUM_SERVER_OBSERVER_JOIN_WAIT_TIME = MINIMUM_CLIENT_GAMEDATA_LOAD_GRACE_TIME + 10; private static final int DEFAULT_SERVER_OBSERVER_JOIN_WAIT_TIME = Math.max(DEFAULT_CLIENT_GAMEDATA_LOAD_GRACE_TIME + 10, 35); public static final int ADDITIONAL_SERVER_ERROR_DISCONNECTION_WAIT_TIME = 10; public static final int MINIMUM_SERVER_START_GAME_SYNC_WAIT_TIME = MINIMUM_SERVER_OBSERVER_JOIN_WAIT_TIME + ADDITIONAL_SERVER_ERROR_DISCONNECTION_WAIT_TIME + 110; private static final int DEFAULT_SERVER_START_GAME_SYNC_WAIT_TIME = Math.max(Math.max(MINIMUM_SERVER_START_GAME_SYNC_WAIT_TIME, 900), DEFAULT_SERVER_OBSERVER_JOIN_WAIT_TIME + ADDITIONAL_SERVER_ERROR_DISCONNECTION_WAIT_TIME + 110); public static final String MAP_FOLDER = "mapFolder"; private static String[] COMMAND_LINE_ARGS = {TRIPLEA_GAME_PROPERTY, TRIPLEA_SERVER_PROPERTY, TRIPLEA_CLIENT_PROPERTY, TRIPLEA_HOST_PROPERTY, TRIPLEA_PORT_PROPERTY, TRIPLEA_NAME_PROPERTY, TRIPLEA_SERVER_PASSWORD_PROPERTY, TRIPLEA_STARTED, LobbyServer.TRIPLEA_LOBBY_PORT_PROPERTY, LOBBY_HOST, LOBBY_GAME_COMMENTS, LOBBY_GAME_HOSTED_BY, TRIPLEA_ENGINE_VERSION_BIN, HttpProxy.PROXY_HOST, HttpProxy.PROXY_PORT, TRIPLEA_DO_NOT_CHECK_FOR_UPDATES, Memory.TRIPLEA_MEMORY_SET, MAP_FOLDER}; private static void usage(GameMode gameMode) { if (gameMode == GameMode.HEADLESS_BOT) { System.out.println("\nUsage and Valid Arguments:\n" + " " + TRIPLEA_GAME_PROPERTY + "=<FILE_NAME>\n" + " " + TRIPLEA_GAME_HOST_CONSOLE_PROPERTY + "=<true/false>\n" + " " + TRIPLEA_SERVER_PROPERTY + "=true\n" + " " + TRIPLEA_PORT_PROPERTY + "=<PORT>\n" + " " + TRIPLEA_NAME_PROPERTY + "=<PLAYER_NAME>\n" + " " + LOBBY_HOST + "=<LOBBY_HOST>\n" + " " + LobbyServer.TRIPLEA_LOBBY_PORT_PROPERTY + "=<LOBBY_PORT>\n" + " " + LOBBY_GAME_COMMENTS + "=<LOBBY_GAME_COMMENTS>\n" + " " + LOBBY_GAME_HOSTED_BY + "=<LOBBY_GAME_HOSTED_BY>\n" + " " + LOBBY_GAME_SUPPORT_EMAIL + "=<youremail@emailprovider.com>\n" + " " + LOBBY_GAME_SUPPORT_PASSWORD + "=<password for remote actions, such as remote stop game>\n" + " " + LOBBY_GAME_RECONNECTION + "=<seconds between refreshing lobby connection [min " + LOBBY_RECONNECTION_REFRESH_SECONDS_MINIMUM + "]>\n" + " " + TRIPLEA_SERVER_START_GAME_SYNC_WAIT_TIME + "=<seconds to wait for all clients to start the game>\n" + " " + TRIPLEA_SERVER_OBSERVER_JOIN_WAIT_TIME + "=<seconds to wait for an observer joining the game>\n" + " " + MAP_FOLDER + "=mapFolder" + "\n" + " You must start the Name and HostedBy with \"Bot\".\n" + " Game Comments must have this string in it: \"automated_host\".\n" + " You must include a support email for your host, so that you can be alerted by lobby admins when your " + "host has an error." + " (For example they may email you when your host is down and needs to be restarted.)\n" + " Support password is a remote access password that will allow lobby admins to remotely take the " + "following actions: ban player, stop game, shutdown server." + " (Please email this password to one of the lobby moderators, or private message an admin on the " + "TripleaWarClub.org website forum.)\n"); } else { System.out.println("Arguments\n" + " " + TRIPLEA_GAME_PROPERTY + "=<FILE_NAME>\n" + " " + TRIPLEA_SERVER_PROPERTY + "=true\n" + " " + TRIPLEA_CLIENT_PROPERTY + "=true\n" + " " + TRIPLEA_HOST_PROPERTY + "=<HOST_IP>\n" + " " + TRIPLEA_PORT_PROPERTY + "=<PORT>\n" + " " + TRIPLEA_NAME_PROPERTY + "=<PLAYER_NAME>\n" + " " + LobbyServer.TRIPLEA_LOBBY_PORT_PROPERTY + "=<LOBBY_PORT>\n" + " " + LOBBY_HOST + "=<LOBBY_HOST>\n" + " " + LOBBY_GAME_COMMENTS + "=<LOBBY_GAME_COMMENTS>\n" + " " + LOBBY_GAME_HOSTED_BY + "=<LOBBY_GAME_HOSTED_BY>\n" + " " + HttpProxy.PROXY_HOST + "=<Proxy_Host>\n" + " " + HttpProxy.PROXY_PORT + "=<Proxy_Port>\n" + " " + Memory.TRIPLEA_MEMORY_SET + "=true/false <did you set the xmx manually?>\n" + "\n" + "if there is only one argument, and it does not start with triplea.game, the argument will be \n" + "taken as the name of the file to load.\n" + "\n" + "Example\n" + " to start a game using the given file:\n" + "\n" + " triplea /home/sgb/games/test.xml\n" + "\n" + " or\n" + "\n" + " triplea triplea.game=/home/sgb/games/test.xml\n" + "\n" + " to connect to a remote host:\n" + "\n" + " triplea triplea.client=true triplea.host=127.0.0.0 triplea.port=3300 triplea.name=Paul\n" + "\n" + " to start a server with the given game\n" + "\n" + " triplea triplea.game=/home/sgb/games/test.xml triplea.server=true triplea.port=3300 triplea.name=Allan" + "\n" + " to start a server, you can optionally password protect the game using triplea.server.password=foo"); } } public static void main(final String[] args) { ErrorConsole.getConsole(); // do after we handle command line args Memory.checkForMemoryXMX(); SwingUtilities.invokeLater(() -> LookAndFeel.setupLookAndFeel()); showMainFrame(); new Thread(() -> setupLogging(GameMode.SWING_CLIENT)).start(); HttpProxy.setupProxies(); new Thread(() -> checkForUpdates()).start(); handleCommandLineArgs(args, COMMAND_LINE_ARGS, GameMode.SWING_CLIENT); } private static void showMainFrame() { SwingUtilities.invokeLater(() -> { final MainFrame frame = new MainFrame(); frame.requestFocus(); frame.toFront(); frame.setVisible(true); }); } /** * Move command line arguments to System.properties */ public static void handleCommandLineArgs(final String[] args, final String[] availableProperties, GameMode gameMode) { if (args.length == 1 && !args[0].contains("=")) { // assume a default single arg, convert the format so we can process as normally. args[0] = GameRunner.TRIPLEA_GAME_PROPERTY + "=" + args[0]; } boolean usagePrinted = false; for (final String arg : args) { String key; final int indexOf = arg.indexOf('='); if (indexOf > 0) { key = arg.substring(0, indexOf); } else { throw new IllegalArgumentException("Argument " + arg + " doesn't match pattern 'key=value'"); } if (!setSystemProperty(key, getValue(arg), availableProperties)) { System.out.println("Unrecogized:" + arg); if (!usagePrinted) { usagePrinted = true; usage(gameMode); } } } if (gameMode == GameMode.HEADLESS_BOT) { System.getProperties().setProperty(TRIPLEA_HEADLESS, "true"); boolean printUsage = false; final String playerName = System.getProperty(GameRunner.TRIPLEA_NAME_PROPERTY, ""); final String hostName = System.getProperty(GameRunner.LOBBY_GAME_HOSTED_BY, ""); if (playerName.length() < 7 || hostName.length() < 7 || !hostName.equals(playerName) || !playerName.startsWith("Bot") || !hostName.startsWith("Bot")) { System.out.println( "Invalid argument: " + GameRunner.TRIPLEA_NAME_PROPERTY + " and " + GameRunner.LOBBY_GAME_HOSTED_BY + " must start with \"Bot\" and be at least 7 characters long and be the same."); printUsage = true; } final String comments = System.getProperty(GameRunner.LOBBY_GAME_COMMENTS, ""); if (!comments.contains("automated_host")) { System.out.println( "Invalid argument: " + GameRunner.LOBBY_GAME_COMMENTS + " must contain the string \"automated_host\"."); printUsage = true; } final String email = System.getProperty(GameRunner.LOBBY_GAME_SUPPORT_EMAIL, ""); if (email.length() < 3 || !Util.isMailValid(email)) { System.out.println( "Invalid argument: " + GameRunner.LOBBY_GAME_SUPPORT_EMAIL + " must contain a valid email address."); printUsage = true; } final String reconnection = System.getProperty(GameRunner.LOBBY_GAME_RECONNECTION, "" + LOBBY_RECONNECTION_REFRESH_SECONDS_DEFAULT); try { final int reconnect = Integer.parseInt(reconnection); if (reconnect < LOBBY_RECONNECTION_REFRESH_SECONDS_MINIMUM) { System.out.println("Invalid argument: " + GameRunner.LOBBY_GAME_RECONNECTION + " must be an integer equal to or greater than " + LOBBY_RECONNECTION_REFRESH_SECONDS_MINIMUM + " seconds, and should normally be either " + LOBBY_RECONNECTION_REFRESH_SECONDS_DEFAULT + " or " + (2 * LOBBY_RECONNECTION_REFRESH_SECONDS_DEFAULT) + " seconds."); printUsage = true; } } catch (final NumberFormatException e) { System.out.println("Invalid argument: " + GameRunner.LOBBY_GAME_RECONNECTION + " must be an integer equal to or greater than " + LOBBY_RECONNECTION_REFRESH_SECONDS_MINIMUM + " seconds, and should normally be either " + LOBBY_RECONNECTION_REFRESH_SECONDS_DEFAULT + " or " + (2 * LOBBY_RECONNECTION_REFRESH_SECONDS_DEFAULT) + " seconds."); printUsage = true; } // no passwords allowed for bots // take any actions or commit to preferences final String clientWait = System.getProperty(GameRunner.TRIPLEA_SERVER_START_GAME_SYNC_WAIT_TIME, ""); final String observerWait = System.getProperty(GameRunner.TRIPLEA_SERVER_OBSERVER_JOIN_WAIT_TIME, ""); if (clientWait.length() > 0) { try { final int wait = Integer.parseInt(clientWait); GameRunner.setServerStartGameSyncWaitTime(wait); } catch (final NumberFormatException e) { System.out.println( "Invalid argument: " + GameRunner.TRIPLEA_SERVER_START_GAME_SYNC_WAIT_TIME + " must be an integer."); printUsage = true; } } if (observerWait.length() > 0) { try { final int wait = Integer.parseInt(observerWait); GameRunner.setServerObserverJoinWaitTime(wait); } catch (final NumberFormatException e) { System.out.println( "Invalid argument: " + GameRunner.TRIPLEA_SERVER_START_GAME_SYNC_WAIT_TIME + " must be an integer."); printUsage = true; } } if (printUsage || usagePrinted) { usage(gameMode); System.exit(-1); } } else { final String version = System.getProperty(TRIPLEA_ENGINE_VERSION_BIN); if (version != null && version.length() > 0) { final Version testVersion; try { testVersion = new Version(version); // if successful we don't do anything System.out.println(TRIPLEA_ENGINE_VERSION_BIN + ":" + version); if (!ClientContext.engineVersion().getVersion().equals(testVersion, false)) { System.out.println("Current Engine version in use: " + ClientContext.engineVersion()); } } catch (final Exception e) { System.getProperties().setProperty(TRIPLEA_ENGINE_VERSION_BIN, ClientContext.engineVersion().toString()); System.out.println(TRIPLEA_ENGINE_VERSION_BIN + ":" + ClientContext.engineVersion()); } } else { System.getProperties().setProperty(TRIPLEA_ENGINE_VERSION_BIN, ClientContext.engineVersion().toString()); System.out.println(TRIPLEA_ENGINE_VERSION_BIN + ":" + ClientContext.engineVersion()); } } } private static boolean setSystemProperty(String key, String value, String[] availableProperties) { for (final String property : availableProperties) { if (key.equals(property)) { if (property.equals(MAP_FOLDER)) { SystemPreferences.put(SystemPreferenceKey.MAP_FOLDER_OVERRIDE, value); } else { System.getProperties().setProperty(property, value); } System.out.println(property + ":" + value); return true; } } return false; } private static String getValue(final String arg) { final int index = arg.indexOf('='); if (index == -1) { return ""; } return arg.substring(index + 1); } public static void setupLogging(GameMode gameMode) { if (gameMode == GameMode.SWING_CLIENT) { Toolkit.getDefaultToolkit().getSystemEventQueue().push(new EventQueue() { @Override protected void dispatchEvent(AWTEvent newEvent) { try { super.dispatchEvent(newEvent); // This ensures, that all exceptions/errors inside any swing framework (like substance) are logged correctly } catch (Throwable t) { ClientLogger.logError(t); throw t; } } }); } } public static Properties getSystemIni() { final Properties rVal = new Properties(); final File systemIni = new File(ClientFileSystemHelper.getRootFolder(), SYSTEM_INI); if (systemIni.exists()) { try (FileInputStream fis = new FileInputStream(systemIni)) { rVal.load(fis); } catch (final IOException e) { ClientLogger.logQuietly(e); } } return rVal; } public static void writeSystemIni(final Properties properties) { final Properties toWrite; toWrite = getSystemIni(); for (final Entry<Object, Object> entry : properties.entrySet()) { toWrite.put(entry.getKey(), entry.getValue()); } final File systemIni = new File(ClientFileSystemHelper.getRootFolder(), SYSTEM_INI); try (FileOutputStream fos = new FileOutputStream(systemIni)) { toWrite.store(fos, SYSTEM_INI); } catch (final IOException e) { ClientLogger.logQuietly(e); } } public static boolean getDelayedParsing() { final Preferences pref = Preferences.userNodeForPackage(GameRunner.class); return pref.getBoolean(DELAYED_PARSING, true); } public static void setDelayedParsing(final boolean delayedParsing) { final Preferences pref = Preferences.userNodeForPackage(GameRunner.class); pref.putBoolean(DELAYED_PARSING, delayedParsing); try { pref.sync(); } catch (final BackingStoreException e) { ClientLogger.logQuietly(e); } } public static int getServerStartGameSyncWaitTime() { return Math.max(MINIMUM_SERVER_START_GAME_SYNC_WAIT_TIME, Preferences.userNodeForPackage(GameRunner.class) .getInt(TRIPLEA_SERVER_START_GAME_SYNC_WAIT_TIME, DEFAULT_SERVER_START_GAME_SYNC_WAIT_TIME)); } public static void resetServerStartGameSyncWaitTime() { setServerStartGameSyncWaitTime(DEFAULT_SERVER_START_GAME_SYNC_WAIT_TIME); } public static void setServerStartGameSyncWaitTime(final int seconds) { final int wait = Math.max(MINIMUM_SERVER_START_GAME_SYNC_WAIT_TIME, seconds); if (wait == getServerStartGameSyncWaitTime()) { return; } SystemPreferences.put(SystemPreferenceKey.TRIPLEA_SERVER_START_GAME_SYNC_WAIT_TIME, wait); } public static int getServerObserverJoinWaitTime() { return Math.max(MINIMUM_SERVER_OBSERVER_JOIN_WAIT_TIME, Preferences.userNodeForPackage(GameRunner.class) .getInt(TRIPLEA_SERVER_OBSERVER_JOIN_WAIT_TIME, DEFAULT_SERVER_OBSERVER_JOIN_WAIT_TIME)); } public static void resetServerObserverJoinWaitTime() { setServerObserverJoinWaitTime(DEFAULT_SERVER_OBSERVER_JOIN_WAIT_TIME); } public static void setServerObserverJoinWaitTime(final int seconds) { final int wait = Math.max(MINIMUM_SERVER_OBSERVER_JOIN_WAIT_TIME, seconds); if (wait == getServerObserverJoinWaitTime()) { return; } SystemPreferences.put(SystemPreferenceKey.TRIPLEA_SERVER_OBSERVER_JOIN_WAIT_TIME, wait); } private static void checkForUpdates() { new Thread(() -> { // do not check if we are the old extra jar. (a jar kept for backwards compatibility only) if (ClientFileSystemHelper.areWeOldExtraJar()) { return; } if (System.getProperty(TRIPLEA_SERVER_PROPERTY, "false").equalsIgnoreCase("true")) { return; } if (System.getProperty(TRIPLEA_CLIENT_PROPERTY, "false").equalsIgnoreCase("true")) { return; } if (System.getProperty(TRIPLEA_DO_NOT_CHECK_FOR_UPDATES, "false").equalsIgnoreCase("true")) { return; } // if we are joining a game online, or hosting, or loading straight into a savegame, do not check final String fileName = System.getProperty(TRIPLEA_GAME_PROPERTY, ""); if (fileName.trim().length() > 0) { return; } boolean busy = false; busy = checkForTutorialMap(); if (!busy) { busy = checkForLatestEngineVersionOut(); } if (!busy) { busy = checkForUpdatedMaps(); } }, "Checking Latest TripleA Engine Version").start(); } /** * @return true if we are out of date or this is the first time this triplea has ever been run. */ private static boolean checkForLatestEngineVersionOut() { try { final boolean firstTimeThisVersion = SystemPreferences.get(SystemPreferenceKey.TRIPLEA_FIRST_TIME_THIS_VERSION_PROPERTY, true); // check at most once per 2 days (but still allow a 'first run message' for a new version of triplea) final Calendar calendar = Calendar.getInstance(); final int year = calendar.get(Calendar.YEAR); final int day = calendar.get(Calendar.DAY_OF_YEAR); // format year:day final String lastCheckTime = SystemPreferences.get(SystemPreferenceKey.TRIPLEA_LAST_CHECK_FOR_ENGINE_UPDATE, ""); if (!firstTimeThisVersion && lastCheckTime != null && lastCheckTime.trim().length() > 0) { final String[] yearDay = lastCheckTime.split(":"); if (Integer.parseInt(yearDay[0]) >= year && Integer.parseInt(yearDay[1]) + 1 >= day) { return false; } } SystemPreferences.put(SystemPreferenceKey.TRIPLEA_LAST_CHECK_FOR_ENGINE_UPDATE, year + ":" + day); final EngineVersionProperties latestEngineOut = EngineVersionProperties.contactServerForEngineVersionProperties(); if (latestEngineOut == null) { return false; } if (ClientContext.engineVersion().getVersion().isLessThan(latestEngineOut.getLatestVersionOut())) { SwingUtilities .invokeLater(() -> EventThreadJOptionPane.showMessageDialog(null, latestEngineOut.getOutOfDateComponent(), "Please Update TripleA", JOptionPane.INFORMATION_MESSAGE, false, new CountDownLatchHandler(true))); return true; } } catch (final Exception e) { System.out.println("Error while checking for engine updates: " + e.getMessage()); } return false; } private static boolean checkForTutorialMap() { final MapDownloadController mapDownloadController = ClientContext.mapDownloadController(); final boolean promptToDownloadTutorialMap = mapDownloadController.shouldPromptToDownloadTutorialMap(); mapDownloadController.preventPromptToDownloadTutorialMap(); if (!promptToDownloadTutorialMap) { return false; } final StringBuilder messageBuilder = new StringBuilder(); messageBuilder.append("<html>"); messageBuilder.append("Would you like to download the tutorial map?<br>"); messageBuilder.append("<br>"); messageBuilder.append("(You can always download it later using the Download Maps<br>"); messageBuilder.append("command if you don't want to do it now.)"); messageBuilder.append("</html>"); SwingComponents.promptUser("Welcome to TripleA", messageBuilder.toString(), () -> { DownloadMapsWindow.showDownloadMapsWindow("Tutorial"); }); return true; } /** * @return true if we have any out of date maps. */ private static boolean checkForUpdatedMaps() { MapDownloadController downloadController = ClientContext.mapDownloadController(); return downloadController.checkDownloadedMapsAreLatest(); } public static Image getGameIcon(final Window frame) { Image img = null; try { img = frame.getToolkit().getImage(GameRunner.class.getResource("ta_icon.png")); } catch (final Exception ex) { ClientLogger.logError("ta_icon.png not loaded", ex); } final MediaTracker tracker = new MediaTracker(frame); tracker.addImage(img, 0); try { tracker.waitForAll(); } catch (final InterruptedException ex) { ClientLogger.logQuietly(ex); } return img; } public static void startNewTripleA(final Long maxMemory) { startGame(System.getProperty(GameRunner.TRIPLEA_GAME_PROPERTY), null, maxMemory); } public static void startGame(final String savegamePath, final String classpath, final Long maxMemory) { final List<String> commands = new ArrayList<>(); if (maxMemory != null && maxMemory > (32 * 1024 * 1024)) { ProcessRunnerUtil.populateBasicJavaArgs(commands, classpath, maxMemory); } else { ProcessRunnerUtil.populateBasicJavaArgs(commands, classpath); } if (savegamePath != null && savegamePath.length() > 0) { commands.add("-D" + GameRunner.TRIPLEA_GAME_PROPERTY + "=" + savegamePath); } // add in any existing command line items for (final String property : GameRunner.COMMAND_LINE_ARGS) { // we add game property above, and we add version bin in the populateBasicJavaArgs if (GameRunner.TRIPLEA_GAME_PROPERTY.equals(property) || GameRunner.TRIPLEA_ENGINE_VERSION_BIN.equals(property)) { continue; } final String value = System.getProperty(property); if (value != null) { commands.add("-D" + property + "=" + value); } else if (GameRunner.LOBBY_HOST.equals(property) || LobbyServer.TRIPLEA_LOBBY_PORT_PROPERTY.equals(property) || GameRunner.LOBBY_GAME_HOSTED_BY.equals(property)) { // for these 3 properties, we clear them after hosting, but back them up. final String oldValue = System.getProperty(property + GameRunner.OLD_EXTENSION); if (oldValue != null) { commands.add("-D" + property + "=" + oldValue); } } } // classpath for main commands.add(GameRunner.class.getName()); ProcessRunnerUtil.exec(commands); } public static void hostGame(final int port, final String playerName, final String comments, final String password, final Messengers messengers) { final List<String> commands = new ArrayList<>(); ProcessRunnerUtil.populateBasicJavaArgs(commands); commands.add("-D" + TRIPLEA_SERVER_PROPERTY + "=true"); commands.add("-D" + TRIPLEA_PORT_PROPERTY + "=" + port); commands.add("-D" + TRIPLEA_NAME_PROPERTY + "=" + playerName); commands.add("-D" + LOBBY_HOST + "=" + messengers.getMessenger().getRemoteServerSocketAddress().getAddress().getHostAddress()); commands .add("-D" + LobbyServer.TRIPLEA_LOBBY_PORT_PROPERTY + "=" + messengers.getMessenger().getRemoteServerSocketAddress().getPort()); commands.add("-D" + LOBBY_GAME_COMMENTS + "=" + comments); commands.add("-D" + LOBBY_GAME_HOSTED_BY + "=" + messengers.getMessenger().getLocalNode().getName()); if (password != null && password.length() > 0) { commands.add("-D" + TRIPLEA_SERVER_PASSWORD_PROPERTY + "=" + password); } final String fileName = System.getProperty(TRIPLEA_GAME_PROPERTY, ""); if (fileName.length() > 0) { commands.add("-D" + TRIPLEA_GAME_PROPERTY + "=" + fileName); } final String javaClass = GameRunner.class.getName(); commands.add(javaClass); ProcessRunnerUtil.exec(commands); } public static void joinGame(final GameDescription description, final Messengers messengers, final Container parent) { final GameDescription.GameStatus status = description.getStatus(); if (GameDescription.GameStatus.LAUNCHING.equals(status)) { return; } final Version engineVersionOfGameToJoin = new Version(description.getEngineVersion()); String newClassPath = null; final boolean sameVersion = ClientContext.engineVersion().getVersion().equals(engineVersionOfGameToJoin); if (!sameVersion) { try { newClassPath = findOldJar(engineVersionOfGameToJoin, false); } catch (final Exception e) { if (ClientFileSystemHelper.areWeOldExtraJar()) { JOptionPane.showMessageDialog(parent, "<html>Please run the default TripleA and try joining the online lobby for it instead. " + "<br>This TripleA engine is old and kept only for backwards compatibility and can only play with " + "people using the exact same version as this one. " + "<br><br>Host is using a different engine than you, and cannot find correct engine: " + engineVersionOfGameToJoin.toStringFull("_") + "</html>", "Correct TripleA Engine Not Found", JOptionPane.WARNING_MESSAGE); } else { JOptionPane.showMessageDialog(parent, "Host is using a different engine than you, and cannot find correct engine: " + engineVersionOfGameToJoin.toStringFull("_"), "Correct TripleA Engine Not Found", JOptionPane.WARNING_MESSAGE); } return; } // ask user if we really want to do this? final String messageString = "<html>This TripleA engine is version " + ClientContext.engineVersion().getVersion() + " and you are trying to join a game made with version " + engineVersionOfGameToJoin.toString() + "<br>However, this TripleA can only play with engines that are the exact same version as itself (x_x_x_x)." + "<br><br>TripleA now comes with older engines included with it, and has found the engine used by the host. " + "This is a new feature and is in 'beta' stage." + "<br>It will attempt to run a new instance of TripleA using the older engine jar file, and this instance " + "will join the host's game." + "<br>Your current instance will not be closed. Please report any bugs or issues." + "<br><br>Do you wish to continue?</html>"; final int answer = JOptionPane.showConfirmDialog(null, messageString, "Run old jar to join hosted game?", JOptionPane.YES_NO_OPTION); if (answer != JOptionPane.YES_OPTION) { return; } } joinGame(description.getPort(), description.getHostedBy().getAddress().getHostAddress(), newClassPath, messengers); } // newClassPath can be null private static void joinGame(final int port, final String hostAddressIP, final String newClassPath, final Messengers messengers) { final List<String> commands = new ArrayList<>(); ProcessRunnerUtil.populateBasicJavaArgs(commands, newClassPath); final String prefix = "-D"; commands.add(prefix + TRIPLEA_CLIENT_PROPERTY + "=true"); commands.add(prefix + TRIPLEA_PORT_PROPERTY + "=" + port); commands.add(prefix + TRIPLEA_HOST_PROPERTY + "=" + hostAddressIP); commands.add(prefix + TRIPLEA_NAME_PROPERTY + "=" + messengers.getMessenger().getLocalNode().getName()); commands.add(GameRunner.class.getName()); ProcessRunnerUtil.exec(commands); } public static String findOldJar(final Version oldVersionNeeded, final boolean ignoreMicro) throws IOException { if (ClientContext.engineVersion().getVersion().equals(oldVersionNeeded, ignoreMicro)) { return System.getProperty("java.class.path"); } // first, see if the default/main triplea can run it if (ClientFileSystemHelper.areWeOldExtraJar()) { final String version = System.getProperty(GameRunner.TRIPLEA_ENGINE_VERSION_BIN); if (version != null && version.length() > 0) { Version defaultVersion = null; try { defaultVersion = new Version(version); } catch (final Exception e) { // nothing, just continue } if (defaultVersion != null) { if (defaultVersion.equals(oldVersionNeeded, ignoreMicro)) { final String jarName = "triplea.jar"; // windows is in 'bin' folder, mac is in 'Java' folder. File binFolder = new File(ClientFileSystemHelper.getRootFolder(), "bin/"); if (!binFolder.exists()) { binFolder = new File(ClientFileSystemHelper.getRootFolder(), "Java/"); } if (binFolder.exists()) { final File[] files = binFolder.listFiles(); if (files == null) { throw new IOException("Cannot find 'bin' engine jars folder"); } File ourBinJar = null; for (final File f : Arrays.asList(files)) { if (!f.exists()) { continue; } final String jarPath = f.getCanonicalPath(); if (jarPath.contains(jarName)) { ourBinJar = f; break; } } if (ourBinJar == null) { throw new IOException( "Cannot find 'bin' engine jar for version: " + oldVersionNeeded.toStringFull("_")); } final String newClassPath = ourBinJar.getCanonicalPath(); if (newClassPath.length() <= 0) { throw new IOException( "Cannot find 'bin' engine jar for version: " + oldVersionNeeded.toStringFull("_")); } return newClassPath; } else { System.err.println("Cannot find 'bin' or 'Java' folder, where main triplea.jar should be."); } } } } } // so, what we do here is try to see if our installed copy of triplea includes older jars with it that are the same // engine as was used // for this savegame, and if so try to run it // System.out.println("System classpath: " + System.getProperty("java.class.path")); // we don't care what the last (micro) number is of the version number. example: triplea 1.5.2.1 can open 1.5.2.0 // savegames. final String jarName = "triplea_" + oldVersionNeeded.toStringFull("_", ignoreMicro); final File oldJarsFolder = new File(ClientFileSystemHelper.getRootFolder(), "old/"); if (!oldJarsFolder.exists()) { throw new IOException("Cannot find 'old' engine jars folder"); } final File[] files = oldJarsFolder.listFiles(); if (files == null) { throw new IOException("Cannot find 'old' engine jars folder"); } File ourOldJar = null; for (final File f : Arrays.asList(files)) { if (!f.exists()) { continue; } // final String jarPath = f.getCanonicalPath(); final String name = f.getName(); if (name.contains(jarName) && name.contains(".jar")) { ourOldJar = f; break; } } if (ourOldJar == null) { throw new IOException("Cannot find 'old' engine jar for version: " + oldVersionNeeded.toStringFull("_")); } final String newClassPath = ourOldJar.getCanonicalPath(); if (newClassPath.length() <= 0) { throw new IOException("Cannot find 'old' engine jar for version: " + oldVersionNeeded.toStringFull("_")); } return newClassPath; } public static void exitGameIfFinished() { SwingUtilities.invokeLater(() -> { boolean allFramesClosed = true; for (Frame f : Frame.getFrames()) { if (f.isVisible()) { allFramesClosed = false; break; } } if (allFramesClosed) { System.exit(0); } }); } }