From 8e977f994fd8ff57a67293552deb01f4825472b9 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 16 Jul 2021 17:43:37 -0700 Subject: [PATCH] Remove LGBs from degraded loadouts without TGPs. This only takes effect for default loadouts. Custom loadouts set from the UI will allow LGBs. In the default case there will not be buddy-lase coordination so we should take iron bombs instead. Also adds single/double Mk 83 and Mk 82 weapon data to accomodate this. --- changelog.md | 1 + game/data/weapons.py | 35 ++++++++++-- gen/flights/loadouts.py | 73 ++++++++++++++++++++------ resources/weapons/bombs/GBU-10-2X.yaml | 2 + resources/weapons/bombs/GBU-10.yaml | 2 + resources/weapons/bombs/GBU-12-2X.yaml | 2 + resources/weapons/bombs/GBU-12.yaml | 2 + resources/weapons/bombs/GBU-16-2X.yaml | 2 + resources/weapons/bombs/GBU-16.yaml | 2 + resources/weapons/bombs/GBU-24.yaml | 1 + resources/weapons/bombs/Mk-82-2X.yaml | 18 +++++++ resources/weapons/bombs/Mk-82.yaml | 11 ++++ resources/weapons/bombs/Mk-83-2X.yaml | 7 +++ resources/weapons/bombs/Mk-83.yaml | 16 ++++++ resources/weapons/pods/atflir.yaml | 1 + resources/weapons/pods/lantirn.yaml | 7 +++ resources/weapons/pods/litening.yaml | 1 + 17 files changed, 162 insertions(+), 21 deletions(-) create mode 100644 resources/weapons/bombs/Mk-82-2X.yaml create mode 100644 resources/weapons/bombs/Mk-82.yaml create mode 100644 resources/weapons/bombs/Mk-83-2X.yaml create mode 100644 resources/weapons/bombs/Mk-83.yaml create mode 100644 resources/weapons/pods/lantirn.yaml diff --git a/changelog.md b/changelog.md index 7722fcd7..ba02af20 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ Saves from 3.x are not compatible with 5.0. ## Features/Improvements * **[Campaign]** Weapon data such as fallbacks and introduction years is now moddable. Due to the new architecture to support this, the old data was not automatically migrated. +* **[Campaign]** Era-restricted loadouts will now skip LGBs when no TGP is available in the loadout. This only applies to default loadouts; buddy-lasing can be coordinated with custom loadouts. * **[Campaign AI]** Overhauled campaign AI target prioritization. This currently only affects the ordering of DEAD missions. * **[Campaign AI]** Player front line stances can now be automated. Improved stance selection for AI. * **[Campaign AI]** Reworked layout of hold, join, split, and ingress points. Should result in much shorter flight plans in general while still maintaining safe join/split/hold points. diff --git a/game/data/weapons.py b/game/data/weapons.py index 6f2889ec..8e7c86c9 100644 --- a/game/data/weapons.py +++ b/game/data/weapons.py @@ -4,6 +4,7 @@ import datetime import inspect import logging from dataclasses import dataclass, field +from enum import unique, Enum from functools import cached_property from pathlib import Path from typing import Iterator, Optional, Any, ClassVar @@ -61,7 +62,7 @@ class Weapon: duplicate = cls._by_clsid[weapon.clsid] raise ValueError( "Weapon CLSID used in more than one weapon type: " - f"{duplicate.name} and {weapon.name}" + f"{duplicate.name} and {weapon.name}: {weapon.clsid}" ) cls._by_clsid[weapon.clsid] = weapon @@ -91,6 +92,13 @@ class Weapon: fallback = fallback.fallback +@unique +class WeaponType(Enum): + LGB = "LGB" + TGP = "TGP" + UNKNOWN = "unknown" + + @dataclass(frozen=True) class WeaponGroup: """Group of "identical" weapons loaded from resources/weapons. @@ -101,7 +109,10 @@ class WeaponGroup: """ #: The name of the weapon group in the resource file. - name: str = field(compare=False) + name: str + + #: The type of the weapon group. + type: WeaponType = field(compare=False) #: The year of introduction. introduction_year: Optional[int] = field(compare=False) @@ -152,9 +163,13 @@ class WeaponGroup: with group_file_path.open(encoding="utf8") as group_file: data = yaml.safe_load(group_file) name = data["name"] + try: + weapon_type = WeaponType(data["type"]) + except KeyError: + weapon_type = WeaponType.UNKNOWN year = data.get("year") fallback_name = data.get("fallback") - group = WeaponGroup(name, year, fallback_name) + group = WeaponGroup(name, weapon_type, year, fallback_name) for clsid in data["clsids"]: weapon = Weapon(clsid, group) Weapon.register(weapon) @@ -163,7 +178,12 @@ class WeaponGroup: @classmethod def register_clean_pylon(cls) -> None: - group = WeaponGroup("Clean pylon", introduction_year=None, fallback_name=None) + group = WeaponGroup( + "Clean pylon", + type=WeaponType.UNKNOWN, + introduction_year=None, + fallback_name=None, + ) cls.register(group) weapon = Weapon("", group) Weapon.register(weapon) @@ -172,7 +192,12 @@ class WeaponGroup: @classmethod def register_unknown_weapons(cls, seen_clsids: set[str]) -> None: unknown_weapons = set(weapon_ids.keys()) - seen_clsids - group = WeaponGroup("Unknown", introduction_year=None, fallback_name=None) + group = WeaponGroup( + "Unknown", + type=WeaponType.UNKNOWN, + introduction_year=None, + fallback_name=None, + ) cls.register(group) for clsid in unknown_weapons: weapon = Weapon(clsid, group) diff --git a/gen/flights/loadouts.py b/gen/flights/loadouts.py index 826cc01a..0e3dd4d6 100644 --- a/gen/flights/loadouts.py +++ b/gen/flights/loadouts.py @@ -1,9 +1,10 @@ from __future__ import annotations import datetime -from typing import Optional, List, Iterator, TYPE_CHECKING, Mapping +from collections import Iterable +from typing import Optional, Iterator, TYPE_CHECKING, Mapping -from game.data.weapons import Weapon, Pylon +from game.data.weapons import Weapon, Pylon, WeaponType from game.dcs.aircrafttype import AircraftType if TYPE_CHECKING: @@ -30,9 +31,28 @@ class Loadout: def derive_custom(self, name: str) -> Loadout: return Loadout(name, self.pylons, self.date, is_custom=True) + @staticmethod + def _fallback_for( + weapon: Weapon, + pylon: Pylon, + date: datetime.date, + skip_types: Optional[Iterable[WeaponType]] = None, + ) -> Optional[Weapon]: + if skip_types is None: + skip_types = set() + for fallback in weapon.fallbacks: + if not pylon.can_equip(fallback): + continue + if not fallback.available_on(date): + continue + if fallback.weapon_group.type in skip_types: + continue + return fallback + return None + def degrade_for_date(self, unit_type: AircraftType, date: datetime.date) -> Loadout: if self.date is not None and self.date <= date: - return Loadout(self.name, self.pylons, self.date) + return Loadout(self.name, self.pylons, self.date, self.is_custom) new_pylons = dict(self.pylons) for pylon_number, weapon in self.pylons.items(): @@ -41,16 +61,41 @@ class Loadout: continue if not weapon.available_on(date): pylon = Pylon.for_aircraft(unit_type, pylon_number) - for fallback in weapon.fallbacks: - if not pylon.can_equip(fallback): - continue - if not fallback.available_on(date): - continue - new_pylons[pylon_number] = fallback - break - else: + fallback = self._fallback_for(weapon, pylon, date) + if fallback is None: del new_pylons[pylon_number] - return Loadout(f"{self.name} ({date.year})", new_pylons, date) + else: + new_pylons[pylon_number] = fallback + loadout = Loadout(self.name, new_pylons, date, self.is_custom) + # If this is not a custom loadout, we should replace any LGBs with iron bombs if + # the loadout lost its TGP. + # + # If the loadout was chosen explicitly by the user, assume they know what + # they're doing. They may be coordinating buddy-lase. + if not loadout.is_custom: + loadout.replace_lgbs_if_no_tgp(unit_type, date) + return loadout + + def replace_lgbs_if_no_tgp( + self, unit_type: AircraftType, date: datetime.date + ) -> None: + for weapon in self.pylons.values(): + if weapon is not None and weapon.weapon_group.type is WeaponType.TGP: + # Have a TGP. Nothing to do. + return + + new_pylons = dict(self.pylons) + for pylon_number, weapon in self.pylons.items(): + if weapon is not None and weapon.weapon_group.type is WeaponType.LGB: + pylon = Pylon.for_aircraft(unit_type, pylon_number) + fallback = self._fallback_for( + weapon, pylon, date, skip_types={WeaponType.LGB} + ) + if fallback is None: + del new_pylons[pylon_number] + else: + new_pylons[pylon_number] = fallback + self.pylons = new_pylons @classmethod def iter_for(cls, flight: Flight) -> Iterator[Loadout]: @@ -72,10 +117,6 @@ class Loadout: date=None, ) - @classmethod - def all_for(cls, flight: Flight) -> List[Loadout]: - return list(cls.iter_for(flight)) - @classmethod def default_loadout_names_for(cls, flight: Flight) -> Iterator[str]: from gen.flights.flight import FlightType diff --git a/resources/weapons/bombs/GBU-10-2X.yaml b/resources/weapons/bombs/GBU-10-2X.yaml index 0f261926..c0c51345 100644 --- a/resources/weapons/bombs/GBU-10-2X.yaml +++ b/resources/weapons/bombs/GBU-10-2X.yaml @@ -1,4 +1,6 @@ name: 2xGBU-10 +type: LGB year: 1976 +fallback: 2xMk 84 clsids: - "{62BE78B1-9258-48AE-B882-279534C0D278}" diff --git a/resources/weapons/bombs/GBU-10.yaml b/resources/weapons/bombs/GBU-10.yaml index 36e30965..4b7306d1 100644 --- a/resources/weapons/bombs/GBU-10.yaml +++ b/resources/weapons/bombs/GBU-10.yaml @@ -1,5 +1,7 @@ name: GBU-10 +type: LGB year: 1976 +fallback: Mk 84 clsids: - "DIS_GBU_10" - "{BRU-32 GBU-10}" diff --git a/resources/weapons/bombs/GBU-12-2X.yaml b/resources/weapons/bombs/GBU-12-2X.yaml index 282667c7..2cd83f89 100644 --- a/resources/weapons/bombs/GBU-12-2X.yaml +++ b/resources/weapons/bombs/GBU-12-2X.yaml @@ -1,5 +1,7 @@ name: 2xGBU-12 +type: LGB year: 1976 +fallback: 2xMk 82 clsids: - "{M2KC_RAFAUT_GBU12}" - "{BRU33_2X_GBU-12}" diff --git a/resources/weapons/bombs/GBU-12.yaml b/resources/weapons/bombs/GBU-12.yaml index 3e9500b3..85705c79 100644 --- a/resources/weapons/bombs/GBU-12.yaml +++ b/resources/weapons/bombs/GBU-12.yaml @@ -1,5 +1,7 @@ name: GBU-12 +type: LGB year: 1976 +fallback: Mk 82 clsids: - "DIS_GBU_12" - "{BRU-32 GBU-12}" diff --git a/resources/weapons/bombs/GBU-16-2X.yaml b/resources/weapons/bombs/GBU-16-2X.yaml index 22a48d70..19afd987 100644 --- a/resources/weapons/bombs/GBU-16-2X.yaml +++ b/resources/weapons/bombs/GBU-16-2X.yaml @@ -1,5 +1,7 @@ name: 2xGBU-16 +type: LGB year: 1976 +fallback: 2xMk 83 clsids: - "{BRU33_2X_GBU-16}" - "{BRU-42_2*GBU-16_LEFT}" diff --git a/resources/weapons/bombs/GBU-16.yaml b/resources/weapons/bombs/GBU-16.yaml index c31f360e..c966af60 100644 --- a/resources/weapons/bombs/GBU-16.yaml +++ b/resources/weapons/bombs/GBU-16.yaml @@ -1,5 +1,7 @@ name: GBU-16 +type: LGB year: 1976 +fallback: Mk 83 clsids: - "DIS_GBU_16" - "{BRU-32 GBU-16}" diff --git a/resources/weapons/bombs/GBU-24.yaml b/resources/weapons/bombs/GBU-24.yaml index b9c8fd14..6258584f 100644 --- a/resources/weapons/bombs/GBU-24.yaml +++ b/resources/weapons/bombs/GBU-24.yaml @@ -1,4 +1,5 @@ name: GBU-24 +type: LGB year: 1986 fallback: GBU-10 clsids: diff --git a/resources/weapons/bombs/Mk-82-2X.yaml b/resources/weapons/bombs/Mk-82-2X.yaml new file mode 100644 index 00000000..3a3d7ea7 --- /dev/null +++ b/resources/weapons/bombs/Mk-82-2X.yaml @@ -0,0 +1,18 @@ +name: 2xMk 82 +fallback: Mk 82 +clsids: + - "{M2KC_RAFAUT_MK82}" + - "{BRU33_2X_MK-82}" + - "DIS_MK_82_DUAL_GDJ_II19_L" + - "DIS_MK_82_DUAL_GDJ_II19_R" + - "{D5D51E24-348C-4702-96AF-97A714E72697}" + - "{TER_9A_2L*MK-82}" + - "{TER_9A_2R*MK-82}" + - "{BRU-42_2*Mk-82_LEFT}" + - "{BRU-42_2*Mk-82_RIGHT}" + - "{BRU42_2*MK82 RS}" + - "{BRU3242_2*MK82 RS}" + - "{PHXBRU3242_2*MK82 RS}" + - "{BRU42_2*MK82 LS}" + - "{BRU3242_2*MK82 LS}" + - "{PHXBRU3242_2*MK82 LS}" diff --git a/resources/weapons/bombs/Mk-82.yaml b/resources/weapons/bombs/Mk-82.yaml new file mode 100644 index 00000000..70733a5b --- /dev/null +++ b/resources/weapons/bombs/Mk-82.yaml @@ -0,0 +1,11 @@ +name: Mk 82 +clsids: + - "{BRU-32 MK-82}" + - "{Mk_82B}" + - "{Mk_82BT}" + - "{Mk_82P}" + - "{Mk_82PT}" + - "{Mk_82SB}" + - "{Mk_82SP}" + - "{Mk_82YT}" + - "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}" diff --git a/resources/weapons/bombs/Mk-83-2X.yaml b/resources/weapons/bombs/Mk-83-2X.yaml new file mode 100644 index 00000000..4d2876ad --- /dev/null +++ b/resources/weapons/bombs/Mk-83-2X.yaml @@ -0,0 +1,7 @@ +name: 2xMk 83 +fallback: Mk 83 +clsids: + - "{BRU33_2X_MK-83}" + - "{18617C93-78E7-4359-A8CE-D754103EDF63}" + - "{BRU-42_2*Mk-83_LEFT}" + - "{BRU-42_2*Mk-83_RIGHT}" diff --git a/resources/weapons/bombs/Mk-83.yaml b/resources/weapons/bombs/Mk-83.yaml new file mode 100644 index 00000000..6df0f06e --- /dev/null +++ b/resources/weapons/bombs/Mk-83.yaml @@ -0,0 +1,16 @@ +name: Mk 83 +clsids: + - "{MAK79_MK83 1R}" + - "{MAK79_MK83 1L}" + - "{BRU-32 MK-83}" + - "{Mk_83BT}" + - "{Mk_83CT}" + - "{Mk_83P}" + - "{Mk_83PT}" + - "{BRU42_MK83 RS}" + - "{BRU3242_MK83 RS}" + - "{PHXBRU3242_MK83 RS}" + - "{7A44FF09-527C-4B7E-B42B-3F111CFE50FB}" + - "{BRU42_MK83 LS}" + - "{BRU3242_MK83 LS}" + - "{PHXBRU3242_MK83 LS}" diff --git a/resources/weapons/pods/atflir.yaml b/resources/weapons/pods/atflir.yaml index 3733a299..a33ee9ca 100644 --- a/resources/weapons/pods/atflir.yaml +++ b/resources/weapons/pods/atflir.yaml @@ -1,4 +1,5 @@ name: AN/ASQ-228 ATFLIR +type: TGP year: 2003 # A bit of a hack, but fixes the common case where the Hornet cheek station is # empty because no TGP is available. diff --git a/resources/weapons/pods/lantirn.yaml b/resources/weapons/pods/lantirn.yaml new file mode 100644 index 00000000..c9af761c --- /dev/null +++ b/resources/weapons/pods/lantirn.yaml @@ -0,0 +1,7 @@ +name: AN/AAQ-14 LANTIRN +type: TGP +year: 1990 +clsids: + - "{F14-LANTIRN-TP}" + - "{CAAC1CFD-6745-416B-AFA4-CB57414856D0}" + - "{D1744B93-2A8A-4C4D-B004-7A09CD8C8F3F}" diff --git a/resources/weapons/pods/litening.yaml b/resources/weapons/pods/litening.yaml index e6fd5141..4bee3ed6 100644 --- a/resources/weapons/pods/litening.yaml +++ b/resources/weapons/pods/litening.yaml @@ -1,4 +1,5 @@ name: AN/AAQ-28 LITENING +type: TGP year: 1999 # A bit of a hack, but fixes the common case where the Hornet cheek station is # empty because no TGP is available. For the Viper this will have no effect