Issue 3204 (#3458)

This issue addresses Issue #3204 by allowing factions to set a different
weapon introduction date.
This commit is contained in:
zhexu14 2024-11-09 23:43:01 +11:00 committed by GitHub
parent c29c61ade3
commit fa41b00ef0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 70 additions and 14 deletions

View File

@ -7,6 +7,7 @@ Saves from 11.x are not compatible with 12.0.0.
* **[Engine]** Support for DCS 2.9.9.2280. * **[Engine]** Support for DCS 2.9.9.2280.
* **[Campaign]** Flights are assigned different callsigns appropriate to the faction. * **[Campaign]** Flights are assigned different callsigns appropriate to the faction.
* **[Campaign]** Removed deprecated settings for generating persistent and invulnerable AWACs and tankers. * **[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)". * **[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. * **[Mods]** F/A-18 E/F/G Super Hornet mod version updated to 2.3.

View File

@ -10,6 +10,8 @@ from dcs.unittype import FlyingType
from game.data.weapons import Pylon, Weapon, WeaponType from game.data.weapons import Pylon, Weapon, WeaponType
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.factions.faction import Faction
from .flighttype import FlightType from .flighttype import FlightType
if TYPE_CHECKING: if TYPE_CHECKING:
@ -52,6 +54,7 @@ class Loadout:
weapon: Weapon, weapon: Weapon,
pylon: Pylon, pylon: Pylon,
date: datetime.date, date: datetime.date,
faction: Faction,
skip_types: Optional[Iterable[WeaponType]] = None, skip_types: Optional[Iterable[WeaponType]] = None,
) -> Optional[Weapon]: ) -> Optional[Weapon]:
if skip_types is None: if skip_types is None:
@ -59,14 +62,16 @@ class Loadout:
for fallback in weapon.fallbacks: for fallback in weapon.fallbacks:
if not pylon.can_equip(fallback): if not pylon.can_equip(fallback):
continue continue
if not fallback.available_on(date): if not fallback.available_on(date, faction):
continue continue
if fallback.weapon_group.type in skip_types: if fallback.weapon_group.type in skip_types:
continue continue
return fallback return fallback
return None 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: if self.date is not None and self.date <= date:
return Loadout(self.name, self.pylons, self.date, self.is_custom) return Loadout(self.name, self.pylons, self.date, self.is_custom)
@ -75,9 +80,9 @@ class Loadout:
if weapon is None: if weapon is None:
del new_pylons[pylon_number] del new_pylons[pylon_number]
continue continue
if not weapon.available_on(date): if not weapon.available_on(date, faction):
pylon = Pylon.for_aircraft(unit_type, pylon_number) 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: if fallback is None:
del new_pylons[pylon_number] del new_pylons[pylon_number]
else: else:
@ -89,11 +94,11 @@ class Loadout:
# If the loadout was chosen explicitly by the user, assume they know what # If the loadout was chosen explicitly by the user, assume they know what
# they're doing. They may be coordinating buddy-lase. # they're doing. They may be coordinating buddy-lase.
if not loadout.is_custom: 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 return loadout
def replace_lgbs_if_no_tgp( def replace_lgbs_if_no_tgp(
self, unit_type: AircraftType, date: datetime.date self, unit_type: AircraftType, date: datetime.date, faction: Faction
) -> None: ) -> None:
if self.has_weapon_of_type(WeaponType.TGP): if self.has_weapon_of_type(WeaponType.TGP):
return return
@ -106,7 +111,7 @@ class Loadout:
if weapon is not None and weapon.weapon_group.type is WeaponType.LGB: if weapon is not None and weapon.weapon_group.type is WeaponType.LGB:
pylon = Pylon.for_aircraft(unit_type, pylon_number) pylon = Pylon.for_aircraft(unit_type, pylon_number)
fallback = self._fallback_for( fallback = self._fallback_for(
weapon, pylon, date, skip_types={WeaponType.LGB} weapon, pylon, date, faction, skip_types={WeaponType.LGB}
) )
if fallback is None: if fallback is None:
del new_pylons[pylon_number] del new_pylons[pylon_number]

View File

