Add predefined squadron support.

https://github.com/dcs-liberation/dcs_liberation/issues/276
This commit is contained in:
Dan Albert 2021-05-26 23:35:05 -07:00
parent 9091afe682
commit ac4a7441e9
6 changed files with 100 additions and 15 deletions

View File

@ -1,7 +1,9 @@
from __future__ import annotations from __future__ import annotations
import itertools import itertools
import logging
import random import random
from collections import defaultdict
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import Type, Tuple, List, TYPE_CHECKING, Optional, Iterable, Iterator from typing import Type, Tuple, List, TYPE_CHECKING, Optional, Iterable, Iterator
@ -54,6 +56,9 @@ class Squadron:
def __post_init__(self) -> None: def __post_init__(self) -> None:
self.available_pilots = list(self.active_pilots) self.available_pilots = list(self.active_pilots)
def __str__(self) -> str:
return f'{self.name} "{self.nickname}"'
def claim_available_pilot(self) -> Optional[Pilot]: def claim_available_pilot(self) -> Optional[Pilot]:
if not self.available_pilots: if not self.available_pilots:
self.enlist_new_pilots(1) self.enlist_new_pilots(1)
@ -100,7 +105,7 @@ class Squadron:
from gen.flights.flight import FlightType from gen.flights.flight import FlightType
with path.open() as squadron_file: with path.open() as squadron_file:
data = yaml.load(squadron_file) data = yaml.safe_load(squadron_file)
unit_type = flying_type_from_name(data["aircraft"]) unit_type = flying_type_from_name(data["aircraft"])
if unit_type is None: if unit_type is None:
@ -109,7 +114,7 @@ class Squadron:
return Squadron( return Squadron(
name=data["name"], name=data["name"],
nickname=data["nickname"], nickname=data["nickname"],
country=game.country_for(player), country=data["country"],
role=data["role"], role=data["role"],
aircraft=unit_type, aircraft=unit_type,
mission_types=tuple(FlightType.from_name(n) for n in data["mission_types"]), mission_types=tuple(FlightType.from_name(n) for n in data["mission_types"]),
@ -119,19 +124,78 @@ class Squadron:
) )
class SquadronLoader:
def __init__(self, game: Game, player: bool) -> None:
self.game = game
self.player = player
@staticmethod
def squadron_directories() -> Iterator[Path]:
from game import persistency
yield Path(persistency.base_path()) / "Liberation/Squadrons"
yield Path("resources/squadrons")
def load(self) -> dict[Type[FlyingType], list[Squadron]]:
squadrons: dict[Type[FlyingType], list[Squadron]] = defaultdict(list)
country = self.game.country_for(self.player)
faction = self.game.faction_for(self.player)
any_country = country.startswith("Combined Joint Task Forces ")
for directory in self.squadron_directories():
for path, squadron in self.load_squadrons_from(directory):
if not any_country and squadron.country != country:
logging.debug(
"Not using squadron for non-matching country (is "
f"{squadron.country}, need {country}: {path}"
)
continue
if squadron.aircraft not in faction.aircrafts:
logging.debug(
f"Not using squadron because {faction.name} cannot use "
f"{squadron.aircraft}: {path}"
)
continue
logging.debug(
f"Found {squadron.name} {squadron.aircraft} {squadron.role} "
f"compatible with {faction.name}"
)
squadrons[squadron.aircraft].append(squadron)
# Convert away from defaultdict because defaultdict doesn't unpickle so we don't
# want it in the save state.
return dict(squadrons)
def load_squadrons_from(self, directory: Path) -> Iterator[Tuple[Path, Squadron]]:
logging.debug(f"Looking for factions in {directory}")
# First directory level is the aircraft type so that historical squadrons that
# have flown multiple airframes can be defined as many times as needed. The main
# load() method is responsible for filtering out squadrons that aren't
# compatible with the faction.
for squadron_path in directory.glob("*/*.yaml"):
try:
yield squadron_path, Squadron.from_yaml(
squadron_path, self.game, self.player
)
except Exception as ex:
raise RuntimeError(
f"Failed to load squadron defined by {squadron_path}"
) from ex
class AirWing: class AirWing:
def __init__(self, game: Game, player: bool) -> None: def __init__(self, game: Game, player: bool) -> None:
from gen.flights.flight import FlightType from gen.flights.flight import FlightType
self.game = game self.game = game
self.player = player self.player = player
self.squadrons: dict[Type[FlyingType], list[Squadron]] = { self.squadrons = SquadronLoader(game, player).load()
aircraft: [] for aircraft in game.faction_for(player).aircrafts
} count = itertools.count(1)
for num, (aircraft, squadrons) in enumerate(self.squadrons.items()): for aircraft in game.faction_for(player).aircrafts:
squadrons.append( if aircraft in self.squadrons:
continue
self.squadrons[aircraft] = [
Squadron( Squadron(
name=f"Squadron {num + 1:03}", name=f"Squadron {next(count):03}",
nickname=self.random_nickname(), nickname=self.random_nickname(),
country=game.country_for(player), country=game.country_for(player),
role="Flying Squadron", role="Flying Squadron",
@ -141,7 +205,7 @@ class AirWing:
game=game, game=game,
player=player, player=player,
) )
) ]
def squadron_for(self, aircraft: Type[FlyingType]) -> Squadron: def squadron_for(self, aircraft: Type[FlyingType]) -> Squadron:
return self.squadrons[aircraft][0] return self.squadrons[aircraft][0]

