mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
The dataclass contructor will not automatically convert the int in the JSON file to the enum type, so our enum equivalence check was not actually working, and could result in us re-allocating a TACAN channel that was used by the map. Fixing this problem surfaces a latent bug, where we can't actually treat duplicate map TACAN channels as a bug because some channels are used by multiple airports in PG.
108 lines
3.0 KiB
Python
108 lines
3.0 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_file = BEACONS_RESOURCE_PATH / f"{theater.terrain.name.lower()}.json"
|
|
if not beacons_file.exists():
|
|
raise RuntimeError(f"Beacon file {beacons_file.resolve()} is missing")
|
|
|
|
beacons = {}
|
|
for bid, beacon in json.loads(beacons_file.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]
|