From d9967bda8f02476ecf62ec58a30a19d226c5d8e1 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 12 Feb 2023 21:24:21 +0100 Subject: [PATCH] Add logic for specification of ground_units in campaign yaml file Resolves #22 --- changelog.md | 1 + game/campaignloader/campaign.py | 8 ++++++ game/campaignloader/campaigngroundconfig.py | 25 +++++++++++++++++ game/theater/start_generator.py | 30 ++++++++++++++++++--- game/version.py | 2 ++ qt_ui/main.py | 1 + qt_ui/windows/newgame/QNewGameWizard.py | 1 + 7 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 game/campaignloader/campaigngroundconfig.py diff --git a/changelog.md b/changelog.md index a989789f..b2e2addb 100644 --- a/changelog.md +++ b/changelog.md @@ -23,6 +23,7 @@ * **[UI]** Configurable LINK4 for Carriers. * **[Kneeboard]** Show package information in Support page * **[Campaign Design]** Ability to define designated CTLD zones for Control Points (Airbases & FOBs/FARPs) +* **[Campaign Design]** Ability to define preset groups for specific TGOs, given the preset group is accessible for the faction and the task matches. ## Fixes * **[UI]** Removed deprecated options diff --git a/game/campaignloader/campaign.py b/game/campaignloader/campaign.py index 0e18c358..b8531482 100644 --- a/game/campaignloader/campaign.py +++ b/game/campaignloader/campaign.py @@ -19,6 +19,7 @@ from game.theater.iadsnetwork.iadsnetwork import IadsNetwork from game.theater.theaterloader import TheaterLoader from game.version import CAMPAIGN_FORMAT_VERSION from .campaignairwingconfig import CampaignAirWingConfig +from .campaigngroundconfig import TgoConfig from .mizcampaignloader import MizCampaignLoader PERF_FRIENDLY = 0 @@ -137,6 +138,13 @@ class Campaign: return CampaignAirWingConfig({}) return CampaignAirWingConfig.from_campaign_data(squadron_data, theater) + def load_ground_forces_config(self) -> TgoConfig: + ground_forces = self.data.get("ground_forces", {}) + if not ground_forces: + logging.warning(f"Campaign {self.name} does not define any squadrons") + return TgoConfig({}) + return TgoConfig.from_campaign_data(ground_forces) + @property def is_out_of_date(self) -> bool: """Returns True if this campaign is not up to date with the latest format. diff --git a/game/campaignloader/campaigngroundconfig.py b/game/campaignloader/campaigngroundconfig.py new file mode 100644 index 00000000..ef243175 --- /dev/null +++ b/game/campaignloader/campaigngroundconfig.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from collections import defaultdict +from dataclasses import dataclass +from typing import Iterator, Optional + +from game.armedforces.forcegroup import ForceGroup + + +@dataclass(frozen=True) +class TgoConfig: + by_original_name: dict[str, ForceGroup] + + def __iter__(self) -> Iterator[str]: + return self.by_original_name.__iter__() + + def __getitem__(self, name: str) -> Optional[ForceGroup]: + return self.by_original_name.get(name) + + @classmethod + def from_campaign_data(cls, data: dict[str, str]) -> TgoConfig: + by_original_name: dict[str, ForceGroup] = defaultdict() + for tgo_name, force_group in data.items(): + by_original_name[tgo_name] = ForceGroup.from_preset_group(force_group) + return TgoConfig(by_original_name) diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 35a6ee7f..2af4cbae 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -4,7 +4,7 @@ import logging import random from dataclasses import dataclass from datetime import datetime, time -from typing import List +from typing import List, Optional import dcs.statics @@ -30,6 +30,7 @@ from .theatergroup import IadsGroundGroup, IadsRole, SceneryUnit, TheaterGroup from ..armedforces.armedforces import ArmedForces from ..armedforces.forcegroup import ForceGroup from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig +from ..campaignloader.campaigngroundconfig import TgoConfig from ..data.groups import GroupTask from ..profiling import logged_duration from ..settings import Settings @@ -47,6 +48,7 @@ class GeneratorSettings: no_lha: bool no_player_navy: bool no_enemy_navy: bool + tgo_config: TgoConfig @dataclass @@ -295,9 +297,31 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): self.generate_missile_sites() self.generate_coastal_sites() + def get_unit_group_for_task( + self, position: PresetLocation, task: GroupTask + ) -> Optional[ForceGroup]: + tgo_config = self.generator_settings.tgo_config + fg = tgo_config[position.original_name] + valid_fg = ( + fg + and task in fg.tasks + and all([u in self.faction.accessible_units for u in fg.units]) + ) + if valid_fg: + unit_group = fg + assert fg + self.armed_forces.add_or_update_force_group(fg) + else: + if fg and not valid_fg: + logging.warning( + f"Override in ground_forces failed for {fg} at {position.original_name}" + ) + unit_group = self.armed_forces.random_group_for_task(task) + return unit_group + def generate_armor_groups(self) -> None: for position in self.control_point.preset_locations.armor_groups: - unit_group = self.armed_forces.random_group_for_task(GroupTask.BASE_DEFENSE) + unit_group = self.get_unit_group_for_task(position, GroupTask.BASE_DEFENSE) if not unit_group: logging.error(f"{self.faction_name} has no ForceGroup for Armor") return @@ -351,7 +375,7 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): def generate_aa_at(self, location: PresetLocation, tasks: list[GroupTask]) -> None: for task in tasks: - unit_group = self.armed_forces.random_group_for_task(task) + unit_group = self.get_unit_group_for_task(location, task) if unit_group: # Only take next (smaller) aa_range when no template available for the # most requested range. Otherwise break the loop and continue diff --git a/game/version.py b/game/version.py index 859f5fab..9f7566e9 100644 --- a/game/version.py +++ b/game/version.py @@ -176,5 +176,7 @@ VERSION = _build_version_string() #: #: Version 10.6 #: * Designated CTLD zones for ControlPoints (Airbases & FOBs/FARPs) +#: * 'ground_forces' in yaml file to specify preset groups for TGOs, +#: given the group is available for the faction and the task matches CAMPAIGN_FORMAT_VERSION = (10, 6) diff --git a/qt_ui/main.py b/qt_ui/main.py index e405dea8..ca430b16 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -318,6 +318,7 @@ def create_game( no_lha=False, no_player_navy=False, no_enemy_navy=False, + tgo_config=campaign.load_ground_forces_config(), ), ModSettings( a4_skyhawk=False, diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py index 4e76c316..5f23581c 100644 --- a/qt_ui/windows/newgame/QNewGameWizard.py +++ b/qt_ui/windows/newgame/QNewGameWizard.py @@ -172,6 +172,7 @@ class NewGameWizard(QtWidgets.QWizard): no_lha=self.field("no_lha"), no_player_navy=self.field("no_player_navy"), no_enemy_navy=self.field("no_enemy_navy"), + tgo_config=campaign.load_ground_forces_config(), ) mod_settings = ModSettings( a4_skyhawk=self.field("a4_skyhawk"),