diff --git a/changelog.md b/changelog.md index 9a5751228..4cb28fb9c 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ Saves from 11.x are not compatible with 12.0.0. * **[Engine]** Support for DCS 2.9.9.2280. * **[Campaign]** Flights are assigned different callsigns appropriate to the faction. * **[Campaign]** Removed deprecated settings for generating persistent and invulnerable AWACs and tankers. +* **[Data]** Added ability to restrict weapons usage for a faction to a different year from the nominal weapon introduction year. Updated faction data to restrict more advanced missiles from Soviet client states during the cold war. Updated Egypt 2000 faction to restrict AIM-120 usage. * **[Mission Generation]** Added option to skip combat when fast forwarding, which progresses fast forward as if the combat did not occur. Simplified fast forward settings by consolidating "Fast forward mission to first contact" and "Player missions interrupt fast forward" into a single setting and expanding options for "Auto-resolve combat during fast-forward (WIP)". * **[Mods]** F/A-18 E/F/G Super Hornet mod version updated to 2.3. diff --git a/game/ato/loadouts.py b/game/ato/loadouts.py index 158afbf22..5fb99daa4 100644 --- a/game/ato/loadouts.py +++ b/game/ato/loadouts.py @@ -10,6 +10,8 @@ from game.data.weapons import Pylon, Weapon, WeaponType from game.dcs.aircrafttype import AircraftType +from game.factions.faction import Faction + from .flighttype import FlightType if TYPE_CHECKING: @@ -52,6 +54,7 @@ def _fallback_for( weapon: Weapon, pylon: Pylon, date: datetime.date, + faction: Faction, skip_types: Optional[Iterable[WeaponType]] = None, ) -> Optional[Weapon]: if skip_types is None: @@ -59,14 +62,16 @@ def _fallback_for( for fallback in weapon.fallbacks: if not pylon.can_equip(fallback): continue - if not fallback.available_on(date): + if not fallback.available_on(date, faction): 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: + def degrade_for_date( + self, unit_type: AircraftType, date: datetime.date, faction: Faction + ) -> Loadout: if self.date is not None and self.date <= date: return Loadout(self.name, self.pylons, self.date, self.is_custom) @@ -75,9 +80,9 @@ def degrade_for_date(self, unit_type: AircraftType, date: datetime.date) -> Load if weapon is None: del new_pylons[pylon_number] continue - if not weapon.available_on(date): + if not weapon.available_on(date, faction): pylon = Pylon.for_aircraft(unit_type, pylon_number) - fallback = self._fallback_for(weapon, pylon, date) + fallback = self._fallback_for(weapon, pylon, date, faction) if fallback is None: del new_pylons[pylon_number] else: @@ -89,11 +94,11 @@ def degrade_for_date(self, unit_type: AircraftType, date: datetime.date) -> Load # 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) + loadout.replace_lgbs_if_no_tgp(unit_type, date, faction) return loadout def replace_lgbs_if_no_tgp( - self, unit_type: AircraftType, date: datetime.date + self, unit_type: AircraftType, date: datetime.date, faction: Faction ) -> None: if self.has_weapon_of_type(WeaponType.TGP): return @@ -106,7 +111,7 @@ def replace_lgbs_if_no_tgp( 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} + weapon, pylon, date, faction, skip_types={WeaponType.LGB} ) if fallback is None: del new_pylons[pylon_number] diff --git a/game/data/weapons.py b/game/data/weapons.py index 13df6d876..dac7eb314 100644 --- a/game/data/weapons.py +++ b/game/data/weapons.py @@ -14,6 +14,8 @@ from dcs.weapons_data import weapon_ids from game.dcs.aircrafttype import AircraftType +from game.factions.faction import Faction + PydcsWeapon = Any PydcsWeaponAssignment = tuple[int, PydcsWeapon] @@ -77,8 +79,12 @@ def _load_all(cls) -> None: WeaponGroup.load_all() cls._loaded = True - def available_on(self, date: datetime.date) -> bool: + def available_on(self, date: datetime.date, faction: Faction) -> bool: introduction_year = self.weapon_group.introduction_year + if self.weapon_group.name in faction.weapons_introduction_year_overrides: + introduction_year = faction.weapons_introduction_year_overrides[ + self.weapon_group.name + ] if introduction_year is None: return True return date >= datetime.date(introduction_year, 1, 1) @@ -243,9 +249,9 @@ def equip(self, unit: FlyingUnit, weapon: Weapon) -> None: def make_pydcs_assignment(self, weapon: Weapon) -> PydcsWeaponAssignment: return self.number, weapon.pydcs_data - def available_on(self, date: datetime.date) -> Iterator[Weapon]: + def available_on(self, date: datetime.date, faction: Faction) -> Iterator[Weapon]: for weapon in self.allowed: - if weapon.available_on(date): + if weapon.available_on(date, faction): yield weapon @classmethod diff --git a/game/factions/faction.py b/game/factions/faction.py index 6cb5f8ccf..49a44dc35 100644 --- a/game/factions/faction.py +++ b/game/factions/faction.py @@ -3,6 +3,7 @@ import itertools import logging from dataclasses import dataclass, field +import datetime from functools import cached_property from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Type @@ -118,6 +119,10 @@ class Faction: #: both will use it. unrestricted_satnav: bool = False + #: Overrides default weapons introduction years for faction. Maps names of + #: weapons groups to their introduction years. + weapons_introduction_year_overrides: Dict[str, int] = field(default_factory=dict) + def has_access_to_dcs_type(self, unit_type: Type[DcsUnitType]) -> bool: # Vehicle and Ship Units if any(unit_type == u.dcs_unit_type for u in self.accessible_units): @@ -262,6 +267,10 @@ def from_dict(cls: Type[Faction], json: Dict[str, Any]) -> Faction: faction.unrestricted_satnav = json.get("unrestricted_satnav", False) + faction.weapons_introduction_year_overrides = json.get( + "weapons_introduction_year_overrides", {} + ) + return faction @property diff --git a/game/missiongenerator/aircraft/flightgroupconfigurator.py b/game/missiongenerator/aircraft/flightgroupconfigurator.py index 951d37560..3eeb0b95a 100644 --- a/game/missiongenerator/aircraft/flightgroupconfigurator.py +++ b/game/missiongenerator/aircraft/flightgroupconfigurator.py @@ -245,7 +245,11 @@ def setup_payload(self, unit: FlyingUnit, member: FlightMember) -> None: loadout = member.loadout if self.game.settings.restrict_weapons_by_date: - loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) + loadout = loadout.degrade_for_date( + self.flight.unit_type, + self.game.date, + self.flight.squadron.coalition.faction, + ) for pylon_number, weapon in loadout.pylons.items(): if weapon is None: diff --git a/qt_ui/windows/mission/flight/payload/QPylonEditor.py b/qt_ui/windows/mission/flight/payload/QPylonEditor.py index 5e3d013ce..059aaafba 100644 --- a/qt_ui/windows/mission/flight/payload/QPylonEditor.py +++ b/qt_ui/windows/mission/flight/payload/QPylonEditor.py @@ -26,7 +26,9 @@ def __init__( self.addItem("None", None) if self.game.settings.restrict_weapons_by_date: - weapons = pylon.available_on(self.game.date) + weapons = pylon.available_on( + self.game.date, flight.squadron.coalition.faction + ) else: weapons = pylon.allowed allowed = sorted(weapons, key=operator.attrgetter("name")) @@ -68,7 +70,11 @@ def weapon_from_loadout(self, loadout: Loadout) -> Optional[Weapon]: def matching_weapon_name(self, loadout: Loadout) -> str: if self.game.settings.restrict_weapons_by_date: - loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) + loadout = loadout.degrade_for_date( + self.flight.unit_type, + self.game.date, + self.flight.squadron.coalition.faction, + ) weapon = self.weapon_from_loadout(loadout) if weapon is None: return "None" diff --git a/resources/factions/egypt_1973.yaml b/resources/factions/egypt_1973.yaml index e9a5f7986..8ec06bfae 100644 --- a/resources/factions/egypt_1973.yaml +++ b/resources/factions/egypt_1973.yaml @@ -42,3 +42,7 @@ air_defense_units: - ZSU-57-2 'Sparka' has_jtac: false doctrine: coldwar +weapons_introduction_year_overrides: + R-3R - AAM, radar guided: 1980 + R-60 x 2: 1980 + R-60: 1980 \ No newline at end of file diff --git a/resources/factions/egypt_2000.yaml b/resources/factions/egypt_2000.yaml index 58752b64b..6135c54d5 100644 --- a/resources/factions/egypt_2000.yaml +++ b/resources/factions/egypt_2000.yaml @@ -82,3 +82,8 @@ air_defense_units: - ZSU-57-2 'Sparka' has_jtac: true jtac_unit: MQ-9 Reaper +weapons_introduction_year_overrides: + AIM-120B: 2050 + 2xAIM-120B: 2050 + AIM-120C: 2050 + 2xAIM-120C: 2050 \ No newline at end of file diff --git a/resources/factions/nva_1970.yaml b/resources/factions/nva_1970.yaml index 7963d50b6..5f313d41e 100644 --- a/resources/factions/nva_1970.yaml +++ b/resources/factions/nva_1970.yaml @@ -42,4 +42,8 @@ air_defense_units: - ZU-23 on Ural-375 - ZSU-23-4 Shilka has_jtac: "false" -doctrine: "coldwar" \ No newline at end of file +doctrine: "coldwar" +weapons_introduction_year_overrides: + R-3R - AAM, radar guided: 1980 + R-60 x 2: 1980 + R-60: 1980 \ No newline at end of file diff --git a/resources/factions/syria_1967.yaml b/resources/factions/syria_1967.yaml index 3e7fac3c0..deb91570c 100644 --- a/resources/factions/syria_1967.yaml +++ b/resources/factions/syria_1967.yaml @@ -46,3 +46,7 @@ helicopter_carrier_names: [] requirements: {} carrier_names: [] doctrine: coldwar +weapons_introduction_year_overrides: + R-3R - AAM, radar guided: 1980 + R-60 x 2: 1980 + R-60: 1980 \ No newline at end of file diff --git a/resources/factions/syria_1967_with_ww2_weapons.yaml b/resources/factions/syria_1967_with_ww2_weapons.yaml index 9f8cb70a4..08ffa873a 100644 --- a/resources/factions/syria_1967_with_ww2_weapons.yaml +++ b/resources/factions/syria_1967_with_ww2_weapons.yaml @@ -52,3 +52,7 @@ carrier_names: [] requirements: WW2 Asset Pack: https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/ doctrine: coldwar +weapons_introduction_year_overrides: + R-3R - AAM, radar guided: 1980 + R-60 x 2: 1980 + R-60: 1980 \ No newline at end of file diff --git a/resources/factions/syria_1973.yaml b/resources/factions/syria_1973.yaml index f7f44a787..8a693d7ae 100644 --- a/resources/factions/syria_1973.yaml +++ b/resources/factions/syria_1973.yaml @@ -51,3 +51,7 @@ helicopter_carrier_names: [] requirements: {} carrier_names: [] doctrine: coldwar +weapons_introduction_year_overrides: + R-3R - AAM, radar guided: 1980 + R-60 x 2: 1980 + R-60: 1980 \ No newline at end of file