Skip to content

Commit

Permalink
Merge pull request #5359 from Sleet01/RFE_5261_rotating_autosaves
Browse files Browse the repository at this point in the history
Implements #5261: Simple rotating per-round save file system.
  • Loading branch information
Sleet01 authored Apr 13, 2024
2 parents 7f666ed + db9a982 commit 5f14229
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 11 deletions.
2 changes: 2 additions & 0 deletions megamek/i18n/megamek/common/options/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ GameOptionsInfo.option.paranoid_autosave.displayableName=Paranoid Autosave
GameOptionsInfo.option.paranoid_autosave.description=If checked, the game will auto-save every phase. \nUnchecked by default
GameOptionsInfo.option.very_paranoid_autosave.displayableName=Very Paranoid Autosave
GameOptionsInfo.option.very_paranoid_autosave.description=If checked, the game will name each auto-save differently so that you can load at almost any point in the game. Note: This option can create a lot of files in long games. \nUnchecked by default
GameOptionsInfo.option.max_rotating_round_saves.displayableName=Maximum Rotating Saves
GameOptionsInfo.option.max_rotating_round_saves.description=Maximum number of rotating save files to keep \n(A new file is created at the start of each Round)
GameOptionsInfo.option.exclusive_db_deployment.displayableName=Exclusive Double Blind deployment zones
GameOptionsInfo.option.exclusive_db_deployment.description=If checked, player-exclusive deployment zones are enforced, to prevent units from deploying on top of each other.\nChecked by default.
GameOptionsInfo.option.deep_deployment.displayableName=Deep deployment zones
Expand Down
23 changes: 12 additions & 11 deletions megamek/src/megamek/common/options/GameOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public synchronized void initialize() {
addOption(base, OptionsConstants.BASE_TEAM_INITIATIVE, true);
addOption(base, OptionsConstants.BASE_AUTOSAVE_MSG, true);
addOption(base, OptionsConstants.BASE_PARANOID_AUTOSAVE, false);
addOption(base, OptionsConstants.BASE_MAX_NUMBER_ROUND_SAVES, 3);
addOption(base, OptionsConstants.BASE_EXCLUSIVE_DB_DEPLOYMENT, true);
addOption(base, OptionsConstants.BASE_BLIND_DROP, false);
addOption(base, OptionsConstants.BASE_REAL_BLIND_DROP, false);
Expand All @@ -72,9 +73,9 @@ public synchronized void initialize() {
addOption(base, OptionsConstants.BASE_RNG_LOG, false);
addOption(base, OptionsConstants.BASE_FLAMER_HEAT, false);
addOption(base, OptionsConstants.BASE_INFANTRY_DAMAGE_HEAT, false);
addOption(base, OptionsConstants.BASE_INDIRECT_FIRE, true);
addOption(base, OptionsConstants.BASE_BREEZE, false);
addOption(base, OptionsConstants.BASE_RANDOM_BASEMENTS, true);
addOption(base, OptionsConstants.BASE_INDIRECT_FIRE, true);
addOption(base, OptionsConstants.BASE_BREEZE, false);
addOption(base, OptionsConstants.BASE_RANDOM_BASEMENTS, true);
addOption(base, OptionsConstants.BASE_AUTO_AMS, true);
addOption(base, OptionsConstants.BASE_TURN_TIMER_TARGETING, 0);
addOption(base, OptionsConstants.BASE_TURN_TIMER_MOVEMENT, 0);
Expand Down Expand Up @@ -108,14 +109,14 @@ public synchronized void initialize() {
addOption(allowed, OptionsConstants.ALLOWED_ERA_BASED, false);
addOption(allowed, OptionsConstants.ALLOWED_ALLOW_ILLEGAL_UNITS, false);
addOption(allowed, OptionsConstants.ALLOWED_SHOW_EXTINCT, true);
addOption(allowed, OptionsConstants.ALLOWED_CLAN_IGNORE_EQ_LIMITS, false);
addOption(allowed, OptionsConstants.ALLOWED_NO_CLAN_PHYSICAL, false);
addOption(allowed, OptionsConstants.ALLOWED_ALLOW_NUKES, false);
addOption(allowed, OptionsConstants.ALLOWED_REALLY_ALLOW_NUKES, false);
IBasicOptionGroup advancedRules = addGroup("advancedRules");
addOption(advancedRules, OptionsConstants.ADVANCED_MINEFIELDS, false);
addOption(advancedRules, OptionsConstants.ADVANCED_HIDDEN_UNITS, true);
addOption(allowed, OptionsConstants.ALLOWED_CLAN_IGNORE_EQ_LIMITS, false);
addOption(allowed, OptionsConstants.ALLOWED_NO_CLAN_PHYSICAL, false);
addOption(allowed, OptionsConstants.ALLOWED_ALLOW_NUKES, false);
addOption(allowed, OptionsConstants.ALLOWED_REALLY_ALLOW_NUKES, false);

IBasicOptionGroup advancedRules = addGroup("advancedRules");
addOption(advancedRules, OptionsConstants.ADVANCED_MINEFIELDS, false);
addOption(advancedRules, OptionsConstants.ADVANCED_HIDDEN_UNITS, true);
addOption(advancedRules, OptionsConstants.ADVANCED_BLACK_ICE, false);
addOption(advancedRules, OptionsConstants.ADVANCED_DOUBLE_BLIND, false);
addOption(advancedRules, OptionsConstants.ADVANCED_TACOPS_SENSORS, false);
Expand Down
1 change: 1 addition & 0 deletions megamek/src/megamek/common/options/OptionsConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ public class OptionsConstants {
public static final String BASE_TEAM_INITIATIVE = "team_initiative";
public static final String BASE_AUTOSAVE_MSG = "autosave_msg";
public static final String BASE_PARANOID_AUTOSAVE = "paranoid_autosave";
public static final String BASE_MAX_NUMBER_ROUND_SAVES = "max_rotating_round_saves";
public static final String BASE_EXCLUSIVE_DB_DEPLOYMENT = "exclusive_db_deployment";
public static final String BASE_BLIND_DROP = "blind_drop";
public static final String BASE_REAL_BLIND_DROP = "real_blind_drop";
Expand Down
114 changes: 114 additions & 0 deletions megamek/src/megamek/common/service/AutosaveService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
*
* This file is part of MegaMek.
*
* MegaMek 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 3 of the License, or
* (at your option) any later version.
*
* MegaMek 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 MegaMek. If not, see <http://www.gnu.org/licenses/>.
*/
package megamek.common.service;

import megamek.MMConstants;
import megamek.codeUtilities.StringUtility;
import megamek.common.annotations.Nullable;
import megamek.common.options.OptionsConstants;
import megamek.common.preference.PreferenceManager;
import megamek.server.GameManager;
import org.apache.logging.log4j.LogManager;

import java.io.File;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class AutosaveService {
public final String FILENAME_FORMAT = "Round-%d-autosave%s.sav.gz";

//region Constructors
public AutosaveService() {

}
//endregion Constructors

public void performRollingAutosave(final GameManager gameManager) {
final int maxNumberAutosaves = gameManager.getGame().getOptions().intOption(OptionsConstants.BASE_MAX_NUMBER_ROUND_SAVES);
if (maxNumberAutosaves > 0) {
try {
final String fileName = getAutosaveFilename(gameManager);
if (!StringUtility.isNullOrBlank(fileName)) {
gameManager.saveGame(fileName, gameManager.getGame().getOptions().booleanOption(OptionsConstants.BASE_AUTOSAVE_MSG));
} else {
LogManager.getLogger().error("Unable to perform an autosave because of a null or empty file name");
}
} catch (Exception ex) {
LogManager.getLogger().error("", ex);
}
}
}

private @Nullable String getAutosaveFilename(final GameManager gameManager) {
// Get all autosave files in ascending order of date creation
final String savesDirectoryPath = MMConstants.SAVEGAME_DIR;
final File folder = new File(savesDirectoryPath);
final File[] files = folder.listFiles();
if (files != null) {
final List<File> autosaveFiles = Arrays.stream(files)
.filter(f -> f.getName().startsWith("Round-"))
.sorted(Comparator.comparing(File::lastModified))
.collect(Collectors.toList());

// Delete older autosave files if needed
final int maxNumberAutosaves = gameManager.getGame().getOptions().intOption(OptionsConstants.BASE_MAX_NUMBER_ROUND_SAVES);

int index = 0;
while ((autosaveFiles.size() >= maxNumberAutosaves) && (autosaveFiles.size() > index)) {
if (autosaveFiles.get(index).delete()) {
autosaveFiles.remove(index);
} else {
LogManager.getLogger().error("Unable to delete file " + autosaveFiles.get(index).getName());
index++;
}
}

// Find a unique name for this autosave
String fileName = null;

boolean repeatedName = true;
index = 1;
String localDateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern(
PreferenceManager.getClientPreferences().getStampFormat()));
while (repeatedName) {
fileName = String.format(
FILENAME_FORMAT,
gameManager.getGame().getRoundCount(),
localDateTime
);

repeatedName = false;
for (final File file : autosaveFiles) {
if (file.getName().compareToIgnoreCase(fileName) == 0) {
repeatedName = true;
break;
}
}
}
// Don't prepend the save dir as we're going to let saveGame handle that.
return Paths.get(fileName).toString();
}
return null;
}
}
4 changes: 4 additions & 0 deletions megamek/src/megamek/server/GameManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import megamek.common.planetaryconditions.PlanetaryConditions;
import megamek.common.planetaryconditions.Wind;
import megamek.common.preference.PreferenceManager;
import megamek.common.service.AutosaveService;
import megamek.common.util.*;
import megamek.common.util.fileUtils.MegaMekFile;
import megamek.common.verifier.*;
Expand Down Expand Up @@ -139,6 +140,8 @@ public Vector<Report> getvPhaseReport() {
*/
private Player playerRequestingGameMaster = null;

private AutosaveService asService = new AutosaveService();

/**
* Special packet queue for client feedback requests.
*/
Expand Down Expand Up @@ -1893,6 +1896,7 @@ private void prepareForPhase(GamePhase phase) {

if (!game.shouldDeployThisRound()) {
incrementAndSendGameRound();
asService.performRollingAutosave(this);
}

// setIneligible(phase);
Expand Down

0 comments on commit 5f14229

Please sign in to comment.