mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
183 lines
5.9 KiB
Python
183 lines
5.9 KiB
Python
"""Extra airfield data that is not exposed by pydcs.
|
|
|
|
Remove once https://github.com/pydcs/dcs/issues/69 tracks getting the missing
|
|
data added to pydcs. Until then, missing data can be manually filled in here.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from collections.abc import Iterator
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import ClassVar, Dict, Optional, TYPE_CHECKING, Tuple
|
|
|
|
import yaml
|
|
from dcs.task import Modulation
|
|
from dcs.terrain import Airport
|
|
|
|
from game.radio.radios import RadioFrequency
|
|
from game.radio.tacan import TacanChannel
|
|
|
|
if TYPE_CHECKING:
|
|
from game.theater import ConflictTheater
|
|
|
|
|
|
@dataclass
|
|
class AtcData:
|
|
hf: RadioFrequency
|
|
vhf_fm: RadioFrequency
|
|
vhf_am: RadioFrequency
|
|
uhf: RadioFrequency
|
|
|
|
@classmethod
|
|
def from_pydcs(cls, airport: Airport) -> Optional[AtcData]:
|
|
if airport.atc_radio is None:
|
|
return None
|
|
return AtcData(
|
|
RadioFrequency(airport.atc_radio.hf_hz, Modulation.FM),
|
|
RadioFrequency(airport.atc_radio.vhf_low_hz, Modulation.FM),
|
|
RadioFrequency(airport.atc_radio.vhf_high_hz, Modulation.AM),
|
|
RadioFrequency(airport.atc_radio.uhf_hz, Modulation.AM),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class AirfieldData:
|
|
"""Additional airfield data not included in pydcs."""
|
|
|
|
#: Airfield name for the UI. Not stable.
|
|
name: str
|
|
|
|
#: pydcs airport ID
|
|
id: int
|
|
|
|
#: ICAO airport code
|
|
icao: Optional[str] = None
|
|
|
|
#: Elevation (in ft).
|
|
elevation: int = 0
|
|
|
|
#: Runway length (in ft).
|
|
runway_length: int = 0
|
|
|
|
#: TACAN channel for the airfield.
|
|
tacan: Optional[TacanChannel] = None
|
|
|
|
#: TACAN callsign
|
|
tacan_callsign: Optional[str] = None
|
|
|
|
#: VOR as a tuple of (callsign, frequency).
|
|
vor: Optional[Tuple[str, RadioFrequency]] = None
|
|
|
|
#: RSBN channel as a tuple of (callsign, channel).
|
|
rsbn: Optional[Tuple[str, int]] = None
|
|
|
|
#: Dict of runway heading -> ILS tuple of (callsign, frequency).
|
|
ils: Dict[str, Tuple[str, RadioFrequency]] = field(default_factory=dict)
|
|
|
|
#: Dict of runway heading -> PRMG tuple of (callsign, channel).
|
|
prmg: Dict[str, Tuple[str, int]] = field(default_factory=dict)
|
|
|
|
#: Dict of runway heading -> outer NDB tuple of (callsign, frequency).
|
|
outer_ndb: Dict[str, Tuple[str, RadioFrequency]] = field(default_factory=dict)
|
|
|
|
#: Dict of runway heading -> inner NDB tuple of (callsign, frequency).
|
|
inner_ndb: Dict[str, Tuple[str, RadioFrequency]] = field(default_factory=dict)
|
|
|
|
_airfields: ClassVar[dict[str, dict[int, AirfieldData]]] = {}
|
|
|
|
def ils_freq(self, runway: str) -> Optional[RadioFrequency]:
|
|
ils = self.ils.get(runway)
|
|
if ils is not None:
|
|
return ils[1]
|
|
return None
|
|
|
|
@classmethod
|
|
def from_file(cls, airfield_yaml: Path) -> AirfieldData:
|
|
with airfield_yaml.open() as yaml_file:
|
|
data = yaml.safe_load(yaml_file)
|
|
|
|
tacan_channel = None
|
|
tacan_callsign = None
|
|
if (tacan := data.get("tacan")) is not None:
|
|
tacan_channel = TacanChannel.parse(tacan["channel"])
|
|
tacan_callsign = tacan["callsign"]
|
|
|
|
vor = None
|
|
if (vor_data := data.get("vor")) is not None:
|
|
vor = (
|
|
vor_data["callsign"],
|
|
RadioFrequency.parse(vor_data["frequency"], Modulation.FM),
|
|
)
|
|
|
|
rsbn = None
|
|
if (rsbn_data := data.get("rsbn")) is not None:
|
|
rsbn = (rsbn_data["callsign"], rsbn_data["channel"])
|
|
|
|
ils = {}
|
|
prmg = {}
|
|
outer_ndb = {}
|
|
inner_ndb = {}
|
|
for name, runway_data in data.get("runways", {}).items():
|
|
if (ils_data := runway_data.get("ils")) is not None:
|
|
ils[name] = (
|
|
ils_data["callsign"],
|
|
RadioFrequency.parse(ils_data["frequency"], Modulation.FM),
|
|
)
|
|
|
|
if (prmg_data := runway_data.get("prmg")) is not None:
|
|
prmg[name] = (prmg_data["callsign"], prmg_data["channel"])
|
|
|
|
if (outer_ndb_data := runway_data.get("outer_ndb")) is not None:
|
|
outer_ndb[name] = (
|
|
outer_ndb_data["callsign"],
|
|
RadioFrequency.parse(outer_ndb_data["frequency"], Modulation.AM),
|
|
)
|
|
|
|
if (inner_ndb_data := runway_data.get("inner_ndb")) is not None:
|
|
inner_ndb[name] = (
|
|
inner_ndb_data["callsign"],
|
|
RadioFrequency.parse(inner_ndb_data["frequency"], Modulation.AM),
|
|
)
|
|
|
|
return AirfieldData(
|
|
data["name"],
|
|
data["id"],
|
|
data.get("icao"),
|
|
data["elevation"],
|
|
data["runway_length"],
|
|
tacan_channel,
|
|
tacan_callsign,
|
|
vor,
|
|
rsbn,
|
|
ils,
|
|
prmg,
|
|
outer_ndb,
|
|
inner_ndb,
|
|
)
|
|
|
|
@classmethod
|
|
def _load_for_theater_if_needed(cls, theater: ConflictTheater) -> None:
|
|
if theater.terrain.name in cls._airfields:
|
|
return
|
|
|
|
airfields = {}
|
|
base_path = Path("resources/airfields") / theater.terrain.name
|
|
if base_path.is_dir():
|
|
for airfield_yaml in base_path.iterdir():
|
|
data = cls.from_file(airfield_yaml)
|
|
airfields[data.id] = data
|
|
else:
|
|
logging.warning("No airfield data available for %s", theater.terrain.name)
|
|
cls._airfields[theater.terrain.name] = airfields
|
|
|
|
@classmethod
|
|
def for_airport(cls, theater: ConflictTheater, airport: Airport) -> AirfieldData:
|
|
cls._load_for_theater_if_needed(theater)
|
|
return cls._airfields[theater.terrain.name][airport.id]
|
|
|
|
@classmethod
|
|
def for_theater(cls, theater: ConflictTheater) -> Iterator[AirfieldData]:
|
|
cls._load_for_theater_if_needed(theater)
|
|
yield from cls._airfields[theater.terrain.name].values()
|