Dan Albert 0e3bc1ce43 Loadout implementation cleanup.
Loadout selection no longer has two (disagreeing) implementations. What
the UI shows is now what the miz will have.

We now store the chosen layout in the Flight *always*, not just for
custom loadouts. This means that we do loadout lookups at the start of
each turn, but the data is cached in pydcs.

Era-specific loadout degradation is still done at generation (and
presentation) time. This is so that players can toggle that option and
have it affect the *current* turn, rather than the next one.
2021-05-17 01:23:00 -07:00

122 lines
4.8 KiB
Python

from __future__ import annotations
import datetime
from typing import Optional, List, Iterator, Type, TYPE_CHECKING, Mapping
from dcs.unittype import FlyingType
from game.data.weapons import Weapon, Pylon
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: Type[FlyingType], 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 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
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.load_payloads()
for payload in payloads["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.
yield from {
FlightType.TARCAP: ("CAP HEAVY", "CAP"),
FlightType.BARCAP: ("CAP HEAVY", "CAP"),
FlightType.CAS: ("CAS MAVERICK F", "CAS"),
FlightType.INTERCEPTION: ("CAP HEAVY", "CAP"),
FlightType.STRIKE: ("STRIKE",),
FlightType.ANTISHIP: ("ANTISHIP",),
FlightType.SEAD: ("SEAD",),
FlightType.DEAD: ("SEAD",),
FlightType.ESCORT: ("CAP HEAVY", "CAP"),
FlightType.BAI: ("BAI", "CAS MAVERICK F", "CAS"),
FlightType.SWEEP: ("CAP HEAVY", "CAP"),
FlightType.OCA_RUNWAY: ("RUNWAY_ATTACK", "RUNWAY_STRIKE", "STRIKE"),
FlightType.OCA_AIRCRAFT: ("OCA", "CAS MAVERICK F", "CAS"),
FlightType.TRANSPORT: (),
FlightType.AEWC: (),
}.get(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.load_payloads()
payload = flight.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)