diff --git a/changelog.md b/changelog.md index b55d5aaa..12c05a12 100644 --- a/changelog.md +++ b/changelog.md @@ -36,6 +36,7 @@ * **[Mission Generator]** Set F-14's IP waypoint according to the flight-plan's ingress point * **[Mission Generator]** Automatically de-spawn aircraft when arrival/divert is an off-map spawn * **[Options]** Option to de-spawn AI flights in the air if their start-type was manually set to In-Flight +* **[Config]** Preference setting to configure the server-port on which Retribution's back-end will run ## Fixes * **[Mission Generation]** Anti-ship strikes should use "group attack" in their attack-task diff --git a/game/persistency.py b/game/persistency.py index 6d9399cb..92790fb5 100644 --- a/game/persistency.py +++ b/game/persistency.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: _dcs_saved_game_folder: Optional[str] = None _prefer_liberation_payloads: bool = False +_server_port: int = 16880 # fmt: off @@ -75,11 +76,13 @@ class MigrationUnpickler(pickle.Unpickler): # fmt: on -def setup(user_folder: str, prefer_liberation_payloads: bool) -> None: +def setup(user_folder: str, prefer_liberation_payloads: bool, port: int) -> None: global _dcs_saved_game_folder - _dcs_saved_game_folder = user_folder global _prefer_liberation_payloads + global _server_port + _dcs_saved_game_folder = user_folder _prefer_liberation_payloads = prefer_liberation_payloads + _server_port = port if not save_dir().exists(): save_dir().mkdir(parents=True) @@ -126,6 +129,11 @@ def save_dir() -> Path: return base_path() / "Retribution" / "Saves" +def server_port() -> int: + global _server_port + return _server_port + + def _temporary_save_file() -> str: return str(save_dir() / "tmpsave.retribution") diff --git a/game/server/server.py b/game/server/server.py index 3cd47eb2..ff59fde2 100644 --- a/game/server/server.py +++ b/game/server/server.py @@ -2,6 +2,7 @@ import time from collections.abc import Iterator from contextlib import contextmanager from threading import Thread +from typing import Optional import uvicorn from uvicorn import Config @@ -13,12 +14,13 @@ from game.sim import GameUpdateEvents class Server(uvicorn.Server): - def __init__(self) -> None: + def __init__(self, port: Optional[int]) -> None: + settings = ServerSettings.get(port) super().__init__( Config( app=app, - host=ServerSettings.get().server_bind_address, - port=ServerSettings.get().server_port, + host=settings.server_bind_address, + port=settings.server_port, # Configured explicitly with default_logging.yaml or logging.yaml. log_config=None, ) diff --git a/game/server/settings.py b/game/server/settings.py index f24ae182..01540279 100644 --- a/game/server/settings.py +++ b/game/server/settings.py @@ -1,6 +1,7 @@ from __future__ import annotations from functools import lru_cache +from typing import Optional from pydantic_settings import BaseSettings @@ -30,5 +31,5 @@ class ServerSettings(BaseSettings): @classmethod @lru_cache - def get(cls) -> ServerSettings: - return cls() + def get(cls, port: Optional[int] = server_port) -> ServerSettings: + return cls(server_port=port) diff --git a/qt_ui/liberation_install.py b/qt_ui/liberation_install.py index 5359bb50..290a394b 100644 --- a/qt_ui/liberation_install.py +++ b/qt_ui/liberation_install.py @@ -12,6 +12,7 @@ global __dcs_saved_game_directory global __dcs_installation_directory global __last_save_file global __prefer_liberation_payloads +global __server_port USER_PATH = Path(os.environ["LOCALAPPDATA"]) / "DCSRetribution" @@ -25,6 +26,7 @@ def init(): global __last_save_file global __ignore_empty_install_directory global __prefer_liberation_payloads + global __server_port if PREFERENCES_PATH.exists(): try: @@ -40,6 +42,7 @@ def init(): __prefer_liberation_payloads = pref_data.get( "prefer_liberation_payloads", False ) + __server_port = pref_data.get("server_port", 16880) is_first_start = False except (KeyError, json.JSONDecodeError): __dcs_saved_game_directory = "" @@ -47,11 +50,13 @@ def init(): __last_save_file = "" __ignore_empty_install_directory = False __prefer_liberation_payloads = False + __server_port = 16880 is_first_start = True else: __last_save_file = "" __ignore_empty_install_directory = False __prefer_liberation_payloads = False + __server_port = 16880 try: __dcs_saved_game_directory = ( dcs.installation.get_dcs_saved_games_directory() @@ -68,18 +73,22 @@ def init(): __dcs_installation_directory = "" is_first_start = True - persistency.setup(__dcs_saved_game_directory, __prefer_liberation_payloads) + persistency.setup( + __dcs_saved_game_directory, __prefer_liberation_payloads, __server_port + ) return is_first_start -def setup(saved_game_dir, install_dir, prefer_liberation_payloads): +def setup(saved_game_dir, install_dir, prefer_liberation_payloads, port): global __dcs_saved_game_directory global __dcs_installation_directory global __prefer_liberation_payloads + global __server_port __dcs_saved_game_directory = saved_game_dir __dcs_installation_directory = install_dir __prefer_liberation_payloads = prefer_liberation_payloads - persistency.setup(__dcs_saved_game_directory, __prefer_liberation_payloads) + __server_port = port + persistency.setup(saved_game_dir, prefer_liberation_payloads, port) def setup_last_save_file(last_save_file): @@ -92,12 +101,14 @@ def save_config(): global __dcs_installation_directory global __last_save_file global __ignore_empty_install_directory + global __server_port pref_data = { "saved_game_dir": __dcs_saved_game_directory, "dcs_install_dir": __dcs_installation_directory, "last_save_file": __last_save_file, "ignore_empty_install_directory": __ignore_empty_install_directory, "prefer_liberation_payloads": __prefer_liberation_payloads, + "server_port": __server_port, } PREFERENCES_PATH.parent.mkdir(exist_ok=True, parents=True) with PREFERENCES_PATH.open("w") as prefs: @@ -119,6 +130,11 @@ def prefer_liberation_payloads(): return __prefer_liberation_payloads +def server_port(): + global __server_port + return __server_port + + def ignore_empty_install_directory(): global __ignore_empty_install_directory return __ignore_empty_install_directory diff --git a/qt_ui/main.py b/qt_ui/main.py index a224b630..1e4757ee 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -442,7 +442,8 @@ def main(): dump_task_priorities() return - with Server().run_in_thread(): + liberation_install.init() + with Server(liberation_install.server_port()).run_in_thread(): run_ui(game, UiFlags(args.dev, args.show_sim_speed_controls)) diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index c1e9e616..49073feb 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -8,6 +8,7 @@ from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineSettings from PySide6.QtWebEngineWidgets import QWebEngineView from game.server.settings import ServerSettings +from qt_ui.liberation_install import server_port from qt_ui.models import GameModel @@ -44,7 +45,7 @@ class QLiberationMap(QWebEngineView): url = QUrl("http://localhost:3000") else: url = QUrl.fromLocalFile(str(Path("client/build/index.html").resolve())) - server_settings = ServerSettings.get() + server_settings = ServerSettings.get(server_port()) host = server_settings.server_bind_address if host.startswith("::"): host = f"[{host}]" diff --git a/qt_ui/windows/preferences/QLiberationPreferences.py b/qt_ui/windows/preferences/QLiberationPreferences.py index 7fbadf9f..ed56f7af 100644 --- a/qt_ui/windows/preferences/QLiberationPreferences.py +++ b/qt_ui/windows/preferences/QLiberationPreferences.py @@ -12,6 +12,7 @@ from PySide6.QtWidgets import ( QPushButton, QVBoxLayout, QCheckBox, + QSpinBox, ) from qt_ui import liberation_install, liberation_theme @@ -46,6 +47,9 @@ class QLiberationPreferences(QFrame): self.payloads_cb = QCheckBox() self.payloads_cb.setChecked(self.prefer_liberation_payloads) + self.port = liberation_install.server_port() + self.port_input = QSpinBox() + self.initUi() def initUi(self): @@ -87,6 +91,17 @@ class QLiberationPreferences(QFrame): ) layout.addWidget(self.payloads_cb, 5, 1, alignment=Qt.AlignmentFlag.AlignRight) + layout.addWidget( + QLabel("Server port (restart required):"), + 6, + 0, + alignment=Qt.AlignmentFlag.AlignLeft, + ) + layout.addWidget(self.port_input, 6, 1, alignment=Qt.AlignmentFlag.AlignRight) + self.port_input.setRange(1, 2**16 - 1) + self.port_input.setValue(self.port) + self.port_input.setStyleSheet("QSpinBox{ width: 50 }") + main_layout.addLayout(layout) main_layout.addStretch() @@ -113,6 +128,7 @@ class QLiberationPreferences(QFrame): self.saved_game_dir = self.edit_saved_game_dir.text() self.dcs_install_dir = self.edit_dcs_install_dir.text() self.prefer_liberation_payloads = self.payloads_cb.isChecked() + self.port = self.port_input.value() set_theme_index(self.themeSelect.currentIndex()) if not os.path.isdir(self.saved_game_dir): @@ -169,7 +185,10 @@ class QLiberationPreferences(QFrame): return False liberation_install.setup( - self.saved_game_dir, self.dcs_install_dir, self.prefer_liberation_payloads + self.saved_game_dir, + self.dcs_install_dir, + self.prefer_liberation_payloads, + self.port, ) liberation_install.save_config() liberation_theme.save_theme_config()