mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
We get TACAN, ILS, and ATC data from pydcs now. The rest of this manually curated data is unused.
133 lines
4.4 KiB
Python
133 lines
4.4 KiB
Python
"""Runway information and selection."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Iterator, Optional, TYPE_CHECKING
|
|
|
|
from dcs.terrain.terrain import Airport, RunwayApproach
|
|
|
|
from game.atcdata import AtcData
|
|
from game.dcs.beacons import BeaconType, Beacons
|
|
from game.radio.radios import RadioFrequency
|
|
from game.radio.tacan import TacanChannel
|
|
from game.utils import Heading
|
|
from game.weather import Conditions
|
|
|
|
if TYPE_CHECKING:
|
|
from game.theater import ConflictTheater
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RunwayData:
|
|
airfield_name: str
|
|
runway_heading: Heading
|
|
runway_name: str
|
|
atc: Optional[RadioFrequency] = None
|
|
tacan: Optional[TacanChannel] = None
|
|
tacan_callsign: Optional[str] = None
|
|
ils: Optional[RadioFrequency] = None
|
|
icls: Optional[int] = None
|
|
|
|
@classmethod
|
|
def for_pydcs_runway_runway(
|
|
cls,
|
|
theater: ConflictTheater,
|
|
airport: Airport,
|
|
runway: RunwayApproach,
|
|
) -> RunwayData:
|
|
"""Creates RunwayData for the given runway of an airfield.
|
|
|
|
Args:
|
|
theater: The theater the airport is in.
|
|
airport: The airfield the runway belongs to.
|
|
runway: The pydcs runway.
|
|
"""
|
|
atc: Optional[RadioFrequency] = None
|
|
tacan: Optional[TacanChannel] = None
|
|
tacan_callsign: Optional[str] = None
|
|
ils: Optional[RadioFrequency] = None
|
|
atc_radio = AtcData.from_pydcs(airport)
|
|
if atc_radio is not None:
|
|
atc = atc_radio.uhf
|
|
|
|
for beacon_data in airport.beacons:
|
|
beacon = Beacons.with_id(beacon_data.id, theater)
|
|
if beacon.is_tacan:
|
|
tacan = beacon.tacan_channel
|
|
tacan_callsign = beacon.callsign
|
|
|
|
for beacon_data in runway.beacons:
|
|
beacon = Beacons.with_id(beacon_data.id, theater)
|
|
if beacon.beacon_type is BeaconType.BEACON_TYPE_ILS_GLIDESLOPE:
|
|
ils = beacon.frequency
|
|
|
|
return cls(
|
|
airfield_name=airport.name,
|
|
runway_heading=Heading(runway.heading),
|
|
runway_name=runway.name,
|
|
atc=atc,
|
|
tacan=tacan,
|
|
tacan_callsign=tacan_callsign,
|
|
ils=ils,
|
|
)
|
|
|
|
@classmethod
|
|
def for_pydcs_airport(
|
|
cls, theater: ConflictTheater, airport: Airport
|
|
) -> Iterator[RunwayData]:
|
|
for runway in airport.runways:
|
|
yield cls.for_pydcs_runway_runway(
|
|
theater,
|
|
airport,
|
|
runway.main,
|
|
)
|
|
yield cls.for_pydcs_runway_runway(
|
|
theater,
|
|
airport,
|
|
runway.opposite,
|
|
)
|
|
|
|
|
|
class RunwayAssigner:
|
|
def __init__(self, conditions: Conditions):
|
|
self.conditions = conditions
|
|
|
|
def angle_off_headwind(self, runway: RunwayData) -> Heading:
|
|
wind = Heading.from_degrees(self.conditions.weather.wind.at_0m.direction)
|
|
ideal_heading = wind.opposite
|
|
return runway.runway_heading.angle_between(ideal_heading)
|
|
|
|
def get_preferred_runway(
|
|
self, theater: ConflictTheater, airport: Airport
|
|
) -> RunwayData:
|
|
"""Returns the preferred runway for the given airport.
|
|
|
|
Right now we're only selecting runways based on whether or not
|
|
they have
|
|
ILS, but we could also choose based on wind conditions, or which
|
|
direction flight plans should follow.
|
|
"""
|
|
runways = list(RunwayData.for_pydcs_airport(theater, airport))
|
|
|
|
# Find the runway with the best headwind first.
|
|
best_runways = [runways[0]]
|
|
best_angle_off_headwind = self.angle_off_headwind(best_runways[0])
|
|
for runway in runways[1:]:
|
|
angle_off_headwind = self.angle_off_headwind(runway)
|
|
if angle_off_headwind == best_angle_off_headwind:
|
|
best_runways.append(runway)
|
|
elif angle_off_headwind < best_angle_off_headwind:
|
|
best_runways = [runway]
|
|
best_angle_off_headwind = angle_off_headwind
|
|
|
|
for runway in best_runways:
|
|
# But if there are multiple runways with the same heading,
|
|
# prefer
|
|
# and ILS capable runway.
|
|
if runway.ils is not None:
|
|
return runway
|
|
|
|
# Otherwise the only difference between the two is the distance from
|
|
# parking, which we don't know, so just pick the first one.
|
|
return best_runways[0]
|