mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
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.
This commit is contained in:
parent
11c2d4ab25
commit
8e977f994f
@ -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.
|
||||
|
||||
@ -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("<CLEAN>", 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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
name: 2xGBU-10
|
||||
type: LGB
|
||||
year: 1976
|
||||
fallback: 2xMk 84
|
||||
clsids:
|
||||
- "{62BE78B1-9258-48AE-B882-279534C0D278}"
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
name: GBU-10
|
||||
type: LGB
|
||||
year: 1976
|
||||
fallback: Mk 84
|
||||
clsids:
|
||||
- "DIS_GBU_10"
|
||||
- "{BRU-32 GBU-10}"
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
name: 2xGBU-12
|
||||
type: LGB
|
||||
year: 1976
|
||||
fallback: 2xMk 82
|
||||
clsids:
|
||||
- "{M2KC_RAFAUT_GBU12}"
|
||||
- "{BRU33_2X_GBU-12}"
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
name: GBU-12
|
||||
type: LGB
|
||||
year: 1976
|
||||
fallback: Mk 82
|
||||
clsids:
|
||||
- "DIS_GBU_12"
|
||||
- "{BRU-32 GBU-12}"
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
name: GBU-16
|
||||
type: LGB
|
||||
year: 1976
|
||||
fallback: Mk 83
|
||||
clsids:
|
||||
- "DIS_GBU_16"
|
||||
- "{BRU-32 GBU-16}"
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
name: GBU-24
|
||||
type: LGB
|
||||
year: 1986
|
||||
fallback: GBU-10
|
||||
clsids:
|
||||
|
||||
18
resources/weapons/bombs/Mk-82-2X.yaml
Normal file
18
resources/weapons/bombs/Mk-82-2X.yaml
Normal file
@ -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}"
|
||||
11
resources/weapons/bombs/Mk-82.yaml
Normal file
11
resources/weapons/bombs/Mk-82.yaml
Normal file
@ -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}"
|
||||
7
resources/weapons/bombs/Mk-83-2X.yaml
Normal file
7
resources/weapons/bombs/Mk-83-2X.yaml
Normal file
@ -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}"
|
||||
16
resources/weapons/bombs/Mk-83.yaml
Normal file
16
resources/weapons/bombs/Mk-83.yaml
Normal file
@ -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}"
|
||||
@ -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.
|
||||
|
||||
7
resources/weapons/pods/lantirn.yaml
Normal file
7
resources/weapons/pods/lantirn.yaml
Normal file
@ -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}"
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user