Restructure save games into a zipped bundle.

This is the first step toward bundling all assets related to a save game
into a single item. That makes it easier to avoid clobbering "temporary"
assets from other games like the state.json, but also makes it easier
for players to file bug reports, since there's only a single asset to
upload.

This is only the first step because so far it only includes the various
save files: start of turn, end of last turn before results processing,
and "latest" (the game saved explicitly by the player).
This commit is contained in:
Dan Albert
2023-01-04 14:29:16 -08:00
parent 575470ae1b
commit 0f34946127
29 changed files with 650 additions and 162 deletions

View File

@@ -6,7 +6,7 @@ from shutil import copyfile
import dcs
from game import persistency
from game import persistence
global __dcs_saved_game_directory
global __dcs_installation_directory
@@ -61,7 +61,7 @@ def init():
__dcs_installation_directory = ""
is_first_start = True
persistency.setup(__dcs_saved_game_directory)
persistence.set_dcs_save_game_directory(Path(__dcs_saved_game_directory))
return is_first_start
@@ -70,10 +70,10 @@ def setup(saved_game_dir, install_dir):
global __dcs_installation_directory
__dcs_saved_game_directory = saved_game_dir
__dcs_installation_directory = install_dir
persistency.setup(__dcs_saved_game_directory)
persistence.set_dcs_save_game_directory(Path(__dcs_saved_game_directory))
def setup_last_save_file(last_save_file):
def setup_last_save_file(last_save_file: str) -> None:
global __last_save_file
__last_save_file = last_save_file
@@ -114,11 +114,10 @@ def set_ignore_empty_install_directory(value: bool):
__ignore_empty_install_directory = value
def get_last_save_file():
def get_last_save_file() -> Path | None:
global __last_save_file
print(__last_save_file)
if os.path.exists(__last_save_file):
return __last_save_file
return Path(__last_save_file)
else:
return None

View File

