mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
This is an attempt to remove a lot of our supposedly unnecessary error handling. Every aircraft should have a price, a description, a name, etc; and none of those should require carrying around the faction's country as context. This moves all the data for aircraft into yaml files (only one converted here as an example). Most of the "extended unit info" isn't actually being read yet. To replace the renaming of units based on the county, we instead generate multiple types of each unit when necessary. The CF-18 is just as much a first-class type as the F/A-18 is. This doesn't work in its current state because it does break all the existing names for aircraft that are used in the faction and squadron files, and we no longer let those errors go as a warning. It will be an annoying one time switch, but it allows us to define the names that get used in these files instead of being sensitive to changes as they happen in pydcs, and allows faction designers to specifically choose, for example, the Su-22 instead of the Su-17. One thing not handled by this is aircraft task capability. This is because the lists in ai_flight_planner_db.py are a priority list, and to move it out to a yaml file we'd need to assign a weight to it that would be used to stack rank each aircraft. That's doable, but it makes it much more difficult to see the ordering of aircraft at a glance, and much more annoying to move aircraft around in the priority list. I don't think this is worth doing, and the priority lists will remain in their own separate lists. This includes the converted I used to convert all the old unit info and factions to the new format. This doesn't need to live long, but we may want to reuse it in the future so we want it in the version history.
137 lines
6.0 KiB
Python
137 lines
6.0 KiB
Python
from __future__ import annotations
|
|
|
|
import datetime
|
|
from typing import Optional, List, Iterator, TYPE_CHECKING, Mapping
|
|
|
|
from game.data.weapons import Weapon, Pylon
|
|
from game.dcs.aircrafttype import AircraftType
|
|
|
|
if TYPE_CHECKING:
|
|
from gen.flights.flight import Flight
|
|
|
|
|
|
class Loadout:
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
pylons: Mapping[int, Optional[Weapon]],
|
|
date: Optional[datetime.date],
|
|
is_custom: bool = False,
|
|
) -> None:
|
|
self.name = name
|
|
self.pylons = {k: v for k, v in pylons.items() if v is not None}
|
|
self.date = date
|
|
self.is_custom = is_custom
|
|
|
|
def derive_custom(self, name: str) -> Loadout:
|
|
return Loadout(name, self.pylons, self.date, is_custom=True)
|
|
|
|
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)
|
|
|
|
new_pylons = dict(self.pylons)
|
|
for pylon_number, weapon in self.pylons.items():
|
|
if weapon is None:
|
|
del new_pylons[pylon_number]
|
|
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:
|
|
del new_pylons[pylon_number]
|
|
return Loadout(f"{self.name} ({date.year})", new_pylons, date)
|
|
|
|
@classmethod
|
|
def iter_for(cls, flight: Flight) -> Iterator[Loadout]:
|
|
# Dict of payload ID (numeric) to:
|
|
#
|
|
# {
|
|
# "name": The name the user set in the ME
|
|
# "pylons": List (as a dict) of dicts of:
|
|
# {"CLSID": class ID, "num": pylon number}
|
|
# "tasks": List (as a dict) of task IDs the payload is used by.
|
|
# }
|
|
payloads = flight.unit_type.dcs_unit_type.load_payloads()
|
|
for payload in payloads.values():
|
|
name = payload["name"]
|
|
pylons = payload["pylons"]
|
|
yield Loadout(
|
|
name,
|
|
{p["num"]: Weapon.from_clsid(p["CLSID"]) for p in pylons.values()},
|
|
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
|
|
|
|
# This is a list of mappings from the FlightType of a Flight to the type of
|
|
# payload defined in the resources/payloads/UNIT_TYPE.lua file. A Flight has no
|
|
# concept of a PyDCS task, so COMMON_OVERRIDE cannot be used here. This is used
|
|
# in the payload editor, for setting the default loadout of an object. The left
|
|
# element is the FlightType name, and the right element is a tuple containing
|
|
# what is used in the lua file. Some aircraft differ from the standard loadout
|
|
# names, so those have been included here too. The priority goes from first to
|
|
# last - the first element in the tuple will be tried first, then the second,
|
|
# etc.
|
|
loadout_names = {t: [f"Liberation {t.value}"] for t in FlightType}
|
|
legacy_names = {
|
|
FlightType.TARCAP: ("CAP HEAVY", "CAP", "Liberation BARCAP"),
|
|
FlightType.BARCAP: ("CAP HEAVY", "CAP", "Liberation TARCAP"),
|
|
FlightType.CAS: ("CAS MAVERICK F", "CAS"),
|
|
FlightType.STRIKE: ("STRIKE",),
|
|
FlightType.ANTISHIP: ("ANTISHIP",),
|
|
FlightType.SEAD: ("SEAD",),
|
|
FlightType.BAI: ("BAI",),
|
|
FlightType.OCA_RUNWAY: ("RUNWAY_ATTACK", "RUNWAY_STRIKE"),
|
|
FlightType.OCA_AIRCRAFT: ("OCA",),
|
|
}
|
|
for flight_type, names in legacy_names.items():
|
|
loadout_names[flight_type].extend(names)
|
|
# A SEAD escort typically does not need a different loadout than a regular
|
|
# SEAD flight, so fall back to SEAD if needed.
|
|
loadout_names[FlightType.SEAD_ESCORT].extend(loadout_names[FlightType.SEAD])
|
|
# Sweep and escort can fall back to TARCAP.
|
|
loadout_names[FlightType.ESCORT].extend(loadout_names[FlightType.TARCAP])
|
|
loadout_names[FlightType.SWEEP].extend(loadout_names[FlightType.TARCAP])
|
|
# Intercept can fall back to BARCAP.
|
|
loadout_names[FlightType.INTERCEPTION].extend(loadout_names[FlightType.BARCAP])
|
|
# OCA/Aircraft falls back to BAI, which falls back to CAS.
|
|
loadout_names[FlightType.BAI].extend(loadout_names[FlightType.CAS])
|
|
loadout_names[FlightType.OCA_AIRCRAFT].extend(loadout_names[FlightType.BAI])
|
|
# DEAD also falls back to BAI.
|
|
loadout_names[FlightType.DEAD].extend(loadout_names[FlightType.BAI])
|
|
# OCA/Runway falls back to Strike
|
|
loadout_names[FlightType.OCA_RUNWAY].extend(loadout_names[FlightType.STRIKE])
|
|
yield from loadout_names[flight.flight_type]
|
|
|
|
@classmethod
|
|
def default_for(cls, flight: Flight) -> Loadout:
|
|
# Iterate through each possible payload type for a given aircraft.
|
|
# Some aircraft have custom loadouts that in aren't the standard set.
|
|
for name in cls.default_loadout_names_for(flight):
|
|
# This operation is cached, but must be called before load_by_name will
|
|
# work.
|
|
flight.unit_type.dcs_unit_type.load_payloads()
|
|
payload = flight.unit_type.dcs_unit_type.loadout_by_name(name)
|
|
if payload is not None:
|
|
return Loadout(
|
|
name,
|
|
{i: Weapon.from_clsid(d["clsid"]) for i, d in payload},
|
|
date=None,
|
|
)
|
|
|
|
# TODO: Try group.load_task_default_loadout(loadout_for_task)
|
|
return Loadout("Empty", {}, date=None)
|