mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
113 lines
3.2 KiB
Python
113 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from collections.abc import Iterator
|
|
from dataclasses import dataclass
|
|
from enum import IntEnum
|
|
from pathlib import Path
|
|
from typing import Optional, TYPE_CHECKING
|
|
|
|
from game.radio.radios import RadioFrequency
|
|
from game.radio.tacan import TacanBand, TacanChannel
|
|
|
|
if TYPE_CHECKING:
|
|
from game.theater import ConflictTheater
|
|
|
|
BEACONS_RESOURCE_PATH = Path("resources/dcs/beacons")
|
|
|
|
|
|
class BeaconType(IntEnum):
|
|
BEACON_TYPE_NULL = 0
|
|
BEACON_TYPE_VOR = 1
|
|
BEACON_TYPE_DME = 2
|
|
BEACON_TYPE_VOR_DME = 3
|
|
BEACON_TYPE_TACAN = 4
|
|
BEACON_TYPE_VORTAC = 5
|
|
BEACON_TYPE_RSBN = 6
|
|
BEACON_TYPE_BROADCAST_STATION = 7
|
|
|
|
BEACON_TYPE_HOMER = 8
|
|
BEACON_TYPE_AIRPORT_HOMER = 9
|
|
BEACON_TYPE_AIRPORT_HOMER_WITH_MARKER = 10
|
|
BEACON_TYPE_ILS_FAR_HOMER = 11
|
|
BEACON_TYPE_ILS_NEAR_HOMER = 12
|
|
|
|
BEACON_TYPE_ILS_LOCALIZER = 13
|
|
BEACON_TYPE_ILS_GLIDESLOPE = 14
|
|
|
|
BEACON_TYPE_PRMG_LOCALIZER = 15
|
|
BEACON_TYPE_PRMG_GLIDESLOPE = 16
|
|
|
|
BEACON_TYPE_ICLS_LOCALIZER = 17
|
|
BEACON_TYPE_ICLS_GLIDESLOPE = 18
|
|
|
|
BEACON_TYPE_NAUTICAL_HOMER = 19
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Beacon:
|
|
name: str
|
|
callsign: str
|
|
beacon_type: BeaconType
|
|
hertz: int
|
|
channel: Optional[int]
|
|
|
|
@property
|
|
def frequency(self) -> RadioFrequency:
|
|
return RadioFrequency(self.hertz)
|
|
|
|
@property
|
|
def is_tacan(self) -> bool:
|
|
return self.beacon_type in (
|
|
BeaconType.BEACON_TYPE_VORTAC,
|
|
BeaconType.BEACON_TYPE_TACAN,
|
|
)
|
|
|
|
@property
|
|
def tacan_channel(self) -> TacanChannel:
|
|
assert self.is_tacan
|
|
assert self.channel is not None
|
|
return TacanChannel(self.channel, TacanBand.X)
|
|
|
|
|
|
class Beacons:
|
|
_by_terrain: dict[str, dict[str, Beacon]] = {}
|
|
|
|
@classmethod
|
|
def _load_for_theater_if_needed(cls, theater: ConflictTheater) -> None:
|
|
if theater.terrain.name in cls._by_terrain:
|
|
return
|
|
|
|
beacons_filename_mapper = {
|
|
"sinaimap": "sinai",
|
|
}
|
|
filename = theater.terrain.name.lower()
|
|
filename = beacons_filename_mapper.get(filename, filename)
|
|
beacons_path = BEACONS_RESOURCE_PATH / f"{filename}.json"
|
|
if not beacons_path.exists():
|
|
raise RuntimeError(f"Beacon file {beacons_path.resolve()} is missing")
|
|
|
|
beacons = {}
|
|
for bid, beacon in json.loads(beacons_path.read_text()).items():
|
|
beacons[bid] = Beacon(
|
|
name=beacon["name"],
|
|
callsign=beacon["callsign"],
|
|
beacon_type=BeaconType(beacon["beacon_type"]),
|
|
hertz=beacon["hertz"],
|
|
channel=beacon["channel"],
|
|
)
|
|
cls._by_terrain[theater.terrain.name] = beacons
|
|
|
|
@classmethod
|
|
def _dict_for_theater(cls, theater: ConflictTheater) -> dict[str, Beacon]:
|
|
cls._load_for_theater_if_needed(theater)
|
|
return cls._by_terrain[theater.terrain.name]
|
|
|
|
@classmethod
|
|
def iter_theater(cls, theater: ConflictTheater) -> Iterator[Beacon]:
|
|
yield from cls._dict_for_theater(theater).values()
|
|
|
|
@classmethod
|
|
def with_id(cls, beacon_id: str, theater: ConflictTheater) -> Beacon:
|
|
return cls._dict_for_theater(theater)[beacon_id]
|