@@ -13,7 +13,7 @@ from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QApplication, QCheckBox, QSplashScreen
from dcs.payloads import PayloadDirectories
from game import Game, VERSION, logging_config, persistency
from game import Game, VERSION, logging_config, persistence
from game.campaignloader.campaign import Campaign, DEFAULT_BUDGET
from game.data.weapons import Pylon, Weapon, WeaponGroup
from game.dcs.aircrafttype import AircraftType
@@ -85,14 +85,14 @@ def run_ui(game: Game | None, ui_flags: UiFlags) -> None:
window = QLiberationFirstStartWindow()
window.exec_()
logging.info("Using {} as 'Saved Game Folder'".format(persistency.base_path()))
logging.info("Using {} as 'Saved Game Folder'".format(persistence.base_path()))
logging.info(
"Using {} as 'DCS installation folder'".format(
liberation_install.get_dcs_install_directory()
)
)
inject_custom_payloads(Path(persistency.base_path()))
inject_custom_payloads(Path(persistence.base_path()))
# Splash screen setup
pixmap = QPixmap("./resources/ui/splash_screen.png")
@@ -271,7 +271,7 @@ def create_game(
# Without this, it is not possible to use next turn (or anything that needs to check
# for loadouts) without saving the generated campaign and reloading it the normal
# way.
inject_custom_payloads(Path(persistency.base_path()))
inject_custom_payloads(Path(persistence.base_path()))
campaign = Campaign.from_file(campaign_path)
theater = campaign.load_theater(advanced_iads)
generator = GameGenerator(

View File

@@ -11,7 +11,7 @@ from PySide6.QtWidgets import (
)
import qt_ui.uiconstants as CONST
from game import Game, persistency
from game import Game, persistence
from game.ato.package import Package
from game.ato.traveltime import TotEstimator
from game.profiling import logged_duration
@@ -290,7 +290,7 @@ class QTopPanel(QFrame):
with logged_duration("Simulating to first contact"):
self.sim_controller.run_to_first_contact()
self.sim_controller.generate_miz(
persistency.mission_path_for("liberation_nextturn.miz")
persistence.mission_path_for("liberation_nextturn.miz")
)
waiting = QWaitingForMissionResultWindow(self.game, self.sim_controller, self)

View File

@@ -1,6 +1,7 @@
import logging
import traceback
import webbrowser
from pathlib import Path
from typing import Optional
from PySide6.QtCore import QSettings, Qt, Signal
@@ -16,9 +17,10 @@ from PySide6.QtWidgets import (
)
import qt_ui.uiconstants as CONST
from game import Game, VERSION, persistency
from game import Game, VERSION
from game.debriefing import Debriefing
from game.layout import LAYOUTS
from game.persistence import SaveManager
from game.server import EventStream, GameContext
from game.server.dependencies import QtCallbacks, QtContext
from game.theater import ControlPoint, MissionTarget, TheaterGroundObject
@@ -106,11 +108,11 @@ class QLiberationWindow(QMainWindow):
if last_save_file:
try:
logging.info("Loading last saved game : " + str(last_save_file))
game = persistency.load_game(last_save_file)
game = SaveManager.load_player_save(last_save_file)
self.onGameGenerated(game)
self.updateWindowTitle(last_save_file if game else None)
except:
logging.info("Error loading latest save game")
logging.exception("Error loading latest save game")
else:
logging.info("No existing save game")
else:
@@ -316,58 +318,61 @@ class QLiberationWindow(QMainWindow):
wizard.accepted.connect(lambda: self.onGameGenerated(wizard.generatedGame))
def openFile(self):
if self.game is not None and self.game.savepath:
save_dir = self.game.savepath
if (
self.game is not None
and self.game.save_manager.player_save_location is not None
):
save_dir = str(self.game.save_manager.player_save_location)
else:
save_dir = str(persistency.save_dir())
save_dir = str(SaveManager.default_save_directory())
file = QFileDialog.getOpenFileName(
self,
"Select game file to open",
dir=save_dir,
filter="*.liberation",
filter="*.liberation.zip",
)
if file is not None and file[0] != "":
game = persistency.load_game(file[0])
GameUpdateSignal.get_instance().game_loaded.emit(game)
try:
game = SaveManager.load_player_save(Path(file[0]))
GameUpdateSignal.get_instance().game_loaded.emit(game)
self.updateWindowTitle(file[0])
self.updateWindowTitle(Path(file[0]))
except Exception:
logging.exception("Error loading save game %s", file[0])
def saveGame(self):
logging.info("Saving game")
if self.game.savepath:
persistency.save_game(self.game)
liberation_install.setup_last_save_file(self.game.savepath)
liberation_install.save_config()
if self.game.save_manager.player_save_location is not None:
self.game.save_manager.save_player()
else:
self.saveGameAs()
def saveGameAs(self):
if self.game is not None and self.game.savepath:
save_dir = self.game.savepath
if (
self.game is not None
and self.game.save_manager.player_save_location is not None
):
save_dir = str(self.game.save_manager.player_save_location)
else:
save_dir = str(persistency.save_dir())
save_dir = str(SaveManager.default_save_directory())
file = QFileDialog.getSaveFileName(
self,
"Save As",
dir=save_dir,
filter="*.liberation",
filter="*.liberation.zip",
)
if file is not None:
self.game.savepath = file[0]
persistency.save_game(self.game)
liberation_install.setup_last_save_file(self.game.savepath)
liberation_install.save_config()
if file is not None and file[0]:
self.game.save_manager.save_player(override_destination=Path(file[0]))
self.updateWindowTitle(Path(file[0]))
self.updateWindowTitle(file[0])
def updateWindowTitle(self, save_path: Optional[str] = None) -> None:
def updateWindowTitle(self, save_path: Path | None = None) -> None:
"""
to DCS Liberation - vX.X.X - file_name
"""
window_title = f"DCS Liberation - v{VERSION}"
if save_path: # appending the file name to title as it is updated
file_name = save_path.split("/")[-1].split(".liberation")[0]
file_name = save_path.name.split(".liberation.zip")[0]
window_title = f"{window_title} - {file_name}"
self.setWindowTitle(window_title)

View File

@@ -1,7 +1,6 @@
from __future__ import annotations
import logging
import os
from pathlib import Path
from typing import Optional
@@ -23,7 +22,6 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
from game import Game
from game.debriefing import Debriefing
from game.persistency import base_path
from game.profiling import logged_duration
from qt_ui.simcontroller import SimController
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
@@ -221,9 +219,6 @@ class QWaitingForMissionResultWindow(QDialog):
GameUpdateSignal.get_instance().updateGame(self.game)
self.close()
def debriefing_directory_location(self) -> str:
return os.path.join(base_path(), "liberation_debriefings")
def closeEvent(self, evt):
super(QWaitingForMissionResultWindow, self).closeEvent(evt)
if self.wait_thread is not None: