/******************************************************************************* * Copyright (c) 2015 - 2017 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *******************************************************************************/ package jsettlers.main.replay; import jsettlers.common.map.MapLoadException; import jsettlers.common.menu.IGameExitListener; import jsettlers.common.menu.IStartedGame; import jsettlers.common.menu.IStartingGame; import jsettlers.common.resources.ResourceManager; import jsettlers.common.utils.FileUtils; import jsettlers.common.utils.FileUtils.IFileVisitor; import jsettlers.common.utils.Tuple; import jsettlers.common.utils.mutables.MutableInt; import jsettlers.input.tasks.EGuiAction; import jsettlers.input.tasks.SimpleGuiTask; import jsettlers.logic.constants.MatchConstants; import jsettlers.logic.map.loading.MapLoader; import jsettlers.logic.map.loading.list.MapList; import jsettlers.logic.player.PlayerSetting; import jsettlers.main.JSettlersGame; import jsettlers.main.JSettlersGame.GameRunner; import jsettlers.main.ReplayStartInformation; import jsettlers.network.NetworkConstants; import jsettlers.network.client.OfflineNetworkConnector; import jsettlers.network.client.interfaces.IGameClock; import jsettlers.network.client.interfaces.INetworkConnector; import java.io.*; import java.util.Arrays; import java.util.List; /** * * @author Andreas Eberle * */ public class ReplayUtils { public static MapLoader replayAndCreateSavegame(IReplayStreamProvider replayFile, int targetGameTimeMinutes, String newReplayFile) throws MapLoadException, IOException { OfflineNetworkConnector networkConnector = createPausingOfflineNetworkConnector(); ReplayStartInformation replayStartInformation = new ReplayStartInformation(); JSettlersGame game = loadGameFromReplay(replayFile, networkConnector, replayStartInformation); IStartedGame startedGame = startGame(game); // before we can save the clock reference, the game must be started IGameClock gameClock = MatchConstants.clock(); // after the game, the clock cannot be accessed any more => save reference before the game MapLoader newSavegame = playGameToTargetTimeAndGetSavegames(startedGame, networkConnector, targetGameTimeMinutes)[0]; // create a jsettlers.integration.replay basing on the savegame and containing the remaining tasks. createReplayOfRemainingTasks(newSavegame, replayStartInformation, newReplayFile, gameClock); System.out.println("Replayed: " + replayFile + " and created savegame: " + newSavegame); return newSavegame; } public static MapLoader[] replayAndCreateSavegames(IReplayStreamProvider replayFile, int[] targetGameTimeMinutes) throws MapLoadException, IOException { OfflineNetworkConnector networkConnector = createPausingOfflineNetworkConnector(); ReplayStartInformation replayStartInformation = new ReplayStartInformation(); JSettlersGame game = loadGameFromReplay(replayFile, networkConnector, replayStartInformation); MapLoader[] newSavegame = playGameToTargetTimeAndGetSavegames(game, networkConnector, targetGameTimeMinutes); System.out.println("Replayed: " + replayFile + " and created savegames: " +Arrays.asList( newSavegame)); return newSavegame; } public static OfflineNetworkConnector createPausingOfflineNetworkConnector() { OfflineNetworkConnector networkConnector = new OfflineNetworkConnector(); networkConnector.getGameClock().setPausing(true); return networkConnector; } public static MapLoader[] playGameToTargetTimeAndGetSavegames(JSettlersGame game, OfflineNetworkConnector networkConnector, final int... targetGameTimesMinutes) throws IOException { IStartedGame startedGame = startGame(game); return playGameToTargetTimeAndGetSavegames(startedGame, networkConnector, targetGameTimesMinutes); } private static MapLoader[] playGameToTargetTimeAndGetSavegames(IStartedGame startedGame, OfflineNetworkConnector networkConnector, final int... targetGameTimesMinutes) { final int[] targetGameTimesMs = getGameTimeMsFromMinutes(targetGameTimesMinutes); // schedule the save task and run the game to the target game time MapLoader[] savegames = new MapLoader[targetGameTimesMs.length]; for (int i = 0; i < targetGameTimesMs.length; i++) { final int targetGameTimeMs = targetGameTimesMs[i]; networkConnector.scheduleTaskAt(targetGameTimeMs / NetworkConstants.Client.LOCKSTEP_PERIOD, new SimpleGuiTask(EGuiAction.QUICK_SAVE, (byte) 0)); MatchConstants.clock().fastForwardTo(targetGameTimeMs); savegames[i] = getNewestSavegame(); } awaitShutdown(startedGame); return savegames; } private static int[] getGameTimeMsFromMinutes(final int... targetGameTimesMinutes) { final int[] targetGameTimesMs = new int[targetGameTimesMinutes.length]; for (int i = 0; i < targetGameTimesMinutes.length; i++) { targetGameTimesMs[i] = targetGameTimesMinutes[i] * 60 * 1000; } Arrays.sort(targetGameTimesMs); return targetGameTimesMs; } public static MapLoader getNewestSavegame() { List<? extends MapLoader> savedMaps = MapList.getDefaultList().getSavedMaps().getItems(); if (savedMaps.isEmpty()) { throw new RuntimeException("No saved games found."); } MapLoader newest = savedMaps.get(0); for (MapLoader map : savedMaps) { if (newest.getCreationDate().before(map.getCreationDate())) { newest = map; } } return newest; } public static void awaitShutdown(IStartedGame startedGame) { final MutableInt gameStopped = new MutableInt(0); startedGame.setGameExitListener(new IGameExitListener() { @Override public void gameExited(IStartedGame game) { gameStopped.value = 1; synchronized (gameStopped) { gameStopped.notifyAll(); } } }); ((GameRunner) startedGame).stopGame(); synchronized (gameStopped) { while (gameStopped.value == 0 && !startedGame.isShutdownFinished()) { try { gameStopped.wait(); } catch (InterruptedException e) { } } } } private static IStartedGame startGame(JSettlersGame game) { IStartingGame startingGame = game.start(); IStartedGame startedGame = waitForGameStartup(startingGame); return startedGame; } public static IStartedGame waitForGameStartup(IStartingGame game) { DummyStartingGameListener startingGameListener = new DummyStartingGameListener(); game.setListener(startingGameListener); return startingGameListener.waitForGameStartup(); } private static JSettlersGame loadGameFromReplay(IReplayStreamProvider replayFile, INetworkConnector networkConnector, ReplayStartInformation replayStartInformation) throws MapLoadException { System.out.println("Found loadable jsettlers.integration.replay file. Started loading it: " + replayFile); return JSettlersGame.loadFromReplayFile(replayFile, networkConnector, replayStartInformation); } private static void createReplayOfRemainingTasks(MapLoader newSavegame, ReplayStartInformation replayStartInformation, String newReplayFile, IGameClock gameClock) throws IOException { System.out.println("Creating new jsettlers.integration.replay file (" + newReplayFile + ")..."); ReplayStartInformation replayInfo = new ReplayStartInformation(0, newSavegame.getMapName(), newSavegame.getMapId(), replayStartInformation.getPlayerId(), replayStartInformation.getPlayerSettings()); DataOutputStream dos = new DataOutputStream(ResourceManager.writeUserFile(newReplayFile)); replayInfo.serialize(dos); gameClock.saveRemainingTasks(dos); dos.close(); System.out.println("New jsettlers.integration.replay file successfully created!"); } public static PlayMapResult playMapToTargetTimes(MapLoader map, final int... targetTimeMinutes) throws IOException { OfflineNetworkConnector networkConnector = ReplayUtils.createPausingOfflineNetworkConnector(); byte playerId = (byte) 0; JSettlersGame game = new JSettlersGame(map, 0L, networkConnector, playerId, PlayerSetting.createDefaultSettings(playerId, (byte) map.getMaxPlayers())) { @Override protected OutputStream createReplayWriteStream() throws IOException { return ResourceManager.writeConfigurationFile("jsettlers.integration.replay"); } }; final MapLoader[] savegames = ReplayUtils.playGameToTargetTimeAndGetSavegames(game, networkConnector, targetTimeMinutes); return new PlayMapResult(map, savegames); } private static File findNewestReplayFile() throws IOException { final File[] newestReplay = new File[1]; FileUtils.walkFileTree(new File(ResourceManager.getResourcesDirectory(), "logs"), new IFileVisitor() { private long newestModificationTime; @Override public void visitFile(File file) throws IOException { if (file.isDirectory() || !file.getName().endsWith("jsettlers.integration.replay.log")) { return; } if (newestModificationTime < file.lastModified()) { newestModificationTime = file.lastModified(); newestReplay[0] = file; } } }); return newestReplay[0]; } public interface IReplayStreamProvider { InputStream openStream() throws IOException; MapLoader getMap(ReplayStartInformation replayStartInformation) throws MapLoadException; } /** * A jsettlers.integration.replay file using the default list. * * @see MapList#defaultList */ public static class ReplayFile implements IReplayStreamProvider { private final File file; public ReplayFile(File file) { this.file = file; } @Override public InputStream openStream() throws IOException { return new FileInputStream(file); } @Override public MapLoader getMap(ReplayStartInformation replayStartInformation) throws MapLoadException { return MapList.getDefaultList().getMapById(replayStartInformation.getMapId()); } } public static class ReplayAndSavegames extends Tuple<File, MapLoader[]> implements IReplayStreamProvider { private static final long serialVersionUID = -334532778493138737L; public ReplayAndSavegames(File replayFile, MapLoader[] savegames) { super(replayFile, savegames); } public File getReplayFile() { return e1; } public MapLoader[] getSavegames() { return e2; } @Override public InputStream openStream() throws IOException { return new FileInputStream(e1); } @Override public MapLoader getMap(ReplayStartInformation replayStartInformation) throws MapLoadException { for (MapLoader m : e2) { if (m.getMapId().equals(replayStartInformation.getMapId())) { return m; } } throw new MapLoadException("No file found for " + replayStartInformation); } } public static class PlayMapResult implements IReplayStreamProvider { private final MapLoader map; private final MapLoader[] savegames; public PlayMapResult(MapLoader map, MapLoader[] savegames) { this.map = map; this.savegames = savegames; } @Override public InputStream openStream() throws IOException { return ResourceManager.getResourcesFileStream("jsettlers.integration.replay"); } @Override public MapLoader getMap(ReplayStartInformation replayStartInformation) throws MapLoadException { if (map.getMapId().equals(replayStartInformation.getMapId())) { return map; } throw new MapLoadException("No file found for " + replayStartInformation); } public MapLoader[] getSavegames() { return savegames; } } }