From ae2707eed70ba1d27b91706ecf5a5afbd875d15a Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 2 Oct 2023 21:02:24 -0700 Subject: [PATCH] Add per-aircraft datalink configuration. --- game/dcs/aircrafttype.py | 6 ++ game/dcs/datalinkconfig.py | 18 ++++++ .../aircraft/aircraftgenerator.py | 6 +- .../aircraft/flightgroupconfigurator.py | 7 ++- resources/units/aircraft/F-16C_50.yaml | 3 + resources/units/aircraft/FA-18C_hornet.yaml | 29 +++++----- tests/dcs/test_datalinkconfig.py | 55 +++++++++++++++++++ 7 files changed, 107 insertions(+), 17 deletions(-) create mode 100644 game/dcs/datalinkconfig.py create mode 100644 tests/dcs/test_datalinkconfig.py diff --git a/game/dcs/aircrafttype.py b/game/dcs/aircrafttype.py index c454167cd..3551c7fde 100644 --- a/game/dcs/aircrafttype.py +++ b/game/dcs/aircrafttype.py @@ -13,6 +13,7 @@ from dcs.unittype import FlyingType from game.data.units import UnitClass +from game.dcs.datalinkconfig import DatalinkConfig from game.dcs.lasercodeconfig import LaserCodeConfig from game.dcs.unittype import UnitType from game.radio.channels import ( @@ -206,6 +207,7 @@ class AircraftType(UnitType[Type[FlyingType]]): has_built_in_target_pod: bool laser_code_configs: list[LaserCodeConfig] + datalink_config: DatalinkConfig | None use_f15e_waypoint_names: bool @@ -347,6 +349,9 @@ def capable_of(self, task: FlightType) -> bool: def task_priority(self, task: FlightType) -> int: return self.task_priorities[task] + def should_alloc_stn(self) -> bool: + return self.datalink_config is not None + def __setstate__(self, state: dict[str, Any]) -> None: # Update any existing models with new data on load. updated = AircraftType.named(state["variant_id"]) @@ -504,6 +509,7 @@ def _variant_from_dict( laser_code_configs=[ LaserCodeConfig.from_yaml(d) for d in data.get("laser_codes", []) ], + datalink_config=DatalinkConfig.from_data(data), use_f15e_waypoint_names=data.get("use_f15e_waypoint_names", False), ) diff --git a/game/dcs/datalinkconfig.py b/game/dcs/datalinkconfig.py new file mode 100644 index 000000000..4783cdc10 --- /dev/null +++ b/game/dcs/datalinkconfig.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + + +@dataclass(frozen=True) +class DatalinkConfig: + max_team_members: int + max_donors: int + + @staticmethod + def from_data(data: dict[str, Any]) -> DatalinkConfig | None: + try: + data = data["datalink"] + except KeyError: + return None + return DatalinkConfig(int(data["max_team_members"]), int(data["max_donors"])) diff --git a/game/missiongenerator/aircraft/aircraftgenerator.py b/game/missiongenerator/aircraft/aircraftgenerator.py index 4014b868f..8aaba9774 100644 --- a/game/missiongenerator/aircraft/aircraftgenerator.py +++ b/game/missiongenerator/aircraft/aircraftgenerator.py @@ -172,6 +172,10 @@ def create_and_configure_flight( flight, country, self.mission, self.helipads ).create_flight_group() + stn_prefix: SourceTrackNumberPrefix | None = None + if flight.squadron.aircraft.should_alloc_stn(): + stn_prefix = SourceTrackNumberPrefix(next(self.stn_prefix_allocator)) + briefing_data = FlightGroupConfigurator( flight, group, @@ -180,7 +184,7 @@ def create_and_configure_flight( self.time, self.radio_registry, self.tacan_registy, - SourceTrackNumberPrefix(next(self.stn_prefix_allocator)), + stn_prefix, self.mission_data, dynamic_runways, self.use_client, diff --git a/game/missiongenerator/aircraft/flightgroupconfigurator.py b/game/missiongenerator/aircraft/flightgroupconfigurator.py index 5f0b9b1e8..a5ef0f22f 100644 --- a/game/missiongenerator/aircraft/flightgroupconfigurator.py +++ b/game/missiongenerator/aircraft/flightgroupconfigurator.py @@ -42,7 +42,7 @@ def __init__( time: datetime, radio_registry: RadioRegistry, tacan_registry: TacanRegistry, - stn_prefix: SourceTrackNumberPrefix, + stn_prefix: SourceTrackNumberPrefix | None, mission_data: MissionData, dynamic_runways: dict[str, RunwayData], use_client: bool, @@ -70,12 +70,13 @@ def configure(self) -> FlightData: flight_channel = self.setup_radios() laser_codes: list[Optional[int]] = [] - stns = [] + stns: list[SourceTrackNumber] = [] for idx, (unit, member) in enumerate( zip(self.group.units, self.flight.iter_members()) ): self.configure_flight_member(unit, member, laser_codes) - stns.append(SourceTrackNumber(self.stn_prefix, idx)) + if self.stn_prefix is not None: + stns.append(SourceTrackNumber(self.stn_prefix, idx)) divert = None if self.flight.divert is not None: diff --git a/resources/units/aircraft/F-16C_50.yaml b/resources/units/aircraft/F-16C_50.yaml index 27d0aee09..f1d6753e6 100644 --- a/resources/units/aircraft/F-16C_50.yaml +++ b/resources/units/aircraft/F-16C_50.yaml @@ -78,3 +78,6 @@ laser_codes: digit: 1 - id: LaserCode1 digit: 0 +datalink: + max_team_members: 8 + max_donors: 4 diff --git a/resources/units/aircraft/FA-18C_hornet.yaml b/resources/units/aircraft/FA-18C_hornet.yaml index d774eb428..6e05475fd 100644 --- a/resources/units/aircraft/FA-18C_hornet.yaml +++ b/resources/units/aircraft/FA-18C_hornet.yaml @@ -1,21 +1,21 @@ carrier_capable: true -description: - 'The F/A-18C Hornet is twin engine, supersonic fighter that is flown - by a single pilot in a "glass cockpit". It combines extreme maneuverability , a - deadly arsenal of weapons, and the ability to operate from an aircraft carrier. - Operated by several nations, this multi-role fighter has been instrumental in conflicts - from 1986 to today. +description: 'The F/A-18C Hornet is twin engine, supersonic fighter that is + flown by a single pilot in a "glass cockpit". It combines extreme + maneuverability , a deadly arsenal of weapons, and the ability to operate from + an aircraft carrier. Operated by several nations, this multi-role fighter has + been instrumental in conflicts from 1986 to today. - The Hornet is equipped with a large suite of sensors that includes a radar, targeting - pod, and a helmet mounted sight. In addition to its internal 20mm cannon, the Hornet - can be armed with a large assortment of unguided bombs and rockets, laser and GPS-guided - bombs, air-to-surface missiles of all sorts, and both radar and infrared-guided - air-to-air missiles. + The Hornet is equipped with a large suite of sensors that includes a radar, + targeting pod, and a helmet mounted sight. In addition to its internal 20mm + cannon, the Hornet can be armed with a large assortment of unguided bombs and + rockets, laser and GPS-guided bombs, air-to-surface missiles of all sorts, and + both radar and infrared-guided air-to-air missiles. - The Hornet is also known for its extreme, slow-speed maneuverability in a dogfight. - Although incredibly deadly, the Hornet is also a very easy aircraft to fly.' + The Hornet is also known for its extreme, slow-speed maneuverability in a + dogfight. Although incredibly deadly, the Hornet is also a very easy aircraft + to fly.' introduced: 1987 manufacturer: McDonnell Douglas origin: USA @@ -68,3 +68,6 @@ tasks: SEAD Escort: 160 Strike: 600 TARCAP: 450 +datalink: + max_team_members: 4 + max_donors: 8 diff --git a/tests/dcs/test_datalinkconfig.py b/tests/dcs/test_datalinkconfig.py new file mode 100644 index 000000000..d971d9831 --- /dev/null +++ b/tests/dcs/test_datalinkconfig.py @@ -0,0 +1,55 @@ +import pytest + +from game.dcs.datalinkconfig import DatalinkConfig + + +def test_from_data_no_data() -> None: + assert DatalinkConfig.from_data({}) is None + + +def test_from_data_bad_data() -> None: + with pytest.raises(KeyError): + DatalinkConfig.from_data( + { + "datalink": { + "max_team_members": 4, + } + } + ) + with pytest.raises(KeyError): + DatalinkConfig.from_data( + { + "datalink": { + "max_donors": 4, + } + } + ) + with pytest.raises(ValueError): + DatalinkConfig.from_data( + { + "datalink": { + "max_team_members": 4, + "max_donors": "a", + } + } + ) + with pytest.raises(ValueError): + DatalinkConfig.from_data( + { + "datalink": { + "max_team_members": "a", + "max_donors": 4, + } + } + ) + + +def test_from_data() -> None: + assert DatalinkConfig.from_data( + { + "datalink": { + "max_team_members": 4, + "max_donors": 8, + } + } + ) == DatalinkConfig(max_team_members=4, max_donors=8)