View File

@ -74,9 +74,9 @@ class FlightType(Enum):
@classmethod @classmethod
def from_name(cls, name: str) -> FlightType: def from_name(cls, name: str) -> FlightType:
for value in cls: for entry in cls:
if name == value: if name == entry.value:
return value return entry
raise KeyError(f"No FlightType with name {name}") raise KeyError(f"No FlightType with name {name}")

View File

@ -397,7 +397,7 @@ class AirWingModel(QAbstractListModel):
@staticmethod @staticmethod
def text_for_squadron(squadron: Squadron) -> str: def text_for_squadron(squadron: Squadron) -> str:
"""Returns the text that should be displayed for the squadron.""" """Returns the text that should be displayed for the squadron."""
return squadron.name return str(squadron)
@staticmethod @staticmethod
def icon_for_squadron(squadron: Squadron) -> Optional[QIcon]: def icon_for_squadron(squadron: Squadron) -> Optional[QIcon]:

View File

@ -68,7 +68,7 @@ class SquadronDialog(QDialog):
self.squadron_model = squadron_model self.squadron_model = squadron_model
self.setMinimumSize(1000, 440) self.setMinimumSize(1000, 440)
self.setWindowTitle(squadron_model.squadron.name) self.setWindowTitle(str(squadron_model.squadron))
# TODO: self.setWindowIcon() # TODO: self.setWindowIcon()
layout = QVBoxLayout() layout = QVBoxLayout()

View File

@ -102,7 +102,7 @@ class QFlightSlotEditor(QGroupBox):
layout.addWidget(self.aircraft_count_spinner, 0, 1) layout.addWidget(self.aircraft_count_spinner, 0, 1)
layout.addWidget(QLabel("Squadron:"), 1, 0) layout.addWidget(QLabel("Squadron:"), 1, 0)
layout.addWidget(QLabel(self.flight.squadron.name), 1, 1) layout.addWidget(QLabel(str(self.flight.squadron)), 1, 1)
layout.addWidget(QLabel("Assigned pilots:"), 2, 0) layout.addWidget(QLabel("Assigned pilots:"), 2, 0)
self.pilot_selectors = [] self.pilot_selectors = []

View File

@ -0,0 +1,21 @@
---
name: VFA-113
nickname: Stingers
country: USA
role: Strike Fighter
aircraft: FA-18C_hornet
mission_types:
- Anti-ship
- BAI
- BARCAP
- CAS
- DEAD
- Escort
- Intercept
- OCA/Aircraft
- OCA/Runway
- SEAD
- SEAD Escort
- Strike
- Fighter sweep
- TARCAP