package games.strategy.engine.framework.startup.mc; import java.awt.Component; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.Observable; import java.util.concurrent.atomic.AtomicReference; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import javax.swing.JOptionPane; import games.strategy.debug.ClientLogger; import games.strategy.engine.ClientFileSystemHelper; import games.strategy.engine.data.EngineVersionException; import games.strategy.engine.data.GameData; import games.strategy.engine.data.GameParseException; import games.strategy.engine.data.GameParser; import games.strategy.engine.framework.GameDataManager; import games.strategy.engine.framework.ui.NewGameChooser; import games.strategy.engine.framework.ui.NewGameChooserEntry; import games.strategy.engine.framework.ui.NewGameChooserModel; import games.strategy.triplea.ai.proAI.ProAI; public class GameSelectorModel extends Observable { /** * Returns the name of the directory within a map's directory where the game xml is held. * Example: returns "games" which would be the games folder of "triplea/maps/someMapFooBar/games" */ public static final String DEFAULT_GAME_XML_DIRECTORY_NAME = "games"; private static final String DEFAULT_GAME_NAME_PREF = "DefaultGameName2"; private static final String DEFAULT_GAME_NAME = "Big World : 1942"; private static final String DEFAULT_GAME_URI_PREF = "DefaultGameURI"; private static final String DEFAULT_GAME_URI = ""; private GameData m_data = null; private String m_gameName = ""; private String m_gameVersion = ""; private String m_gameRound = ""; private String m_fileName = ""; private boolean m_canSelect = true; private boolean m_isHostHeadlessBot = false; // just for host bots, so we can get the actions for loading/saving games on the bots // from this model private ClientModel m_clientModelForHostBots = null; public GameSelectorModel() { setGameData(null); m_fileName = ""; } public void resetGameDataToNull() { setGameData(null); m_fileName = ""; } public void load(final GameData data, final String fileName) { setGameData(data); m_fileName = fileName; } public void load(final NewGameChooserEntry entry) { // we don't want to load anything if we are an older jar, because otherwise the user may get confused on which // version of triplea they // are using right now, // and then start a game with an older jar when they should be using the newest jar (we want user to be using the // normal default // [newest] triplea.jar for new games) if (ClientFileSystemHelper.areWeOldExtraJar()) { return; } m_fileName = entry.getLocation(); setGameData(entry.getGameData()); final Preferences prefs = Preferences.userNodeForPackage(this.getClass()); if (entry.getGameData() != null) { prefs.put(DEFAULT_GAME_NAME_PREF, entry.getGameData().getGameName()); } prefs.put(DEFAULT_GAME_URI_PREF, entry.getURI().toString()); try { prefs.flush(); } catch (final BackingStoreException e) { // ignore } } public GameData getGameData(final InputStream input) { final GameDataManager manager = new GameDataManager(); GameData newData; try { newData = manager.loadGame(input, null); if (newData != null) { return newData; } } catch (final IOException e) { ClientLogger.logQuietly(e); } return null; } public void load(final File file, final Component ui) { if (!file.exists()) { if (ui == null) { System.out.println("Could not find file:" + file); } else { error("Could not find file:" + file, ui); } return; } if (file.isDirectory()) { if (ui == null) { System.out.println("Cannot load a directory:" + file); } else { error("Cannot load a directory:" + file, ui); } return; } final GameDataManager manager = new GameDataManager(); GameData newData; final AtomicReference<String> gameName = new AtomicReference<>(); try { // if the file name is xml, load it as a new game if (file.getName().toLowerCase().endsWith("xml")) { try (FileInputStream fis = new FileInputStream(file)) { newData = (new GameParser(file.getAbsolutePath())).parse(fis, gameName, false); } } else { // the extension should be tsvg, but // try to load it as a saved game whatever the extension newData = manager.loadGame(file); } if (newData != null) { m_fileName = file.getName(); setGameData(newData); } } catch (final EngineVersionException e) { System.out.println(e.getMessage()); } catch (final Exception e) { String message = e.getMessage(); if (message == null && e.getStackTrace() != null) { message = e.getClass().getName() + " at " + e.getStackTrace()[0].toString(); } message = "Exception while parsing: " + file.getName() + " : " + (gameName.get() != null ? gameName.get() + " : " : "") + message; System.out.println(message); if (ui != null) { error(message + "\r\nPlease see console for full error log!", ui); } } } public boolean isSavedGame() { return !m_fileName.endsWith(".xml"); } private static void error(final String message, final Component ui) { JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(ui), message, "Could not load Game", JOptionPane.ERROR_MESSAGE); } public synchronized GameData getGameData() { return m_data; } public void setCanSelect(final boolean aBool) { synchronized (this) { m_canSelect = aBool; } notifyObs(); } public synchronized boolean canSelect() { return m_canSelect; } public void setIsHostHeadlessBot(final boolean aBool) { synchronized (this) { m_isHostHeadlessBot = aBool; } notifyObs(); } public synchronized boolean isHostHeadlessBot() { return m_isHostHeadlessBot; } public void setClientModelForHostBots(final ClientModel clientModel) { synchronized (this) { m_clientModelForHostBots = clientModel; } } public synchronized ClientModel getClientModelForHostBots() { return m_clientModelForHostBots; } /** * We dont have a gane data (ie we are a remote player and the data has not been sent yet), but * we still want to display game info. */ public void clearDataButKeepGameInfo(final String gameName, final String gameRound, final String gameVersion) { synchronized (this) { m_data = null; m_gameName = gameName; m_gameRound = gameRound; m_gameVersion = gameVersion; } notifyObs(); } public synchronized String getFileName() { if (m_data == null) { return "-"; } else { return m_fileName; } } public synchronized String getGameName() { return m_gameName; } public synchronized String getGameRound() { return m_gameRound; } public synchronized String getGameVersion() { return m_gameVersion; } public void setGameData(final GameData data) { synchronized (this) { if (data == null) { m_gameName = m_gameRound = m_gameVersion = "-"; } else { m_gameName = data.getGameName(); m_gameRound = "" + data.getSequence().getRound(); m_gameVersion = data.getGameVersion().toString(); } m_data = data; } notifyObs(); } private void notifyObs() { super.setChanged(); super.notifyObservers(m_data); super.clearChanged(); } private void resetDefaultGame() { final Preferences prefs = Preferences.userNodeForPackage(this.getClass()); prefs.put(DEFAULT_GAME_NAME_PREF, DEFAULT_GAME_NAME); prefs.put(DEFAULT_GAME_URI_PREF, DEFAULT_GAME_URI); try { prefs.flush(); } catch (final BackingStoreException e2) { // ignore } } public void loadDefaultGame(final Component ui) { // clear out ai cached properties (this ended up being the best place to put it, as we have definitely left a game // at this point) ProAI.gameOverClearCache(); new Thread(() -> loadDefaultGame(ui, false)).start(); } /** * @param forceFactoryDefault * - False is default behavior and causes the new game chooser model to be cleared (and refreshed if needed). * True causes the default game preference to be reset, but the model does not get cleared/refreshed (because * we only call with * 'true' if loading the user preferred map failed). */ private void loadDefaultGame(final Component ui, final boolean forceFactoryDefault) { // load the previously saved value final Preferences prefs = Preferences.userNodeForPackage(this.getClass()); if (forceFactoryDefault) { // we don't refresh the game chooser model because we have just removed a bad map from it resetDefaultGame(); } NewGameChooserEntry selectedGame = null; // just in case flush doesn't work, we still force it again here final String userPreferredDefaultGameURI = (forceFactoryDefault ? DEFAULT_GAME_URI : prefs.get(DEFAULT_GAME_URI_PREF, DEFAULT_GAME_URI)); // we don't want to load a game file by default that is not within the map folders we can load. (ie: if a previous // version of triplea // was using running a game within its root folder, we shouldn't open it) final String user = ClientFileSystemHelper.getUserRootFolder().toURI().toString(); if (!forceFactoryDefault && userPreferredDefaultGameURI != null && userPreferredDefaultGameURI.length() > 0 && userPreferredDefaultGameURI.contains(user)) { // if the user has a preferred URI, then we load it, and don't bother parsing or doing anything with the whole // game model list try { final URI defaultURI = new URI(userPreferredDefaultGameURI); selectedGame = new NewGameChooserEntry(defaultURI); } catch (final Exception e) { selectedGame = selectByName(ui, forceFactoryDefault); if (selectedGame == null) { return; } } if (!selectedGame.isGameDataLoaded()) { try { selectedGame.fullyParseGameData(); } catch (final GameParseException e) { loadDefaultGame(ui, true); return; } } } else { selectedGame = selectByName(ui, forceFactoryDefault); if (selectedGame == null) { return; } } load(selectedGame); } private NewGameChooserEntry selectByName(final Component ui, final boolean forceFactoryDefault) { NewGameChooserEntry selectedGame = null; final Preferences prefs = Preferences.userNodeForPackage(this.getClass()); // just in case flush doesn't work, we still force it again here final String userPreferredDefaultGameName = (forceFactoryDefault ? DEFAULT_GAME_NAME : prefs.get(DEFAULT_GAME_NAME_PREF, DEFAULT_GAME_NAME)); final NewGameChooserModel model = NewGameChooser.getNewGameChooserModel(); selectedGame = model.findByName(userPreferredDefaultGameName); if (selectedGame == null) { selectedGame = model.findByName(DEFAULT_GAME_NAME); } if (selectedGame == null && model.size() > 0) { selectedGame = model.get(0); } if (selectedGame == null) { return null; } if (!selectedGame.isGameDataLoaded()) { try { selectedGame.fullyParseGameData(); } catch (final GameParseException e) { // Load real default game... selectedGame.delayParseGameData(); model.removeEntry(selectedGame); loadDefaultGame(ui, true); return null; } } return selectedGame; } }