Export airfield data to yaml, switch to ID keys.

This exports all the old AIRFIELD_DATA to yaml files. It's easier for
users to send fixes if it's defined this way, and they can also fix it
in their install without having to wait for a new release.

This also switches the indexes from the unstable DCS airfield names to
airfield IDs, so this fixes another case of DCS updates occasionally
breaking Liberation.

I also ended up finding quite a few typos in airfield names, and
incorrect theater names in the process. Those have been fixed.
This commit is contained in:
Dan Albert
2022-02-11 01:35:03 -08:00
parent 079f19a66e
commit 011d8a4e12
181 changed files with 2673 additions and 1547 deletions

View File

@@ -71,7 +71,7 @@ class FlightGroupConfigurator:
divert = None
if self.flight.divert is not None:
divert = self.flight.divert.active_runway(
self.game.conditions, self.dynamic_runways
self.game.theater, self.game.conditions, self.dynamic_runways
)
mission_start_time, waypoints = WaypointGenerator(
@@ -93,10 +93,10 @@ class FlightGroupConfigurator:
friendly=self.flight.from_cp.captured,
departure_delay=mission_start_time,
departure=self.flight.departure.active_runway(
self.game.conditions, self.dynamic_runways
self.game.theater, self.game.conditions, self.dynamic_runways
),
arrival=self.flight.arrival.active_runway(
self.game.conditions, self.dynamic_runways
self.game.theater, self.game.conditions, self.dynamic_runways
),
divert=divert,
waypoints=waypoints,

View File

@@ -11,16 +11,16 @@ from dcs.coalition import Coalition
from dcs.countries import country_dict
from game import db
from game.radio.radios import RadioFrequency, RadioRegistry
from game.radio.tacan import TacanRegistry
from game.theater.bullseye import Bullseye
from game.theater import Airfield, FrontLine
from game.unitmap import UnitMap
from gen.airfields import AIRFIELD_DATA
from gen.naming import namegen
from game.missiongenerator.aircraft.aircraftgenerator import (
AircraftGenerator,
)
from game.radio.radios import RadioFrequency, RadioRegistry
from game.radio.tacan import TacanRegistry
from game.theater import Airfield, FrontLine
from game.theater.bullseye import Bullseye
from game.unitmap import UnitMap
from gen.airfields import AirfieldData
from gen.naming import namegen
from .aircraft.flightdata import FlightData
from .airsupport import AirSupport
from .airsupportgenerator import AirSupportGenerator
@@ -173,8 +173,8 @@ class MissionGenerator:
def initialize_radio_registry(
self, unique_map_frequencies: set[RadioFrequency]
) -> None:
for data in AIRFIELD_DATA.values():
if data.theater == self.game.theater.terrain.name and data.atc:
for data in AirfieldData.for_theater(self.game.theater):
if data.atc is not None:
unique_map_frequencies.add(data.atc.hf)
unique_map_frequencies.add(data.atc.vhf_fm)
unique_map_frequencies.add(data.atc.vhf_am)

View File

@@ -1,6 +1,9 @@
"""Radio frequency types and allocators."""
from __future__ import annotations
import itertools
import logging
import re
from dataclasses import dataclass
from typing import Dict, FrozenSet, Iterator, List, Set, Tuple
@@ -35,13 +38,37 @@ class RadioFrequency:
"""
return self.hertz / 1000000
@classmethod
def parse(cls, text: str) -> RadioFrequency:
match = re.match(r"""^(\d+)(?:\.(\d{1,3}))? (MHz|kHz)$""", text)
if match is None:
raise ValueError(f"Could not parse radio frequency from {text}")
whole = int(match.group(1))
partial_str = match.group(2)
units = match.group(3)
partial = 0
if partial_str is not None:
partial = int(partial_str)
if len(partial_str) == 1:
partial *= 100
elif len(partial_str) == 2:
partial *= 10
if units == "MHz":
return MHz(whole, partial)
if units == "kHz":
return kHz(whole, partial)
raise ValueError(f"Unexpected units in radio frequency: {units}")
def MHz(num: int, khz: int = 0) -> RadioFrequency:
return RadioFrequency(num * 1000000 + khz * 1000)
def kHz(num: int) -> RadioFrequency:
return RadioFrequency(num * 1000)
def kHz(num: int, hz: int = 0) -> RadioFrequency:
return RadioFrequency(num * 1000 + hz)
@dataclass(frozen=True)

View File

@@ -1,4 +1,7 @@
"""TACAN channel handling."""
from __future__ import annotations
import re
from dataclasses import dataclass
from enum import Enum
from typing import Dict, Iterator, Set
@@ -45,6 +48,16 @@ class TacanChannel:
def __str__(self) -> str:
return f"{self.number}{self.band.value}"
@classmethod
def parse(cls, text: str) -> TacanChannel:
match = re.match(r"""^(\d{1,3})([XY])$""", text)
if match is None:
raise ValueError(f"Could not parse TACAN from {text}")
number = int(match.group(1))
if not number:
raise ValueError("TACAN channel cannot be 0")
return TacanChannel(number, TacanBand(match.group(2)))
class OutOfTacanChannelsError(RuntimeError):
"""Raised when all channels in this band have been allocated."""

View File

@@ -7,33 +7,26 @@ import math
from abc import ABC, abstractmethod
from collections import defaultdict
from dataclasses import dataclass, field
from enum import Enum, unique, auto, IntEnum
from functools import total_ordering, cached_property
from enum import Enum, IntEnum, auto, unique
from functools import cached_property, total_ordering
from typing import (
Any,
Dict,
Iterable,
Iterator,
List,
Optional,
Sequence,
Set,
TYPE_CHECKING,
Union,
Sequence,
Iterable,
Tuple,
Union,
)
from dcs.mapping import Point
from dcs.ships import (
Forrestal,
Stennis,
KUZNECOW,
LHA_Tarawa,
Type_071,
)
from dcs.ships import Forrestal, KUZNECOW, LHA_Tarawa, Stennis, Type_071
from dcs.terrain.terrain import Airport, ParkingSlot
from dcs.unit import Unit
from dcs.unittype import FlyingType
from game import db
from game.point_with_heading import PointWithHeading
@@ -45,9 +38,9 @@ from gen.runways import RunwayAssigner, RunwayData
from .base import Base
from .missiontarget import MissionTarget
from .theatergroundobject import (
BuildingGroundObject,
GenericCarrierGroundObject,
TheaterGroundObject,
BuildingGroundObject,
)
from ..ato.starttype import StartType
from ..dcs.aircrafttype import AircraftType
@@ -61,6 +54,7 @@ if TYPE_CHECKING:
from game.squadrons.squadron import Squadron
from ..coalition import Coalition
from ..transfers import PendingTransfers
from . import ConflictTheater
FREE_FRONTLINE_UNIT_SUPPLY: int = 15
AMMO_DEPOT_FRONTLINE_UNIT_CONTRIBUTION: int = 12
@@ -694,7 +688,10 @@ class ControlPoint(MissionTarget, ABC):
@abstractmethod
def active_runway(
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
self,
theater: ConflictTheater,
conditions: Conditions,
dynamic_runways: Dict[str, RunwayData],
) -> RunwayData:
...
@@ -936,10 +933,13 @@ class Airfield(ControlPoint):
self.runway_status.damage()
def active_runway(
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
self,
theater: ConflictTheater,
conditions: Conditions,
dynamic_runways: Dict[str, RunwayData],
) -> RunwayData:
assigner = RunwayAssigner(conditions)
return assigner.get_preferred_runway(self.airport)
return assigner.get_preferred_runway(theater, self.airport)
@property
def airdrome_id_for_landing(self) -> Optional[int]:
@@ -1019,7 +1019,10 @@ class NavalControlPoint(ControlPoint, ABC):
return False
def active_runway(
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
self,
theater: ConflictTheater,
conditions: Conditions,
dynamic_runways: Dict[str, RunwayData],
) -> RunwayData:
# TODO: Assign TACAN and ICLS earlier so we don't need this.
fallback = RunwayData(
@@ -1161,7 +1164,10 @@ class OffMapSpawn(ControlPoint):
return Heading.from_degrees(0)
def active_runway(
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
self,
theater: ConflictTheater,
conditions: Conditions,
dynamic_runways: Dict[str, RunwayData],
) -> RunwayData:
logging.warning("TODO: Off map spawns have no runways.")
return RunwayData(
@@ -1202,7 +1208,10 @@ class Fob(ControlPoint):
return self.has_helipads
def active_runway(
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
self,
theater: ConflictTheater,
conditions: Conditions,
dynamic_runways: Dict[str, RunwayData],
) -> RunwayData:
logging.warning("TODO: FOBs have no runways.")
return RunwayData(