mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
The manual for the legacy warthog usually calls these VHF 1/2 and UHF, or VHF AM/FM and UHF. The AM/FM nomenclature is what I usually hear people call them and it's clearer, so go with that. For the A-10C II, that manual hasn't been updated for the AN/ARC-210 yet, so I'm not really sure what that ought to be called. The UFC calls it COM 1 though, so I went with that. The alternative would be something like VHF/UHF for the 210 and UHF for the 164, but I don't know if that's actually better. Could be completely explicit and call them by their full names, but that's probably less clear to people that aren't fiddling with the radio implementation constantly (and even I confuse the 164 and the 186 all the damn time).
392 lines
12 KiB
Python
392 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any, Optional, TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from game.missiongenerator.aircraft.flightdata import FlightData
|
|
from game.missiongenerator.missiondata import MissionData
|
|
|
|
|
|
class RadioChannelAllocator:
|
|
"""Base class for radio channel allocators."""
|
|
|
|
def assign_channels_for_flight(
|
|
self, flight: FlightData, mission_data: MissionData
|
|
) -> 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, mission_data: MissionData
|
|
) -> 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 mission_data.awacs:
|
|
flight.assign_channel(radio_id, next(channel_alloc), awacs.freq)
|
|
|
|
for jtac in mission_data.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 mission_data.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, mission_data: MissionData
|
|
) -> 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, mission_data: MissionData
|
|
) -> 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, mission_data: MissionData
|
|
) -> None:
|
|
# The Viggen's preset channels are handled differently from other
|
|
# aircraft. Since 2.7.9 the group channels will not be generated automatically
|
|
# anymore. So we have to set AWACS and JTAC manually. There are also seven
|
|
# special channels we can modify. 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
|
|
|
|
# Possible Group Channels (100-139)
|
|
channel_alloc = iter(range(1, 40))
|
|
|
|
# Intra-Flight channel on Special 1 and Group 100 (required by module)
|
|
flight.assign_channel(radio_id, 41, flight.intra_flight_channel) # Special 1
|
|
flight.assign_channel(
|
|
radio_id, next(channel_alloc), flight.intra_flight_channel
|
|
)
|
|
|
|
for awacs in mission_data.awacs:
|
|
flight.assign_channel(radio_id, next(channel_alloc), awacs.freq)
|
|
|
|
for jtac in mission_data.jtacs:
|
|
flight.assign_channel(radio_id, next(channel_alloc), jtac.freq)
|
|
|
|
if flight.departure.atc is not None:
|
|
flight.assign_channel(radio_id, 44, flight.departure.atc) # FR24 E
|
|
if flight.arrival.atc is not None:
|
|
flight.assign_channel(radio_id, 45, flight.arrival.atc) # FR24 F
|
|
if flight.divert is not None and flight.divert.atc is not None:
|
|
flight.assign_channel(radio_id, 46, flight.divert.atc) # FR24 G
|
|
|
|
@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, mission_data: MissionData
|
|
) -> 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 MirageF1CEChannelNamer(ChannelNamer):
|
|
"""Channel namer for the Mirage-F1CE."""
|
|
|
|
@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-f1CE"
|
|
|
|
|
|
class ApacheChannelNamer(ChannelNamer):
|
|
"""Channel namer for the AH-64D Apache"""
|
|
|
|
@staticmethod
|
|
def channel_name(radio_id: int, channel_id: int) -> str:
|
|
# From the manual: Radio identifier (“VHF” for ARC-186, “UHF” for ARC-164,
|
|
# “FM1” for first ARC-201D, “FM2” for second ARC-201D, or “HF” for ARC-220).
|
|
radio_name = [
|
|
"VHF", # ARC-186
|
|
"UHF", # ARC-164
|
|
"FM1", # first ARC-201D
|
|
"FM2", # second ARC-201D
|
|
"HF", # ARC-220
|
|
][radio_id - 1]
|
|
return f"{radio_name} Ch {channel_id}"
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "apache"
|
|
|
|
|
|
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:
|
|
special_channels = [
|
|
"FR 22 Special 1",
|
|
"FR 22 Special 2",
|
|
"FR 22 Special 3",
|
|
"FR 24 E",
|
|
"FR 24 F",
|
|
"FR 24 G",
|
|
"FR 24 H",
|
|
]
|
|
if channel_id >= 41: # Special channels are 41-47
|
|
return special_channels[channel_id - 41]
|
|
return f"FR 22 Group {99 + 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"
|
|
|
|
|
|
class LegacyWarthogChannelNamer(ChannelNamer):
|
|
"""Channel namer for the legacy A-10C."""
|
|
|
|
@staticmethod
|
|
def channel_name(radio_id: int, channel_id: int) -> str:
|
|
radio_name = {
|
|
1: "VHF AM",
|
|
2: "UHF",
|
|
3: "VHF FM",
|
|
}[radio_id]
|
|
return f"{radio_name} Ch {channel_id}"
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "a10c-legacy"
|
|
|
|
|
|
class WarthogChannelNamer(ChannelNamer):
|
|
"""Channel namer for the legacy A-10C II"""
|
|
|
|
@staticmethod
|
|
def channel_name(radio_id: int, channel_id: int) -> str:
|
|
radio_name = {
|
|
1: "COM 1",
|
|
2: "UHF",
|
|
3: "VHF FM",
|
|
}[radio_id]
|
|
return f"{radio_name} Ch {channel_id}"
|
|
|
|
@classmethod
|
|
def name(cls) -> str:
|
|
return "a10c-ii"
|