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.
This commit is contained in:
Dan Albert
2021-05-17 01:01:11 -07:00
parent 6ca175345f
commit 0e3bc1ce43
7 changed files with 190 additions and 286 deletions

View File

@@ -2,19 +2,19 @@ from __future__ import annotations
from datetime import timedelta
from enum import Enum
from typing import Dict, List, Optional, TYPE_CHECKING, Type
from typing import List, Optional, TYPE_CHECKING, Type
from dcs.mapping import Point
from dcs.point import MovingPoint, PointAction
from dcs.unittype import FlyingType
from game import db
from game.data.weapons import Weapon
from game.theater.controlpoint import ControlPoint, MissionTarget
from game.utils import Distance, meters
from gen.flights.loadouts import Loadout
if TYPE_CHECKING:
from game.transfers import Airlift, TransferOrder
from game.transfers import TransferOrder
from gen.ato import Package
from gen.flights.flightplan import FlightPlan
@@ -179,7 +179,7 @@ class Flight:
self.flight_type = flight_type
# TODO: Replace with FlightPlan.
self.targets: List[MissionTarget] = []
self.loadout: Dict[int, Optional[Weapon]] = {}
self.loadout = Loadout.default_for(self)
self.start_type = start_type
self.use_custom_loadout = False
self.client_count = 0

121
gen/flights/loadouts.py Normal file
View File

@@ -0,0 +1,121 @@
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)