#110 Ability to load/save settings

Also includes support for remaining enum/timedelta settings through the yaml file.
- timedelta's in minutes
- enum's should be written out: enumType.enumValue
This commit is contained in:
Raffson 2023-04-09 21:53:37 +02:00
parent 5916ed43d2
commit 3f509a876e
No known key found for this signature in database
GPG Key ID: B0402B2C9B764D99
3 changed files with 90 additions and 7 deletions

View File

@ -27,6 +27,10 @@ def base_path() -> str:
return _dcs_saved_game_folder return _dcs_saved_game_folder
def settings_dir() -> Path:
return Path(base_path()) / "Retribution" / "Settings"
def save_dir() -> Path: def save_dir() -> Path:
return Path(base_path()) / "Retribution" / "Saves" return Path(base_path()) / "Retribution" / "Saves"

View File

@ -16,6 +16,8 @@ from .skilloption import skill_option
from ..ato.starttype import StartType from ..ato.starttype import StartType
from ..weather import NightMissions from ..weather import NightMissions
Views = ForcedOptions.Views
@unique @unique
class AutoAtoBehavior(Enum): class AutoAtoBehavior(Enum):
@ -126,18 +128,18 @@ class Settings:
choices=["Full", "Abbreviated", "Dot Only", "Neutral Dot", "Off"], choices=["Full", "Abbreviated", "Dot Only", "Neutral Dot", "Off"],
default="Full", default="Full",
) )
map_coalition_visibility: ForcedOptions.Views = choices_option( map_coalition_visibility: Views = choices_option(
"Map visibility options", "Map visibility options",
page=DIFFICULTY_PAGE, page=DIFFICULTY_PAGE,
section=MISSION_RESTRICTIONS_SECTION, section=MISSION_RESTRICTIONS_SECTION,
choices={ choices={
"All": ForcedOptions.Views.All, "All": Views.All,
"Fog of war": ForcedOptions.Views.Allies, "Fog of war": Views.Allies,
"Allies only": ForcedOptions.Views.OnlyAllies, "Allies only": Views.OnlyAllies,
"Own aircraft only": ForcedOptions.Views.MyAircraft, "Own aircraft only": Views.MyAircraft,
"Map only": ForcedOptions.Views.OnlyMap, "Map only": Views.OnlyMap,
}, },
default=ForcedOptions.Views.All, default=Views.All,
) )
external_views_allowed: bool = boolean_option( external_views_allowed: bool = boolean_option(
"Allow external views", "Allow external views",
@ -673,6 +675,15 @@ class Settings:
self.plugins[self.plugin_settings_key(identifier)] = value self.plugins[self.plugin_settings_key(identifier)] = value
def __setstate__(self, state: dict[str, Any]) -> None: def __setstate__(self, state: dict[str, Any]) -> None:
# restore Enum & timedelta types
for key, value in state.items():
if isinstance(self.__dict__.get(key), timedelta) and isinstance(value, int):
state[key] = timedelta(minutes=value)
elif isinstance(self.__dict__.get(key), Enum) and isinstance(value, str):
state[key] = eval(value)
elif isinstance(value, dict):
state[key] = self.obj_hook(value)
# __setstate__ is called with the dict of the object being unpickled. We # __setstate__ is called with the dict of the object being unpickled. We
# can provide save compatibility for new settings options (which # can provide save compatibility for new settings options (which
# normally would not be present in the unpickled object) by creating a # normally would not be present in the unpickled object) by creating a
@ -716,3 +727,22 @@ class Settings:
for settings_field in fields(cls): for settings_field in fields(cls):
if SETTING_DESCRIPTION_KEY in settings_field.metadata: if SETTING_DESCRIPTION_KEY in settings_field.metadata:
yield settings_field yield settings_field
@staticmethod
def default_json(obj: Any) -> Any:
# Known types that don't like being serialized,
# so we introduce our own implementation...
if isinstance(obj, Enum):
return {"Enum": str(obj)}
elif isinstance(obj, timedelta):
return {"timedelta": round(obj.seconds / 60)}
return obj
@staticmethod
def obj_hook(obj: Any) -> Any:
if (value := obj.get("Enum")) is not None:
return eval(value)
elif (value := obj.get("timedelta")) is not None:
return timedelta(minutes=value)
else:
return obj

View File

@ -1,6 +1,8 @@
import json
import logging import logging
import textwrap import textwrap
from typing import Callable from typing import Callable
import zipfile
from PySide2.QtCore import QItemSelectionModel, QPoint, QSize, Qt from PySide2.QtCore import QItemSelectionModel, QPoint, QSize, Qt
from PySide2.QtGui import QStandardItem, QStandardItemModel from PySide2.QtGui import QStandardItem, QStandardItemModel
@ -19,10 +21,12 @@ from PySide2.QtWidgets import (
QStackedLayout, QStackedLayout,
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,
QFileDialog,
) )
import qt_ui.uiconstants as CONST import qt_ui.uiconstants as CONST
from game.game import Game from game.game import Game
from game.persistency import settings_dir
from game.server import EventStream from game.server import EventStream
from game.settings import ( from game.settings import (
BooleanOption, BooleanOption,
@ -332,6 +336,13 @@ class QSettingsWindow(QDialog):
self.layout.addWidget(self.categoryList, 0, 0, 1, 1) self.layout.addWidget(self.categoryList, 0, 0, 1, 1)
self.layout.addLayout(self.right_layout, 0, 1, 5, 1) self.layout.addLayout(self.right_layout, 0, 1, 5, 1)
load = QPushButton("Load Settings")
load.clicked.connect(self.load_settings)
self.layout.addWidget(load, 1, 0, 1, 1)
save = QPushButton("Save Settings")
save.clicked.connect(self.save_settings)
self.layout.addWidget(save, 2, 0, 1, 1)
self.setLayout(self.layout) self.setLayout(self.layout)
def initCheatLayout(self): def initCheatLayout(self):
@ -387,3 +398,41 @@ class QSettingsWindow(QDialog):
def onSelectionChanged(self): def onSelectionChanged(self):
index = self.categoryList.selectionModel().currentIndex().row() index = self.categoryList.selectionModel().currentIndex().row()
self.right_layout.setCurrentIndex(index) self.right_layout.setCurrentIndex(index)
def load_settings(self):
sd = settings_dir()
if not sd.exists():
sd.mkdir()
fd = QFileDialog(caption="Load Settings", directory=str(sd), filter="*.zip")
if fd.exec_():
zipfilename = fd.selectedFiles()[0]
with zipfile.ZipFile(zipfilename, "r") as zf:
filename = zipfilename.split("/")[-1].replace(".zip", ".json")
settings = json.loads(
zf.read(filename).decode("utf-8"),
object_hook=self.game.settings.obj_hook,
)
self.game.settings.__setstate__(settings)
self.close()
new = QSettingsWindow(self.game)
new.exec_()
def save_settings(self):
sd = settings_dir()
if not sd.exists():
sd.mkdir()
fd = QFileDialog(caption="Save Settings", directory=str(sd), filter="*.zip")
fd.setAcceptMode(QFileDialog.AcceptSave)
if fd.exec_():
zipfilename = fd.selectedFiles()[0]
with zipfile.ZipFile(zipfilename, "w", zipfile.ZIP_DEFLATED) as zf:
filename = zipfilename.split("/")[-1].replace(".zip", ".json")
zf.writestr(
filename,
json.dumps(
self.game.settings.__dict__,
indent=2,
default=self.game.settings.default_json,
),
zipfile.ZIP_DEFLATED,
)