mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Configurable RF/TCN/ICLS/LINK4 with UI feedback
Resolves #70 Freq/Channel will turn orange when double booked. Freq will turn red if GUARD freq was assigned.
This commit is contained in:
@@ -12,6 +12,10 @@ from .flightroster import FlightRoster
|
||||
from .flightstate import FlightState, Navigating, Uninitialized
|
||||
from .flightstate.killed import Killed
|
||||
from .loadouts import Loadout, Weapon
|
||||
from ..radio.RadioFrequencyContainer import RadioFrequencyContainer
|
||||
from ..radio.TacanContainer import TacanContainer
|
||||
from ..radio.radios import RadioFrequency
|
||||
from ..radio.tacan import TacanChannel
|
||||
from ..sidc import (
|
||||
Entity,
|
||||
SidcDescribable,
|
||||
@@ -36,7 +40,7 @@ if TYPE_CHECKING:
|
||||
F18_TGP_PYLON: int = 4
|
||||
|
||||
|
||||
class Flight(SidcDescribable):
|
||||
class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
|
||||
def __init__(
|
||||
self,
|
||||
package: Package,
|
||||
@@ -49,6 +53,9 @@ class Flight(SidcDescribable):
|
||||
custom_name: Optional[str] = None,
|
||||
cargo: Optional[TransferOrder] = None,
|
||||
roster: Optional[FlightRoster] = None,
|
||||
frequency: Optional[RadioFrequency] = None,
|
||||
channel: Optional[TacanChannel] = None,
|
||||
callsign: Optional[str] = None,
|
||||
) -> None:
|
||||
self.id = uuid.uuid4()
|
||||
self.package = package
|
||||
@@ -68,6 +75,11 @@ class Flight(SidcDescribable):
|
||||
self.custom_name = custom_name
|
||||
self.group_id: int = 0
|
||||
|
||||
self.frequency = frequency
|
||||
if self.unit_type.dcs_unit_type.tacan:
|
||||
self.tacan = channel
|
||||
self.tcn_name = callsign
|
||||
|
||||
# Only used by transport missions.
|
||||
self.cargo = cargo
|
||||
|
||||
|
||||
@@ -13,13 +13,14 @@ from .flightplans.formation import FormationFlightPlan
|
||||
from .flighttype import FlightType
|
||||
from .packagewaypoints import PackageWaypoints
|
||||
from .traveltime import TotEstimator
|
||||
from ..radio.RadioFrequencyContainer import RadioFrequencyContainer
|
||||
from ..radio.radios import RadioFrequency
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.theater import ControlPoint, MissionTarget
|
||||
|
||||
|
||||
class Package:
|
||||
class Package(RadioFrequencyContainer):
|
||||
"""A mission package."""
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from game.ato.packagewaypoints import PackageWaypoints
|
||||
from game.data.doctrine import MODERN_DOCTRINE, COLDWAR_DOCTRINE, WWII_DOCTRINE
|
||||
@@ -9,6 +9,11 @@ if TYPE_CHECKING:
|
||||
from game import Game
|
||||
|
||||
|
||||
def try_set_attr(obj: Any, attr_name: str, val: Any = None) -> None:
|
||||
if not hasattr(obj, attr_name):
|
||||
setattr(obj, attr_name, val)
|
||||
|
||||
|
||||
class Migrator:
|
||||
def __init__(self, game: Game):
|
||||
self.game = game
|
||||
@@ -18,6 +23,8 @@ class Migrator:
|
||||
self._update_doctrine()
|
||||
self._update_packagewaypoints()
|
||||
self._update_package_attributes()
|
||||
self._update_control_points()
|
||||
self._update_flights()
|
||||
|
||||
def _update_doctrine(self) -> None:
|
||||
doctrines = [
|
||||
@@ -40,13 +47,32 @@ class Migrator:
|
||||
def _update_packagewaypoints(self) -> None:
|
||||
for c in self.game.coalitions:
|
||||
for p in c.ato.packages:
|
||||
if p.flights and not hasattr(p.waypoints, "initial"):
|
||||
p.waypoints = PackageWaypoints.create(p, c)
|
||||
if p.flights:
|
||||
try_set_attr(p.waypoints, "initial", PackageWaypoints.create(p, c))
|
||||
|
||||
def _update_package_attributes(self) -> None:
|
||||
for c in self.game.coalitions:
|
||||
for p in c.ato.packages:
|
||||
if not hasattr(p, "custom_name"):
|
||||
p.custom_name = None
|
||||
if not hasattr(p, "frequency"):
|
||||
p.frequency = None
|
||||
try_set_attr(p, "custom_name")
|
||||
try_set_attr(p, "frequency")
|
||||
|
||||
def _update_control_points(self) -> None:
|
||||
for cp in self.game.theater.controlpoints:
|
||||
is_carrier = cp.is_carrier
|
||||
is_lha = cp.is_lha
|
||||
is_fob = cp.category == "fob"
|
||||
radio_configurable = is_carrier or is_lha or is_fob
|
||||
if radio_configurable:
|
||||
try_set_attr(cp, "frequency")
|
||||
if is_carrier or is_lha:
|
||||
try_set_attr(cp, "tacan")
|
||||
try_set_attr(cp, "tcn_name")
|
||||
try_set_attr(cp, "icls_channel")
|
||||
try_set_attr(cp, "icls_name")
|
||||
try_set_attr(cp, "link4")
|
||||
|
||||
def _update_flights(self) -> None:
|
||||
for f in self.game.db.flights.objects.values():
|
||||
try_set_attr(f, "frequency")
|
||||
try_set_attr(f, "tacan")
|
||||
try_set_attr(f, "tcn_name")
|
||||
|
||||
@@ -104,11 +104,7 @@ class AircraftGenerator:
|
||||
ato: The ATO to spawn aircraft for.
|
||||
dynamic_runways: Runway data for carriers and FARPs.
|
||||
"""
|
||||
for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)):
|
||||
if package.frequency is None:
|
||||
continue
|
||||
if package.frequency not in self.radio_registry.allocated_channels:
|
||||
self.radio_registry.reserve(package.frequency)
|
||||
self._reserve_frequencies_and_tacan(ato)
|
||||
|
||||
for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)):
|
||||
if not package.flights:
|
||||
@@ -206,3 +202,18 @@ class AircraftGenerator:
|
||||
).configure()
|
||||
)
|
||||
return group
|
||||
|
||||
def _reserve_frequencies_and_tacan(self, ato: AirTaskingOrder) -> None:
|
||||
for package in ato.packages:
|
||||
if package.frequency is None:
|
||||
continue
|
||||
if package.frequency not in self.radio_registry.allocated_channels:
|
||||
self.radio_registry.reserve(package.frequency)
|
||||
for f in package.flights:
|
||||
if (
|
||||
f.frequency
|
||||
and f.frequency not in self.radio_registry.allocated_channels
|
||||
):
|
||||
self.radio_registry.reserve(f.frequency)
|
||||
if f.tacan and f.tacan not in self.tacan_registy.allocated_channels:
|
||||
self.tacan_registy.mark_unavailable(f.tacan)
|
||||
|
||||
@@ -133,15 +133,16 @@ class FlightGroupConfigurator:
|
||||
laser_codes.append(None)
|
||||
|
||||
def setup_radios(self) -> RadioFrequency:
|
||||
if (freq := self.flight.package.frequency) is None:
|
||||
freq = self.flight.frequency
|
||||
if freq is None and (freq := self.flight.package.frequency) is None:
|
||||
freq = self.radio_registry.alloc_uhf()
|
||||
self.flight.package.frequency = freq
|
||||
elif freq not in self.radio_registry.allocated_channels:
|
||||
if freq not in self.radio_registry.allocated_channels:
|
||||
self.radio_registry.reserve(freq)
|
||||
|
||||
if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}:
|
||||
self.register_air_support(freq)
|
||||
elif self.flight.client_count:
|
||||
elif self.flight.frequency is None and self.flight.client_count:
|
||||
freq = self.flight.unit_type.alloc_flight_radio(self.radio_registry)
|
||||
|
||||
self.group.set_frequency(freq.mhz)
|
||||
@@ -162,7 +163,12 @@ class FlightGroupConfigurator:
|
||||
)
|
||||
)
|
||||
elif isinstance(self.flight.flight_plan, TheaterRefuelingFlightPlan):
|
||||
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y, TacanUsage.AirToAir)
|
||||
if self.flight.tacan is None:
|
||||
tacan = self.tacan_registry.alloc_for_band(
|
||||
TacanBand.Y, TacanUsage.AirToAir
|
||||
)
|
||||
else:
|
||||
tacan = self.flight.tacan
|
||||
self.mission_data.tankers.append(
|
||||
TankerInfo(
|
||||
group_name=str(self.group.name),
|
||||
|
||||
@@ -71,11 +71,14 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
|
||||
if self.flight.unit_type.dcs_unit_type.tacan:
|
||||
tanker_info = self.mission_data.tankers[-1]
|
||||
tacan = tanker_info.tacan
|
||||
tacan_callsign = {
|
||||
"Texaco": "TEX",
|
||||
"Arco": "ARC",
|
||||
"Shell": "SHL",
|
||||
}.get(tanker_info.callsign)
|
||||
if self.flight.tcn_name is None:
|
||||
tacan_callsign = {
|
||||
"Texaco": "TEX",
|
||||
"Arco": "ARC",
|
||||
"Shell": "SHL",
|
||||
}.get(tanker_info.callsign)
|
||||
else:
|
||||
tacan_callsign = self.flight.tcn_name
|
||||
|
||||
waypoint.add_task(
|
||||
ActivateBeaconCommand(
|
||||
|
||||
@@ -37,6 +37,7 @@ from .missiondata import MissionData
|
||||
from .tgogenerator import TgoGenerator
|
||||
from .triggergenerator import TriggerGenerator
|
||||
from .visualsgenerator import VisualsGenerator
|
||||
from ..radio.TacanContainer import TacanContainer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@@ -177,6 +178,9 @@ class MissionGenerator:
|
||||
logging.warning(f"TACAN beacon has no channel: {beacon.callsign}")
|
||||
else:
|
||||
self.tacan_registry.mark_unavailable(beacon.tacan_channel)
|
||||
for cp in self.game.theater.controlpoints:
|
||||
if isinstance(cp, TacanContainer) and cp.tacan is not None:
|
||||
self.tacan_registry.mark_unavailable(cp.tacan)
|
||||
|
||||
def initialize_radio_registry(
|
||||
self, unique_map_frequencies: set[RadioFrequency]
|
||||
|
||||
@@ -22,6 +22,8 @@ from dcs.ships import (
|
||||
CVN_73,
|
||||
CVN_75,
|
||||
Stennis,
|
||||
Forrestal,
|
||||
LHA_Tarawa,
|
||||
)
|
||||
from dcs.statics import Fortification
|
||||
from dcs.task import (
|
||||
@@ -35,7 +37,7 @@ from dcs.task import (
|
||||
)
|
||||
from dcs.translation import String
|
||||
from dcs.triggers import Event, TriggerOnce, TriggerStart, TriggerZone
|
||||
from dcs.unit import Unit, InvisibleFARP
|
||||
from dcs.unit import Unit, InvisibleFARP, BaseFARP
|
||||
from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup
|
||||
from dcs.unittype import ShipType, VehicleType
|
||||
from dcs.vehicles import vehicle_map
|
||||
@@ -45,10 +47,16 @@ from game.missiongenerator.groundforcepainter import (
|
||||
GroundForcePainter,
|
||||
)
|
||||
from game.missiongenerator.missiondata import CarrierInfo, MissionData
|
||||
from game.radio.RadioFrequencyContainer import RadioFrequencyContainer
|
||||
from game.radio.radios import RadioFrequency, RadioRegistry
|
||||
from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage
|
||||
from game.runways import RunwayData
|
||||
from game.theater import ControlPoint, TheaterGroundObject, TheaterUnit
|
||||
from game.theater import (
|
||||
ControlPoint,
|
||||
TheaterGroundObject,
|
||||
TheaterUnit,
|
||||
NavalControlPoint,
|
||||
)
|
||||
from game.theater.theatergroundobject import (
|
||||
CarrierGroundObject,
|
||||
GenericCarrierGroundObject,
|
||||
@@ -351,7 +359,7 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
||||
def __init__(
|
||||
self,
|
||||
ground_object: GenericCarrierGroundObject,
|
||||
control_point: ControlPoint,
|
||||
control_point: NavalControlPoint,
|
||||
country: Country,
|
||||
game: Game,
|
||||
mission: Mission,
|
||||
@@ -372,9 +380,12 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
||||
self.mission_data = mission_data
|
||||
|
||||
def generate(self) -> None:
|
||||
|
||||
# This can also be refactored as the general generation was updated
|
||||
atc = self.radio_registry.alloc_uhf()
|
||||
if self.control_point.frequency is not None:
|
||||
atc = self.control_point.frequency
|
||||
if atc not in self.radio_registry.allocated_channels:
|
||||
self.radio_registry.reserve(atc)
|
||||
else:
|
||||
atc = self.radio_registry.alloc_uhf()
|
||||
|
||||
for g_id, group in enumerate(self.ground_object.groups):
|
||||
if not group.units:
|
||||
@@ -409,15 +420,33 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
||||
f"Error generating carrier group for {self.control_point.name}"
|
||||
)
|
||||
ship_group.units[0].type = carrier_type.id
|
||||
tacan = self.tacan_registry.alloc_for_band(
|
||||
TacanBand.X, TacanUsage.TransmitReceive
|
||||
)
|
||||
tacan_callsign = self.tacan_callsign()
|
||||
icls = next(self.icls_alloc)
|
||||
if self.control_point.tacan is None:
|
||||
tacan = self.tacan_registry.alloc_for_band(
|
||||
TacanBand.X, TacanUsage.TransmitReceive
|
||||
)
|
||||
else:
|
||||
tacan = self.control_point.tacan
|
||||
if self.control_point.tcn_name is None:
|
||||
tacan_callsign = self.tacan_callsign()
|
||||
else:
|
||||
tacan_callsign = self.control_point.tcn_name
|
||||
link4 = None
|
||||
if carrier_type in [Stennis, CVN_71, CVN_72, CVN_73, CVN_75]:
|
||||
link4 = self.radio_registry.alloc_uhf()
|
||||
self.activate_beacons(ship_group, tacan, tacan_callsign, icls, link4)
|
||||
link4carriers = [Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal]
|
||||
if carrier_type in link4carriers:
|
||||
if self.control_point.link4 is None:
|
||||
link4 = self.radio_registry.alloc_uhf()
|
||||
else:
|
||||
link4 = self.control_point.link4
|
||||
icls = None
|
||||
icls_name = self.control_point.icls_name
|
||||
if carrier_type in link4carriers or carrier_type == LHA_Tarawa:
|
||||
if self.control_point.icls_channel is None:
|
||||
icls = next(self.icls_alloc)
|
||||
else:
|
||||
icls = self.control_point.icls_channel
|
||||
self.activate_beacons(
|
||||
ship_group, tacan, tacan_callsign, icls, icls_name, link4
|
||||
)
|
||||
self.add_runway_data(
|
||||
brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls
|
||||
)
|
||||
@@ -461,7 +490,8 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
||||
group: ShipGroup,
|
||||
tacan: TacanChannel,
|
||||
callsign: str,
|
||||
icls: int,
|
||||
icls: Optional[int] = None,
|
||||
icls_name: Optional[str] = None,
|
||||
link4: Optional[RadioFrequency] = None,
|
||||
) -> None:
|
||||
group.points[0].tasks.append(
|
||||
@@ -473,12 +503,14 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
||||
aa=False,
|
||||
)
|
||||
)
|
||||
group.points[0].tasks.append(
|
||||
ActivateICLSCommand(icls, unit_id=group.units[0].id)
|
||||
)
|
||||
if icls is not None:
|
||||
icls_name = "" if icls_name is None else icls_name
|
||||
group.points[0].tasks.append(
|
||||
ActivateICLSCommand(icls, group.units[0].id, icls_name)
|
||||
)
|
||||
if link4 is not None:
|
||||
group.points[0].tasks.append(
|
||||
ActivateLink4Command(int(link4.mhz), group.units[0].id)
|
||||
ActivateLink4Command(link4.hertz, group.units[0].id)
|
||||
)
|
||||
group.points[0].tasks.append(ActivateACLSCommand(unit_id=group.units[0].id))
|
||||
|
||||
@@ -488,7 +520,7 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
||||
atc: RadioFrequency,
|
||||
tacan: TacanChannel,
|
||||
callsign: str,
|
||||
icls: int,
|
||||
icls: Optional[int],
|
||||
) -> None:
|
||||
# TODO: Make unit name usable.
|
||||
# This relies on one control point mapping exactly
|
||||
@@ -590,6 +622,13 @@ class HelipadGenerator:
|
||||
self.helipads.add_unit(
|
||||
InvisibleFARP(self.m.terrain, self.m.next_unit_id(), name_i)
|
||||
)
|
||||
|
||||
# Set FREQ
|
||||
if isinstance(self.cp, RadioFrequencyContainer) and self.cp.frequency:
|
||||
for hp in self.helipads.units:
|
||||
if isinstance(hp, BaseFARP):
|
||||
hp.heliport_frequency = self.cp.frequency.mhz
|
||||
|
||||
pad = self.helipads.units[-1]
|
||||
pad.position = helipad
|
||||
pad.heading = heading
|
||||
@@ -661,7 +700,9 @@ class TgoGenerator:
|
||||
|
||||
for ground_object in cp.ground_objects:
|
||||
generator: GroundObjectGenerator
|
||||
if isinstance(ground_object, CarrierGroundObject):
|
||||
if isinstance(ground_object, CarrierGroundObject) and isinstance(
|
||||
cp, NavalControlPoint
|
||||
):
|
||||
generator = CarrierGenerator(
|
||||
ground_object,
|
||||
cp,
|
||||
@@ -675,7 +716,9 @@ class TgoGenerator:
|
||||
self.unit_map,
|
||||
self.mission_data,
|
||||
)
|
||||
elif isinstance(ground_object, LhaGroundObject):
|
||||
elif isinstance(ground_object, LhaGroundObject) and isinstance(
|
||||
cp, NavalControlPoint
|
||||
):
|
||||
generator = LhaGenerator(
|
||||
ground_object,
|
||||
cp,
|
||||
|
||||
6
game/radio/ICLSContainer.py
Normal file
6
game/radio/ICLSContainer.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class ICLSContainer:
|
||||
icls_channel: Optional[int] = None
|
||||
icls_name: Optional[str] = None
|
||||
8
game/radio/Link4Container.py
Normal file
8
game/radio/Link4Container.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from typing import Optional
|
||||
|
||||
from game.radio.RadioFrequencyContainer import RadioFrequencyContainer
|
||||
from game.radio.radios import RadioFrequency
|
||||
|
||||
|
||||
class Link4Container(RadioFrequencyContainer):
|
||||
link4: Optional[RadioFrequency] = None
|
||||
7
game/radio/RadioFrequencyContainer.py
Normal file
7
game/radio/RadioFrequencyContainer.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from typing import Optional
|
||||
|
||||
from game.radio.radios import RadioFrequency
|
||||
|
||||
|
||||
class RadioFrequencyContainer:
|
||||
frequency: Optional[RadioFrequency] = None
|
||||
8
game/radio/TacanContainer.py
Normal file
8
game/radio/TacanContainer.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from typing import Optional
|
||||
|
||||
from game.radio.tacan import TacanChannel
|
||||
|
||||
|
||||
class TacanContainer:
|
||||
tacan: Optional[TacanChannel] = None
|
||||
tcn_name: Optional[str] = None
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import random
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, FrozenSet, Iterator, List, Set, Tuple
|
||||
@@ -156,57 +157,61 @@ class ChannelInUseError(RuntimeError):
|
||||
# TODO: Figure out appropriate steps for each radio. These are just guesses.
|
||||
#: List of all known radios used by aircraft in the game.
|
||||
RADIOS: List[Radio] = [
|
||||
Radio("AN/ARC-164", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
|
||||
Radio("AN/ARC-186(V) AM", (RadioRange(MHz(116), MHz(152), MHz(1), Modulation.AM),)),
|
||||
Radio("AN/ARC-186(V) FM", (RadioRange(MHz(30), MHz(76), MHz(1), Modulation.FM),)),
|
||||
Radio("AN/ARC-164", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)),
|
||||
Radio(
|
||||
"AN/ARC-186(V) AM", (RadioRange(MHz(116), MHz(152), kHz(25), Modulation.AM),)
|
||||
),
|
||||
Radio("AN/ARC-186(V) FM", (RadioRange(MHz(30), MHz(76), kHz(25), Modulation.FM),)),
|
||||
Radio(
|
||||
"AN/ARC-210",
|
||||
(
|
||||
RadioRange(
|
||||
MHz(225),
|
||||
MHz(400),
|
||||
MHz(1),
|
||||
kHz(25),
|
||||
Modulation.AM,
|
||||
frozenset((MHz(243),)),
|
||||
),
|
||||
RadioRange(MHz(136), MHz(155), MHz(1), Modulation.AM),
|
||||
RadioRange(MHz(156), MHz(174), MHz(1), Modulation.FM),
|
||||
RadioRange(MHz(118), MHz(136), MHz(1), Modulation.AM),
|
||||
RadioRange(MHz(30), MHz(88), MHz(1), Modulation.FM),
|
||||
RadioRange(MHz(136), MHz(155), kHz(25), Modulation.AM),
|
||||
RadioRange(MHz(156), MHz(174), kHz(25), Modulation.FM),
|
||||
RadioRange(MHz(118), MHz(136), kHz(25), Modulation.AM),
|
||||
RadioRange(MHz(30), MHz(88), kHz(25), Modulation.FM),
|
||||
# The AN/ARC-210 can also use 225-400 and 136-155 with FM Modulation
|
||||
RadioRange(
|
||||
MHz(225),
|
||||
MHz(400),
|
||||
MHz(1),
|
||||
kHz(25),
|
||||
Modulation.FM,
|
||||
frozenset((MHz(243),)),
|
||||
),
|
||||
RadioRange(MHz(136), MHz(155), MHz(1), Modulation.FM),
|
||||
RadioRange(MHz(136), MHz(155), kHz(25), Modulation.FM),
|
||||
),
|
||||
),
|
||||
Radio("AN/ARC-222", (RadioRange(MHz(116), MHz(152), MHz(1), Modulation.AM),)),
|
||||
Radio("SCR-522", (RadioRange(MHz(100), MHz(156), MHz(1), Modulation.AM),)),
|
||||
Radio("A.R.I. 1063", (RadioRange(MHz(100), MHz(156), MHz(1), Modulation.AM),)),
|
||||
Radio("AN/ARC-222", (RadioRange(MHz(116), MHz(152), kHz(25), Modulation.AM),)),
|
||||
Radio("SCR-522", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)),
|
||||
Radio("A.R.I. 1063", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)),
|
||||
Radio("BC-1206", (RadioRange(kHz(200), kHz(400), kHz(10), Modulation.AM),)),
|
||||
Radio(
|
||||
"TRT ERA 7000 V/UHF",
|
||||
(
|
||||
RadioRange(MHz(118), MHz(150), MHz(1), Modulation.AM),
|
||||
RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),
|
||||
RadioRange(MHz(118), MHz(150), kHz(25), Modulation.AM),
|
||||
RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),
|
||||
),
|
||||
),
|
||||
Radio("TRT ERA 7200 UHF", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
|
||||
Radio(
|
||||
"TRT ERA 7200 UHF", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)
|
||||
),
|
||||
# Tomcat radios
|
||||
# # https://www.heatblur.se/F-14Manual/general.html#an-arc-159-uhf-1-radio
|
||||
Radio("AN/ARC-159", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
|
||||
Radio("AN/ARC-159", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)),
|
||||
# https://www.heatblur.se/F-14Manual/general.html#an-arc-182-v-uhf-2-radio
|
||||
Radio(
|
||||
"AN/ARC-182",
|
||||
(
|
||||
RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),
|
||||
RadioRange(MHz(108), MHz(174), MHz(1), Modulation.AM),
|
||||
RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),
|
||||
RadioRange(MHz(108), MHz(174), kHz(25), Modulation.AM),
|
||||
# The Range from 30-88MHz should be FM but its modeled as AM in dcs
|
||||
RadioRange(MHz(30), MHz(88), MHz(1), Modulation.AM),
|
||||
RadioRange(MHz(30), MHz(88), kHz(25), Modulation.AM),
|
||||
),
|
||||
),
|
||||
Radio(
|
||||
@@ -220,14 +225,14 @@ RADIOS: List[Radio] = [
|
||||
# 4 preset channels (A/B/C/D)
|
||||
Radio("SCR522", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)),
|
||||
# JF-17 Radios should use AM
|
||||
Radio("R&S M3AR VHF", (RadioRange(MHz(120), MHz(174), MHz(1), Modulation.AM),)),
|
||||
Radio("R&S M3AR UHF", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
|
||||
Radio("R&S M3AR VHF", (RadioRange(MHz(120), MHz(174), kHz(25), Modulation.AM),)),
|
||||
Radio("R&S M3AR UHF", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)),
|
||||
# MiG-15bis
|
||||
Radio("RSI-6K HF", (RadioRange(MHz(3, 750), MHz(5), kHz(25), Modulation.AM),)),
|
||||
# MiG-19P
|
||||
Radio("RSIU-4V", (RadioRange(MHz(100), MHz(150), MHz(1), Modulation.AM),)),
|
||||
Radio("RSIU-4V", (RadioRange(MHz(100), MHz(150), kHz(25), Modulation.AM),)),
|
||||
# MiG-21bis
|
||||
Radio("RSIU-5V", (RadioRange(MHz(118), MHz(140), MHz(1), Modulation.AM),)),
|
||||
Radio("RSIU-5V", (RadioRange(MHz(118), MHz(140), kHz(25), Modulation.AM),)),
|
||||
# Ka-50
|
||||
# Note: Also capable of 100MHz-150MHz, but we can't model gaps.
|
||||
Radio("R-800L1", (RadioRange(MHz(220), MHz(400), kHz(25), Modulation.AM),)),
|
||||
@@ -263,7 +268,7 @@ RADIOS: List[Radio] = [
|
||||
RadioRange(
|
||||
MHz(225),
|
||||
MHz(400),
|
||||
MHz(1),
|
||||
kHz(25),
|
||||
Modulation.AM,
|
||||
frozenset((MHz(243),)),
|
||||
),
|
||||
@@ -275,28 +280,28 @@ RADIOS: List[Radio] = [
|
||||
RadioRange(
|
||||
MHz(30),
|
||||
MHz(88),
|
||||
MHz(1),
|
||||
kHz(25),
|
||||
Modulation.FM,
|
||||
frozenset((MHz(40, 500),)),
|
||||
),
|
||||
RadioRange(
|
||||
MHz(108),
|
||||
MHz(156),
|
||||
MHz(1),
|
||||
kHz(25),
|
||||
Modulation.AM,
|
||||
frozenset((MHz(121, 500),)),
|
||||
),
|
||||
RadioRange(
|
||||
MHz(156),
|
||||
MHz(174),
|
||||
MHz(1),
|
||||
kHz(25),
|
||||
Modulation.FM,
|
||||
frozenset((MHz(156, 800),)),
|
||||
),
|
||||
RadioRange(
|
||||
MHz(225),
|
||||
MHz(400),
|
||||
MHz(1),
|
||||
kHz(25),
|
||||
Modulation.AM, # Actually AM/FM, but we can't represent that.
|
||||
frozenset((MHz(243),)),
|
||||
),
|
||||
@@ -323,6 +328,13 @@ def get_radio(name: str) -> Radio:
|
||||
raise KeyError(f"Unknown radio: {name}")
|
||||
|
||||
|
||||
def random_frequency(radio: Radio) -> RadioFrequency:
|
||||
range: RadioRange = radio.ranges[0]
|
||||
delta = round(random.random() * (range.maximum.hertz - range.minimum.hertz))
|
||||
delta -= delta % range.step.hertz
|
||||
return RadioFrequency(range.minimum.hertz + delta)
|
||||
|
||||
|
||||
class RadioRegistry:
|
||||
"""Manages allocation of radio channels.
|
||||
|
||||
@@ -368,9 +380,8 @@ class RadioRegistry:
|
||||
OutOfChannelsError: All channels compatible with the given radio are
|
||||
already allocated.
|
||||
"""
|
||||
allocator = self.radio_allocators[radio]
|
||||
try:
|
||||
while (channel := next(allocator)) in self.allocated_channels:
|
||||
while (channel := random_frequency(radio)) in self.allocated_channels:
|
||||
pass
|
||||
self.reserve(channel)
|
||||
return channel
|
||||
|
||||
@@ -54,8 +54,8 @@ class TacanChannel:
|
||||
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")
|
||||
if not (0 < number <= 126):
|
||||
raise ValueError("TACAN channel cannot be 0 or larger than 126")
|
||||
return TacanChannel(number, TacanBand(match.group(2)))
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ from typing import (
|
||||
TYPE_CHECKING,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
@@ -73,6 +74,10 @@ from ..data.units import UnitClass
|
||||
from ..db import Database
|
||||
from ..dcs.aircrafttype import AircraftType
|
||||
from ..dcs.groundunittype import GroundUnitType
|
||||
from ..radio.ICLSContainer import ICLSContainer
|
||||
from ..radio.Link4Container import Link4Container
|
||||
from ..radio.RadioFrequencyContainer import RadioFrequencyContainer
|
||||
from ..radio.TacanContainer import TacanContainer
|
||||
from ..utils import nautical_miles
|
||||
from ..weather import Conditions
|
||||
|
||||
@@ -305,7 +310,7 @@ class ControlPointStatus(IntEnum):
|
||||
Destroyed = auto()
|
||||
|
||||
|
||||
StartingPosition = ShipGroup | StaticGroup | Airport | Point
|
||||
StartingPosition = Union[ShipGroup, StaticGroup, Airport, Point]
|
||||
|
||||
|
||||
class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
||||
@@ -1161,7 +1166,9 @@ class Airfield(ControlPoint):
|
||||
return ControlPointStatus.Functional
|
||||
|
||||
|
||||
class NavalControlPoint(ControlPoint, ABC):
|
||||
class NavalControlPoint(
|
||||
ControlPoint, ABC, Link4Container, TacanContainer, ICLSContainer
|
||||
):
|
||||
@property
|
||||
def is_fleet(self) -> bool:
|
||||
return True
|
||||
@@ -1392,7 +1399,7 @@ class OffMapSpawn(ControlPoint):
|
||||
return ControlPointStatus.Functional
|
||||
|
||||
|
||||
class Fob(ControlPoint):
|
||||
class Fob(ControlPoint, RadioFrequencyContainer):
|
||||
def __init__(
|
||||
self, name: str, at: Point, theater: ConflictTheater, starts_blue: bool
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user