Add a CLI tool for viewing default loadouts.

This commit is contained in:
Dan Albert 2022-05-29 15:23:21 -07:00
parent c5efc908de
commit 22c3d4ebc5
6 changed files with 165 additions and 18 deletions

View File

@ -2,10 +2,13 @@ from __future__ import annotations
import datetime import datetime
from collections.abc import Iterable from collections.abc import Iterable
from typing import Iterator, Mapping, Optional, TYPE_CHECKING from typing import Iterator, Mapping, Optional, TYPE_CHECKING, Type
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 .flighttype import FlightType
if TYPE_CHECKING: if TYPE_CHECKING:
from .flight import Flight from .flight import Flight
@ -103,6 +106,10 @@ class Loadout:
@classmethod @classmethod
def iter_for(cls, flight: Flight) -> Iterator[Loadout]: def iter_for(cls, flight: Flight) -> Iterator[Loadout]:
return cls.iter_for_aircraft(flight.unit_type)
@classmethod
def iter_for_aircraft(cls, aircraft: AircraftType) -> Iterator[Loadout]:
# Dict of payload ID (numeric) to: # Dict of payload ID (numeric) to:
# #
# { # {
@ -111,7 +118,7 @@ class Loadout:
# {"CLSID": class ID, "num": pylon number} # {"CLSID": class ID, "num": pylon number}
# "tasks": List (as a dict) of task IDs the payload is used by. # "tasks": List (as a dict) of task IDs the payload is used by.
# } # }
payloads = flight.unit_type.dcs_unit_type.load_payloads() payloads = aircraft.dcs_unit_type.load_payloads()
for payload in payloads.values(): for payload in payloads.values():
name = payload["name"] name = payload["name"]
pylons = payload["pylons"] pylons = payload["pylons"]
@ -122,9 +129,7 @@ class Loadout:
) )
@classmethod @classmethod
def default_loadout_names_for(cls, flight: Flight) -> Iterator[str]: def default_loadout_names_for(cls, task: FlightType) -> Iterator[str]:
from game.ato.flighttype import FlightType
# This is a list of mappings from the FlightType of a Flight to the type of # 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 # 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 # concept of a PyDCS task, so COMMON_OVERRIDE cannot be used here. This is used
@ -164,17 +169,25 @@ class Loadout:
loadout_names[FlightType.DEAD].extend(loadout_names[FlightType.BAI]) loadout_names[FlightType.DEAD].extend(loadout_names[FlightType.BAI])
# OCA/Runway falls back to Strike # OCA/Runway falls back to Strike
loadout_names[FlightType.OCA_RUNWAY].extend(loadout_names[FlightType.STRIKE]) loadout_names[FlightType.OCA_RUNWAY].extend(loadout_names[FlightType.STRIKE])
yield from loadout_names[flight.flight_type] yield from loadout_names[task]
@classmethod @classmethod
def default_for(cls, flight: Flight) -> Loadout: def default_for(cls, flight: Flight) -> Loadout:
return cls.default_for_task_and_aircraft(
flight.flight_type, flight.unit_type.dcs_unit_type
)
@classmethod
def default_for_task_and_aircraft(
cls, task: FlightType, dcs_unit_type: Type[FlyingType]
) -> Loadout:
# Iterate through each possible payload type for a given aircraft. # Iterate through each possible payload type for a given aircraft.
# Some aircraft have custom loadouts that in aren't the standard set. # Some aircraft have custom loadouts that in aren't the standard set.
for name in cls.default_loadout_names_for(flight): for name in cls.default_loadout_names_for(task):
# This operation is cached, but must be called before load_by_name will # This operation is cached, but must be called before load_by_name will
# work. # work.
flight.unit_type.dcs_unit_type.load_payloads() dcs_unit_type.load_payloads()
payload = flight.unit_type.dcs_unit_type.loadout_by_name(name) payload = dcs_unit_type.loadout_by_name(name)
if payload is not None: if payload is not None:
return Loadout( return Loadout(
name, name,

View File

@ -4,7 +4,7 @@ import logging
from dataclasses import dataclass from dataclasses import dataclass
from functools import cached_property from functools import cached_property
from pathlib import Path from pathlib import Path
from typing import Any, Iterator, Optional, TYPE_CHECKING, Type, Dict from typing import Any, Dict, Iterator, Optional, TYPE_CHECKING, Type
import yaml import yaml
from dcs.helicopters import helicopter_map from dcs.helicopters import helicopter_map
@ -315,7 +315,7 @@ class AircraftType(UnitType[Type[FlyingType]]):
yield unit yield unit
@staticmethod @staticmethod
def _each_unit_type() -> Iterator[Type[FlyingType]]: def each_dcs_type() -> Iterator[Type[FlyingType]]:
yield from helicopter_map.values() yield from helicopter_map.values()
yield from plane_map.values() yield from plane_map.values()

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Any, Optional, Type, Iterator from typing import Any, Iterator, Optional, Type
import yaml import yaml
from dcs.unittype import VehicleType from dcs.unittype import VehicleType
@ -76,7 +76,7 @@ class GroundUnitType(UnitType[Type[VehicleType]]):
yield unit yield unit
@staticmethod @staticmethod
def _each_unit_type() -> Iterator[Type[VehicleType]]: def each_dcs_type() -> Iterator[Type[VehicleType]]:
yield from vehicle_map.values() yield from vehicle_map.values()
@classmethod @classmethod

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Type, Iterator from typing import Iterator, Type
import yaml import yaml
from dcs.ships import ship_map from dcs.ships import ship_map
@ -32,7 +32,7 @@ class ShipUnitType(UnitType[Type[ShipType]]):
yield unit yield unit
@staticmethod @staticmethod
def _each_unit_type() -> Iterator[Type[ShipType]]: def each_dcs_type() -> Iterator[Type[ShipType]]:
yield from ship_map.values() yield from ship_map.values()
@classmethod @classmethod

View File

@ -4,7 +4,7 @@ from abc import ABC
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from functools import cached_property from functools import cached_property
from typing import TypeVar, Generic, Type, ClassVar, Any, Iterator from typing import Any, ClassVar, Generic, Iterator, Type, TypeVar
from dcs.unittype import UnitType as DcsUnitType from dcs.unittype import UnitType as DcsUnitType
@ -52,7 +52,7 @@ class UnitType(ABC, Generic[DcsUnitTypeT]):
raise NotImplementedError raise NotImplementedError
@staticmethod @staticmethod
def _each_unit_type() -> Iterator[DcsUnitTypeT]: def each_dcs_type() -> Iterator[DcsUnitTypeT]:
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
@ -61,7 +61,7 @@ class UnitType(ABC, Generic[DcsUnitTypeT]):
@classmethod @classmethod
def _load_all(cls) -> None: def _load_all(cls) -> None:
for unit_type in cls._each_unit_type(): for unit_type in cls.each_dcs_type():
for data in cls._each_variant_of(unit_type): for data in cls._each_variant_of(unit_type):
cls.register(data) cls.register(data)
cls._loaded = True cls._loaded = True

View File

@ -0,0 +1,134 @@
"""Command-line utility for displaying human readable loadout configurations."""
import argparse
import sys
from collections.abc import Iterator
from pathlib import Path
from typing import Type
from dcs.helicopters import helicopter_map
from dcs.planes import plane_map
from dcs.unittype import FlyingType
from game import persistency
from game.ato import FlightType
from game.ato.loadouts import Loadout
from game.dcs.aircrafttype import AircraftType
# TODO: Move this logic out of the UI.
from qt_ui import liberation_install
from qt_ui.main import inject_custom_payloads
def non_empty_loadouts_for(
aircraft: Type[FlyingType],
) -> Iterator[tuple[FlightType, Loadout]]:
for task in FlightType:
try:
loadout = Loadout.default_for_task_and_aircraft(task, aircraft)
except KeyError:
# Not all aircraft have a unitPayloads field. This should maybe be handled
# in pydcs, but I'm not sure about the cause. For now, just ignore the field
# since we can be less robust in optional tooling.
continue
if loadout.name != "Empty":
yield task, loadout
def print_pylons(loadout: Loadout, prefix: str = "\t") -> None:
pylons = dict(sorted(loadout.pylons.items()))
for pylon_id, weapon in pylons.items():
if weapon is not None:
print(f"{prefix}{pylon_id}: {weapon.name}")
def show_all_loadouts(aircraft: Type[FlyingType]) -> None:
loadouts = list(non_empty_loadouts_for(aircraft))
if not loadouts:
return
print(f"Loadouts for {aircraft.id}:")
for task, loadout in loadouts:
print(f"\t{task.value}: {loadout.name}")
print_pylons(loadout, prefix="\t\t")
def task_by_name(name: str) -> FlightType:
for task in FlightType:
if task.value == name:
return task
raise KeyError(f"No FlightType named {name}")
def show_single_loadout(aircraft: Type[FlyingType], task_name: str) -> None:
task = task_by_name(task_name)
try:
loadout = Loadout.default_for_task_and_aircraft(task, aircraft)
except KeyError:
# Not all aircraft have a unitPayloads field. This should maybe be handled
# in pydcs, but I'm not sure about the cause. For now, just ignore the field
# since we can be less robust in optional tooling.
return
if loadout.pylons:
print(f"{aircraft.id} {loadout.name}:")
print_pylons(loadout)
def show_loadouts_for(aircraft: Type[FlyingType], task_name: str | None) -> None:
if task_name is None:
show_all_loadouts(aircraft)
else:
show_single_loadout(aircraft, task_name)
def show_all_aircraft(task_name: str | None) -> None:
for aircraft in AircraftType.each_dcs_type():
show_loadouts_for(aircraft, task_name)
def show_single_aircraft(aircraft_id: str, task_name: str | None) -> None:
try:
aircraft: Type[FlyingType] = plane_map[aircraft_id]
except KeyError:
aircraft = helicopter_map[aircraft_id]
show_loadouts_for(aircraft, task_name)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--aircraft-id",
help=(
"ID of the aircraft to display loadouts for. By default all aircraft will "
+ "be displayed."
),
)
parser.add_argument(
"--task",
help=(
"Name of the mission type to display. By default loadouts for all mission "
+ "types will be displayed."
),
)
args = parser.parse_args()
first_start = liberation_install.init()
if first_start:
sys.exit(
"Cannot view payloads without configuring DCS Liberation. Start the UI for "
"the first run configuration."
)
inject_custom_payloads(Path(persistency.base_path()))
if args.aircraft_id is None:
show_all_aircraft(args.task)
else:
show_single_aircraft(args.aircraft_id, args.task)
if __name__ == "__main__":
main()