@ -14,6 +14,8 @@ from dcs.flyingunit import FlyingUnit
from dcs.weapons_data import weapon_ids from dcs.weapons_data import weapon_ids
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.factions.faction import Faction
PydcsWeapon = Any PydcsWeapon = Any
PydcsWeaponAssignment = tuple[int, PydcsWeapon] PydcsWeaponAssignment = tuple[int, PydcsWeapon]
@ -77,8 +79,12 @@ class Weapon:
WeaponGroup.load_all() WeaponGroup.load_all()
cls._loaded = True 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 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: if introduction_year is None:
return True return True
return date >= datetime.date(introduction_year, 1, 1) return date >= datetime.date(introduction_year, 1, 1)
@ -243,9 +249,9 @@ class Pylon:
def make_pydcs_assignment(self, weapon: Weapon) -> PydcsWeaponAssignment: def make_pydcs_assignment(self, weapon: Weapon) -> PydcsWeaponAssignment:
return self.number, weapon.pydcs_data 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: for weapon in self.allowed:
if weapon.available_on(date): if weapon.available_on(date, faction):
yield weapon yield weapon
@classmethod @classmethod

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import itertools import itertools
import logging import logging
from dataclasses import dataclass, field from dataclasses import dataclass, field
import datetime
from functools import cached_property from functools import cached_property
from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Type from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Type
@ -118,6 +119,10 @@ class Faction:
#: both will use it. #: both will use it.
unrestricted_satnav: bool = False 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: def has_access_to_dcs_type(self, unit_type: Type[DcsUnitType]) -> bool:
# Vehicle and Ship Units # Vehicle and Ship Units
if any(unit_type == u.dcs_unit_type for u in self.accessible_units): if any(unit_type == u.dcs_unit_type for u in self.accessible_units):
@ -262,6 +267,10 @@ class Faction:
faction.unrestricted_satnav = json.get("unrestricted_satnav", False) faction.unrestricted_satnav = json.get("unrestricted_satnav", False)
faction.weapons_introduction_year_overrides = json.get(
"weapons_introduction_year_overrides", {}
)
return faction return faction
@property @property

View File

@ -245,7 +245,11 @@ class FlightGroupConfigurator:
loadout = member.loadout loadout = member.loadout
if self.game.settings.restrict_weapons_by_date: 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(): for pylon_number, weapon in loadout.pylons.items():
if weapon is None: if weapon is None:

View File

@ -26,7 +26,9 @@ class QPylonEditor(QComboBox):
self.addItem("None", None) self.addItem("None", None)
if self.game.settings.restrict_weapons_by_date: 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: else:
weapons = pylon.allowed weapons = pylon.allowed
allowed = sorted(weapons, key=operator.attrgetter("name")) allowed = sorted(weapons, key=operator.attrgetter("name"))
@ -68,7 +70,11 @@ class QPylonEditor(QComboBox):
def matching_weapon_name(self, loadout: Loadout) -> str: def matching_weapon_name(self, loadout: Loadout) -> str:
if self.game.settings.restrict_weapons_by_date: 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) weapon = self.weapon_from_loadout(loadout)
if weapon is None: if weapon is None:
return "None" return "None"

View File

@ -42,3 +42,7 @@ air_defense_units:
- ZSU-57-2 'Sparka' - ZSU-57-2 'Sparka'
has_jtac: false has_jtac: false
doctrine: coldwar doctrine: coldwar
weapons_introduction_year_overrides:
R-3R - AAM, radar guided: 1980
R-60 x 2: 1980
R-60: 1980

View File

@ -82,3 +82,8 @@ air_defense_units:
- ZSU-57-2 'Sparka' - ZSU-57-2 'Sparka'
has_jtac: true has_jtac: true
jtac_unit: MQ-9 Reaper jtac_unit: MQ-9 Reaper
weapons_introduction_year_overrides:
AIM-120B: 2050
2xAIM-120B: 2050
AIM-120C: 2050
2xAIM-120C: 2050

View File

@ -42,4 +42,8 @@ air_defense_units:
- ZU-23 on Ural-375 - ZU-23 on Ural-375
- ZSU-23-4 Shilka - ZSU-23-4 Shilka
has_jtac: "false" has_jtac: "false"
doctrine: "coldwar" doctrine: "coldwar"
weapons_introduction_year_overrides:
R-3R - AAM, radar guided: 1980
R-60 x 2: 1980
R-60: 1980

View File

@ -46,3 +46,7 @@ helicopter_carrier_names: []
requirements: {} requirements: {}
carrier_names: [] carrier_names: []
doctrine: coldwar doctrine: coldwar
weapons_introduction_year_overrides:
R-3R - AAM, radar guided: 1980
R-60 x 2: 1980
R-60: 1980

View File

@ -52,3 +52,7 @@ carrier_names: []
requirements: requirements:
WW2 Asset Pack: https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/ WW2 Asset Pack: https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/
doctrine: coldwar doctrine: coldwar
weapons_introduction_year_overrides:
R-3R - AAM, radar guided: 1980
R-60 x 2: 1980
R-60: 1980

View File

@ -51,3 +51,7 @@ helicopter_carrier_names: []
requirements: {} requirements: {}
carrier_names: [] carrier_names: []
doctrine: coldwar doctrine: coldwar
weapons_introduction_year_overrides:
R-3R - AAM, radar guided: 1980
R-60 x 2: 1980
R-60: 1980