diff --git a/game/campaignloader/campaign.py b/game/campaignloader/campaign.py index 61ee926f..bf582d6c 100644 --- a/game/campaignloader/campaign.py +++ b/game/campaignloader/campaign.py @@ -183,7 +183,7 @@ class Campaign: @classmethod def iter_campaign_defs(cls) -> Iterator[Path]: yield from cls.iter_campaigns_in_dir( - Path(persistency.base_path()) / "Retribution/Campaigns" + persistency.base_path() / "Retribution/Campaigns" ) yield from cls.iter_campaigns_in_dir(Path("resources/campaigns")) diff --git a/game/dcs/aircrafttype.py b/game/dcs/aircrafttype.py index 7dc7826f..20db8f2e 100644 --- a/game/dcs/aircrafttype.py +++ b/game/dcs/aircrafttype.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging from collections import defaultdict from dataclasses import dataclass -from functools import cached_property +from functools import cache, cached_property from pathlib import Path from typing import Any, ClassVar, Dict, Iterator, Optional, TYPE_CHECKING, Type @@ -355,6 +355,15 @@ class AircraftType(UnitType[Type[FlyingType]]): cls._load_all() yield from cls._by_name.values() + @classmethod + @cache + def priority_list_for_task(cls, task: FlightType) -> list[AircraftType]: + capable = [] + for aircraft in cls.iter_all(): + if aircraft.capable_of(task): + capable.append(aircraft) + return list(reversed(sorted(capable, key=lambda a: a.task_priority(task)))) + @staticmethod def each_dcs_type() -> Iterator[Type[FlyingType]]: yield from helicopter_map.values() diff --git a/game/factions/factionloader.py b/game/factions/factionloader.py index 179698a9..2e71e038 100644 --- a/game/factions/factionloader.py +++ b/game/factions/factionloader.py @@ -31,7 +31,7 @@ class FactionLoader: @classmethod def load_factions(cls: Type[FactionLoader]) -> Dict[str, Faction]: - user_faction_path = Path(persistency.base_path()) / "Retribution/Factions" + user_faction_path = persistency.base_path() / "Retribution/Factions" files = cls.find_faction_files_in( FACTION_DIRECTORY ) + cls.find_faction_files_in(user_faction_path) diff --git a/game/layout/layoutloader.py b/game/layout/layoutloader.py index ef6ddc38..035ef213 100644 --- a/game/layout/layoutloader.py +++ b/game/layout/layoutloader.py @@ -63,7 +63,7 @@ class LayoutLoader: """This will load all pre-loaded layouts from a pickle file. If pickle can not be loaded it will import and dump the layouts""" # We use a pickle for performance reasons. Importing takes many seconds - file = Path(persistency.base_path()) / LAYOUT_DUMP + file = persistency.base_path() / LAYOUT_DUMP if file.is_file(): # Load from pickle if existing with file.open("rb") as f: @@ -106,7 +106,7 @@ class LayoutLoader: self._dump_templates() def _dump_templates(self) -> None: - file = Path(persistency.base_path()) / LAYOUT_DUMP + file = persistency.base_path() / LAYOUT_DUMP dump = (VERSION, self._layouts) with file.open("wb") as fdata: pickle.dump(dump, fdata) diff --git a/game/persistency.py b/game/persistency.py index 06df4d82..c42a3f08 100644 --- a/game/persistency.py +++ b/game/persistency.py @@ -21,18 +21,18 @@ def setup(user_folder: str) -> None: save_dir().mkdir(parents=True) -def base_path() -> str: +def base_path() -> Path: global _dcs_saved_game_folder assert _dcs_saved_game_folder - return _dcs_saved_game_folder + return Path(_dcs_saved_game_folder) def settings_dir() -> Path: - return Path(base_path()) / "Retribution" / "Settings" + return base_path() / "Retribution" / "Settings" def save_dir() -> Path: - return Path(base_path()) / "Retribution" / "Saves" + return base_path() / "Retribution" / "Saves" def _temporary_save_file() -> str: @@ -44,7 +44,7 @@ def _autosave_path() -> str: def mission_path_for(name: str) -> Path: - return Path(base_path()) / "Missions" / name + return base_path() / "Missions" / name def load_game(path: str) -> Optional[Game]: diff --git a/game/squadrons/squadrondefloader.py b/game/squadrons/squadrondefloader.py index ed95d4c6..fac3a4bf 100644 --- a/game/squadrons/squadrondefloader.py +++ b/game/squadrons/squadrondefloader.py @@ -22,7 +22,7 @@ class SquadronDefLoader: def squadron_directories() -> Iterator[Path]: from game import persistency - yield Path(persistency.base_path()) / "Retribution/Squadrons" + yield persistency.base_path() / "Retribution/Squadrons" yield Path("resources/squadrons") def load(self) -> dict[AircraftType, list[SquadronDef]]: diff --git a/qt_ui/main.py b/qt_ui/main.py index 068e9f44..a73889b9 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -1,11 +1,13 @@ import argparse import logging +import ntpath import os import sys from datetime import datetime from pathlib import Path from typing import Optional +import yaml from PySide2 import QtWidgets from PySide2.QtCore import Qt from PySide2.QtGui import QPixmap @@ -13,10 +15,12 @@ from PySide2.QtWidgets import QApplication, QCheckBox, QSplashScreen from dcs.payloads import PayloadDirectories from game import Game, VERSION, logging_config, persistency +from game.ato import FlightType from game.campaignloader.campaign import Campaign, DEFAULT_BUDGET from game.data.weapons import Pylon, Weapon, WeaponGroup from game.dcs.aircrafttype import AircraftType from game.factions import FACTIONS +from game.persistency import base_path from game.profiling import logged_duration from game.server import EventStream, Server from game.settings import Settings @@ -69,12 +73,12 @@ def inject_mod_payloads(mod_path: Path) -> None: PayloadDirectories.set_preferred(payloads) -def on_game_load(game: Game | None) -> None: +def on_game_load(game: Optional[Game]) -> None: EventStream.drain() EventStream.put_nowait(GameUpdateEvents().game_loaded(game)) -def run_ui(game: Game | None, ui_flags: UiFlags) -> None: +def run_ui(game: Optional[Game], ui_flags: UiFlags) -> None: os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1" # Potential fix for 4K screens QApplication.setHighDpiScaleFactorRoundingPolicy( Qt.HighDpiScaleFactorRoundingPolicy.PassThrough @@ -107,7 +111,7 @@ def run_ui(game: Game | None, ui_flags: UiFlags) -> None: ) ) - inject_custom_payloads(Path(persistency.base_path())) + inject_custom_payloads(persistency.base_path()) # Splash screen setup pixmap = QPixmap("./resources/ui/splash_screen.png") @@ -260,6 +264,8 @@ def parse_args() -> argparse.Namespace: lint_weapons = subparsers.add_parser("lint-weapons") lint_weapons.add_argument("aircraft", help="Name of the aircraft variant to lint.") + subparsers.add_parser("dump-task-priorities") + return parser.parse_args() @@ -289,7 +295,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(persistency.base_path()) campaign = Campaign.from_file(campaign_path) theater = campaign.load_theater(advanced_iads) generator = GameGenerator( @@ -364,6 +370,41 @@ def lint_weapon_data_for_aircraft(aircraft: AircraftType) -> None: logging.warning(f'{weapon.clsid} "{weapon.name}" has no weapon data') +def fix_pycharm_debugger_if_needed() -> None: + """Applies workaround for a pycharm debugger bug. + + https://youtrack.jetbrains.com/issue/PY-53232/Debugger-doesnt-work-when-pyproj-is-imported + """ + # sys.gettrace() will return something whenever *some* debugger is being used. The + # pdb module will be loaded if that debugger is the built-in python debugger. + if sys.gettrace() is not None and "pdb" not in sys.modules: + logging.debug( + "Applying workaround for https://youtrack.jetbrains.com/issue/PY-53232/Debugger-doesnt-work-when-pyproj-is-imported" + ) + ntpath.sep = "\\" + + +def dump_task_priorities() -> None: + first_start = liberation_install.init() + if first_start: + sys.exit( + "Cannot dump task priorities without configuring DCS Liberation. Start the" + "UI for the first run configuration." + ) + + data: dict[str, dict[str, int]] = {} + for task in FlightType: + data[task.value] = { + a.name: a.task_priority(task) + for a in AircraftType.priority_list_for_task(task) + } + + debug_path = base_path() / "Debug" / "priorities.yaml" + debug_path.parent.mkdir(parents=True, exist_ok=True) + with debug_path.open("w", encoding="utf-8") as output: + yaml.dump(data, output, sort_keys=False, allow_unicode=True) + + def main(): logging_config.init_logging(VERSION) @@ -401,6 +442,9 @@ def main(): if args.subcommand == "lint-weapons": lint_weapon_data_for_aircraft(AircraftType.named(args.aircraft)) return + if args.subcommand == "dump-task-priorities": + dump_task_priorities() + return with Server().run_in_thread(): run_ui(game, UiFlags(args.dev, args.show_sim_speed_controls)) diff --git a/resources/tools/loadoutviewer.py b/resources/tools/loadoutviewer.py index 129c2f8a..ddd8ca41 100644 --- a/resources/tools/loadoutviewer.py +++ b/resources/tools/loadoutviewer.py @@ -122,7 +122,7 @@ def main() -> None: "the first run configuration." ) - inject_custom_payloads(Path(persistency.base_path())) + inject_custom_payloads(persistency.base_path()) if args.aircraft_id is None: show_all_aircraft(args.task)