mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Add predefined squadron support.
https://github.com/dcs-liberation/dcs_liberation/issues/276
This commit is contained in:
parent
9091afe682
commit
ac4a7441e9
@ -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]
|
||||||
|
|||||||
@ -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}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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]:
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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 = []
|
||||||
|
|||||||
21
resources/squadrons/hornet/VFA-113.yaml
Normal file
21
resources/squadrons/hornet/VFA-113.yaml
Normal 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
|
||||||
Loading…
x
Reference in New Issue
Block a user