mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
302 lines
9.7 KiB
Python
302 lines
9.7 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Optional, Any, TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from gen import FlightData, AirSupport
|
|
|
|
|
|
class RadioChannelAllocator:
|
|
"""Base class for radio channel allocators."""
|
|
|
|
def assign_channels_for_flight(
|
|
self, flight: FlightData, air_support: AirSupport
|
|
) -> None:
|
|
"""Assigns mission frequencies to preset channels for the flight."""
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def from_cfg(cls, cfg: dict[str, Any]) -> RadioChannelAllocator:
|
|
return cls()
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
raise NotImplementedError
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CommonRadioChannelAllocator(RadioChannelAllocator):
|
|
"""Radio channel allocator suitable for most aircraft.
|
|
|
|
Most of the aircraft with preset channels available have one or more radios
|
|
with 20 or more channels available (typically per-radio, but this is not the
|
|
case for the JF-17).
|
|
"""
|
|
|
|
#: Index of the radio used for intra-flight communications. Matches the
|
|
#: index of the panel_radio field of the pydcs.dcs.planes object.
|
|
inter_flight_radio_index: Optional[int]
|
|
|
|
#: Index of the radio used for intra-flight communications. Matches the
|
|
#: index of the panel_radio field of the pydcs.dcs.planes object.
|
|
intra_flight_radio_index: Optional[int]
|
|
|
|
def assign_channels_for_flight(
|
|
self, flight: FlightData, air_support: AirSupport
|
|
) -> None:
|
|
if self.intra_flight_radio_index is not None:
|
|
flight.assign_channel(
|
|
self.intra_flight_radio_index, 1, flight.intra_flight_channel
|
|
)
|
|
|
|
if self.inter_flight_radio_index is None:
|
|
return
|
|
|
|
# For cases where the inter-flight and intra-flight radios share presets
|
|
# (the JF-17 only has one set of channels, even though it can use two
|
|
# channels simultaneously), start assigning inter-flight channels at 2.
|
|
radio_id = self.inter_flight_radio_index
|
|
if self.intra_flight_radio_index == radio_id:
|
|
first_channel = 2
|
|
else:
|
|
first_channel = 1
|
|
|
|
last_channel = flight.num_radio_channels(radio_id)
|
|
channel_alloc = iter(range(first_channel, last_channel + 1))
|
|
|
|
if flight.departure.atc is not None:
|
|
flight.assign_channel(radio_id, next(channel_alloc), flight.departure.atc)
|
|
|
|
# TODO: If there ever are multiple AWACS, limit to mission relevant.
|
|
for awacs in air_support.awacs:
|
|
flight.assign_channel(radio_id, next(channel_alloc), awacs.freq)
|
|
|
|
for jtac in air_support.jtacs:
|
|
flight.assign_channel(radio_id, next(channel_alloc), jtac.freq)
|
|
|
|
if flight.arrival != flight.departure and flight.arrival.atc is not None:
|
|
flight.assign_channel(radio_id, next(channel_alloc), flight.arrival.atc)
|
|
|
|
try:
|
|
# TODO: Skip incompatible tankers.
|
|
for tanker in air_support.tankers:
|
|
flight.assign_channel(radio_id, next(channel_alloc), tanker.freq)
|
|
|
|
if flight.divert is not None and flight.divert.atc is not None:
|
|
flight.assign_channel(radio_id, next(channel_alloc), flight.divert.atc)
|
|
except StopIteration:
|
|
# Any remaining channels are nice-to-haves, but not necessary for
|
|
# the few aircraft with a small number of channels available.
|
|
pass
|
|
|
|
@classmethod
|
|
def from_cfg(cls, cfg: dict[str, Any]) -> CommonRadioChannelAllocator:
|
|
return CommonRadioChannelAllocator(
|
|
inter_flight_radio_index=cfg["inter_flight_radio_index"],
|
|
intra_flight_radio_index=cfg["intra_flight_radio_index"],
|
|
)
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "common"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class NoOpChannelAllocator(RadioChannelAllocator):
|
|
"""Channel allocator for aircraft that don't support preset channels."""
|
|
|
|
def assign_channels_for_flight(
|
|
self, flight: FlightData, air_support: AirSupport
|
|
) -> None:
|
|
pass
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "noop"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FarmerRadioChannelAllocator(RadioChannelAllocator):
|
|
"""Preset channel allocator for the MiG-19P."""
|
|
|
|
def assign_channels_for_flight(
|
|
self, flight: FlightData, air_support: AirSupport
|
|
) -> None:
|
|
# The Farmer only has 6 preset channels. It also only has a VHF radio,
|
|
# and currently our ATC data and AWACS are only in the UHF band.
|
|
radio_id = 1
|
|
flight.assign_channel(radio_id, 1, flight.intra_flight_channel)
|
|
# TODO: Assign 4-6 to VHF frequencies of departure, arrival, and divert.
|
|
# TODO: Assign 2 and 3 to AWACS if it is VHF.
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "farmer"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ViggenRadioChannelAllocator(RadioChannelAllocator):
|
|
"""Preset channel allocator for the AJS37."""
|
|
|
|
def assign_channels_for_flight(
|
|
self, flight: FlightData, air_support: AirSupport
|
|
) -> None:
|
|
# The Viggen's preset channels are handled differently from other
|
|
# aircraft. The aircraft automatically configures channels for every
|
|
# allied flight in the game (including AWACS) and for every airfield. As
|
|
# such, we don't need to allocate any of those. There are seven presets
|
|
# we can modify, however: three channels for the main radio intended for
|
|
# communication with wingmen, and four emergency channels for the backup
|
|
# radio. We'll set the first channel of the main radio to the
|
|
# intra-flight channel, and the first three emergency channels to each
|
|
# of the flight plan's airfields. The fourth emergency channel is always
|
|
# the guard channel.
|
|
radio_id = 1
|
|
flight.assign_channel(radio_id, 1, flight.intra_flight_channel)
|
|
if flight.departure.atc is not None:
|
|
flight.assign_channel(radio_id, 4, flight.departure.atc)
|
|
if flight.arrival.atc is not None:
|
|
flight.assign_channel(radio_id, 5, flight.arrival.atc)
|
|
# TODO: Assign divert to 6 when we support divert airfields.
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "viggen"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SCR522RadioChannelAllocator(RadioChannelAllocator):
|
|
"""Preset channel allocator for the SCR522 WW2 radios. (4 channels)"""
|
|
|
|
def assign_channels_for_flight(
|
|
self, flight: FlightData, air_support: AirSupport
|
|
) -> None:
|
|
radio_id = 1
|
|
flight.assign_channel(radio_id, 1, flight.intra_flight_channel)
|
|
if flight.departure.atc is not None:
|
|
flight.assign_channel(radio_id, 2, flight.departure.atc)
|
|
if flight.arrival.atc is not None:
|
|
flight.assign_channel(radio_id, 3, flight.arrival.atc)
|
|
|
|
# TODO : Some GCI on Channel 4 ?
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "SCR-522"
|
|
|
|
|
|
class ChannelNamer:
|
|
"""Base class allowing channel name customization per-aircraft.
|
|
|
|
Most aircraft will want to customize this behavior, but the default is
|
|
reasonable for any aircraft with numbered radios.
|
|
"""
|
|
|
|
@staticmethod
|
|
def channel_name(radio_id: int, channel_id: int) -> str:
|
|
"""Returns the name of the channel for the given radio and channel."""
|
|
return f"COMM{radio_id} Ch {channel_id}"
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "default"
|
|
|
|
|
|
class SingleRadioChannelNamer(ChannelNamer):
|
|
"""Channel namer for the aircraft with only a single radio.
|
|
|
|
Aircraft like the MiG-19P and the MiG-21bis only have a single radio, so
|
|
it's not necessary for us to name the radio when naming the channel.
|
|
"""
|
|
|
|
@staticmethod
|
|
def channel_name(radio_id: int, channel_id: int) -> str:
|
|
return f"Ch {channel_id}"
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "single"
|
|
|
|
|
|
class HueyChannelNamer(ChannelNamer):
|
|
"""Channel namer for the UH-1H."""
|
|
|
|
@staticmethod
|
|
def channel_name(radio_id: int, channel_id: int) -> str:
|
|
return f"COM3 Ch {channel_id}"
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "huey"
|
|
|
|
|
|
class MirageChannelNamer(ChannelNamer):
|
|
"""Channel namer for the M-2000."""
|
|
|
|
@staticmethod
|
|
def channel_name(radio_id: int, channel_id: int) -> str:
|
|
radio_name = ["V/UHF", "UHF"][radio_id - 1]
|
|
return f"{radio_name} Ch {channel_id}"
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "mirage"
|
|
|
|
|
|
class TomcatChannelNamer(ChannelNamer):
|
|
"""Channel namer for the F-14."""
|
|
|
|
@staticmethod
|
|
def channel_name(radio_id: int, channel_id: int) -> str:
|
|
radio_name = ["UHF", "VHF/UHF"][radio_id - 1]
|
|
return f"{radio_name} Ch {channel_id}"
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "tomcat"
|
|
|
|
|
|
class ViggenChannelNamer(ChannelNamer):
|
|
"""Channel namer for the AJS37."""
|
|
|
|
@staticmethod
|
|
def channel_name(radio_id: int, channel_id: int) -> str:
|
|
if channel_id >= 4:
|
|
channel_letter = "EFGH"[channel_id - 4]
|
|
return f"FR 24 {channel_letter}"
|
|
return f"FR 22 Special {channel_id}"
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "viggen"
|
|
|
|
|
|
class ViperChannelNamer(ChannelNamer):
|
|
"""Channel namer for the F-16."""
|
|
|
|
@staticmethod
|
|
def channel_name(radio_id: int, channel_id: int) -> str:
|
|
return f"COM{radio_id} Ch {channel_id}"
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "viper"
|
|
|
|
|
|
class SCR522ChannelNamer(ChannelNamer):
|
|
"""
|
|
Channel namer for P-51 & P-47D
|
|
"""
|
|
|
|
@staticmethod
|
|
def channel_name(radio_id: int, channel_id: int) -> str:
|
|
if channel_id > 3:
|
|
return "?"
|
|
else:
|
|
return f"Button " + "ABCD"[channel_id - 1]
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "SCR-522"
|