mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Use TACAN channels more selectively, use pytest (#1554)
* Use TACAN channels more selectively * Increase tacan range to 126 * Use pytest and add workflow * Skip faction tests due to outdated test data * Run mypy on tests directory also * Use iterators for bands AND usages, add tests
This commit is contained in:
@@ -92,7 +92,7 @@ from gen.flights.flight import (
|
||||
from gen.lasercoderegistry import LaserCodeRegistry
|
||||
from gen.radios import RadioFrequency, RadioRegistry
|
||||
from gen.runways import RunwayData
|
||||
from gen.tacan import TacanBand, TacanRegistry
|
||||
from gen.tacan import TacanBand, TacanRegistry, TacanUsage
|
||||
from .airsupport import AirSupport, AwacsInfo, TankerInfo
|
||||
from .callsigns import callsign_for_support_unit
|
||||
from .flights.flightplan import (
|
||||
@@ -435,7 +435,7 @@ class AircraftConflictGenerator:
|
||||
if isinstance(flight.flight_plan, RefuelingFlightPlan):
|
||||
callsign = callsign_for_support_unit(group)
|
||||
|
||||
tacan = self.tacan_registy.alloc_for_band(TacanBand.Y)
|
||||
tacan = self.tacan_registy.alloc_for_band(TacanBand.Y, TacanUsage.AirToAir)
|
||||
self.air_support.tankers.append(
|
||||
TankerInfo(
|
||||
group_name=str(group.name),
|
||||
|
||||
@@ -22,7 +22,7 @@ from .conflictgen import Conflict
|
||||
from .flights.ai_flight_planner_db import AEWC_CAPABLE
|
||||
from .naming import namegen
|
||||
from .radios import RadioRegistry
|
||||
from .tacan import TacanBand, TacanRegistry
|
||||
from .tacan import TacanBand, TacanRegistry, TacanUsage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@@ -89,7 +89,9 @@ class AirSupportConflictGenerator:
|
||||
# TODO: Make loiter altitude a property of the unit type.
|
||||
alt, airspeed = self._get_tanker_params(tanker_unit_type.dcs_unit_type)
|
||||
freq = self.radio_registry.alloc_uhf()
|
||||
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y)
|
||||
tacan = self.tacan_registry.alloc_for_band(
|
||||
TacanBand.Y, TacanUsage.AirToAir
|
||||
)
|
||||
tanker_heading = Heading.from_degrees(
|
||||
self.conflict.red_cp.position.heading_between_point(
|
||||
self.conflict.blue_cp.position
|
||||
|
||||
@@ -58,7 +58,7 @@ from game.unitmap import UnitMap
|
||||
from game.utils import Heading, feet, knots, mps
|
||||
from .radios import RadioFrequency, RadioRegistry
|
||||
from .runways import RunwayData
|
||||
from .tacan import TacanBand, TacanChannel, TacanRegistry
|
||||
from .tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@@ -377,7 +377,9 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator[GenericCarrierGroundO
|
||||
for unit in group.units[1:]:
|
||||
ship_group.add_unit(self.create_ship(unit, atc))
|
||||
|
||||
tacan = self.tacan_registry.alloc_for_band(TacanBand.X)
|
||||
tacan = self.tacan_registry.alloc_for_band(
|
||||
TacanBand.X, TacanUsage.TransmitReceive
|
||||
)
|
||||
tacan_callsign = self.tacan_callsign()
|
||||
icls = next(self.icls_alloc)
|
||||
|
||||
|
||||
56
gen/tacan.py
56
gen/tacan.py
@@ -4,13 +4,37 @@ from enum import Enum
|
||||
from typing import Dict, Iterator, Set
|
||||
|
||||
|
||||
class TacanUsage(Enum):
|
||||
TransmitReceive = "transmit receive"
|
||||
AirToAir = "air to air"
|
||||
|
||||
|
||||
class TacanBand(Enum):
|
||||
X = "X"
|
||||
Y = "Y"
|
||||
|
||||
def range(self) -> Iterator["TacanChannel"]:
|
||||
"""Returns an iterator over the channels in this band."""
|
||||
return (TacanChannel(x, self) for x in range(1, 100))
|
||||
return (TacanChannel(x, self) for x in range(1, 126 + 1))
|
||||
|
||||
def valid_channels(self, usage: TacanUsage) -> Iterator["TacanChannel"]:
|
||||
for x in self.range():
|
||||
if x.number not in UNAVAILABLE[usage][self]:
|
||||
yield x
|
||||
|
||||
|
||||
# Avoid certain TACAN channels for various reasons
|
||||
# https://forums.eagle.ru/topic/276390-datalink-issue/
|
||||
UNAVAILABLE = {
|
||||
TacanUsage.TransmitReceive: {
|
||||
TacanBand.X: set(range(2, 30 + 1)) | set(range(47, 63 + 1)),
|
||||
TacanBand.Y: set(range(2, 30 + 1)) | set(range(64, 92 + 1)),
|
||||
},
|
||||
TacanUsage.AirToAir: {
|
||||
TacanBand.X: set(range(1, 36 + 1)) | set(range(64, 99 + 1)),
|
||||
TacanBand.Y: set(range(1, 36 + 1)) | set(range(64, 99 + 1)),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -36,30 +60,42 @@ class TacanChannelInUseError(RuntimeError):
|
||||
super().__init__(f"{channel} is already in use")
|
||||
|
||||
|
||||
class TacanChannelForbiddenError(RuntimeError):
|
||||
"""Raised when attempting to reserve a, for technical reasons, forbidden channel."""
|
||||
|
||||
def __init__(self, channel: TacanChannel) -> None:
|
||||
super().__init__(f"{channel} is forbidden")
|
||||
|
||||
|
||||
class TacanRegistry:
|
||||
"""Manages allocation of TACAN channels."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.allocated_channels: Set[TacanChannel] = set()
|
||||
self.band_allocators: Dict[TacanBand, Iterator[TacanChannel]] = {}
|
||||
self.allocators: Dict[TacanBand, Dict[TacanUsage, Iterator[TacanChannel]]] = {}
|
||||
|
||||
for band in TacanBand:
|
||||
self.band_allocators[band] = band.range()
|
||||
self.allocators[band] = {}
|
||||
for usage in TacanUsage:
|
||||
self.allocators[band][usage] = band.valid_channels(usage)
|
||||
|
||||
def alloc_for_band(self, band: TacanBand) -> TacanChannel:
|
||||
def alloc_for_band(
|
||||
self, band: TacanBand, intended_usage: TacanUsage
|
||||
) -> TacanChannel:
|
||||
"""Allocates a TACAN channel in the given band.
|
||||
|
||||
Args:
|
||||
band: The TACAN band to allocate a channel for.
|
||||
intended_usage: What the caller intends to use the tacan channel for.
|
||||
|
||||
Returns:
|
||||
A TACAN channel in the given band.
|
||||
|
||||
Raises:
|
||||
OutOfChannelsError: All channels compatible with the given radio are
|
||||
OutOfTacanChannelsError: All channels compatible with the given radio are
|
||||
already allocated.
|
||||
"""
|
||||
allocator = self.band_allocators[band]
|
||||
allocator = self.allocators[band][intended_usage]
|
||||
try:
|
||||
while (channel := next(allocator)) in self.allocated_channels:
|
||||
pass
|
||||
@@ -67,17 +103,21 @@ class TacanRegistry:
|
||||
except StopIteration:
|
||||
raise OutOfTacanChannelsError(band)
|
||||
|
||||
def reserve(self, channel: TacanChannel) -> None:
|
||||
def reserve(self, channel: TacanChannel, intended_usage: TacanUsage) -> None:
|
||||
"""Reserves the given channel.
|
||||
|
||||
Reserving a channel ensures that it will not be allocated in the future.
|
||||
|
||||
Args:
|
||||
channel: The channel to reserve.
|
||||
intended_usage: What the caller intends to use the tacan channel for.
|
||||
|
||||
Raises:
|
||||
ChannelInUseError: The given frequency is already in use.
|
||||
TacanChannelInUseError: The given channel is already in use.
|
||||
TacanChannelForbiddenError: The given channel is forbidden.
|
||||
"""
|
||||
if channel.number in UNAVAILABLE[intended_usage][channel.band]:
|
||||
raise TacanChannelForbiddenError(channel)
|
||||
if channel in self.allocated_channels:
|
||||
raise TacanChannelInUseError(channel)
|
||||
self.allocated_channels.add(channel)
|
||||
|
||||
Reference in New Issue
Block a user