From edef0e995effbc1845964bcd3fa9a2d652cd5c4e Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Thu, 18 Apr 2024 12:32:05 +1000 Subject: [PATCH 01/15] Refactor callsigns.py into a callsigns directory --- game/{callsigns.py => callsigns/callsign.py} | 0 game/missiongenerator/aircraft/flightdata.py | 2 +- game/missiongenerator/aircraft/flightgroupconfigurator.py | 2 +- game/missiongenerator/airsupportgenerator.py | 2 +- game/missiongenerator/flotgenerator.py | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename game/{callsigns.py => callsigns/callsign.py} (100%) diff --git a/game/callsigns.py b/game/callsigns/callsign.py similarity index 100% rename from game/callsigns.py rename to game/callsigns/callsign.py diff --git a/game/missiongenerator/aircraft/flightdata.py b/game/missiongenerator/aircraft/flightdata.py index 6d55813ea..f80ae9882 100644 --- a/game/missiongenerator/aircraft/flightdata.py +++ b/game/missiongenerator/aircraft/flightdata.py @@ -6,7 +6,7 @@ from dcs.flyingunit import FlyingUnit -from game.callsigns import create_group_callsign_from_unit +from game.callsigns.callsign import create_group_callsign_from_unit if TYPE_CHECKING: from game.ato import FlightType, FlightWaypoint, Package diff --git a/game/missiongenerator/aircraft/flightgroupconfigurator.py b/game/missiongenerator/aircraft/flightgroupconfigurator.py index 11bc26ad5..6eacd0c87 100644 --- a/game/missiongenerator/aircraft/flightgroupconfigurator.py +++ b/game/missiongenerator/aircraft/flightgroupconfigurator.py @@ -10,7 +10,7 @@ from dcs.unitgroup import FlyingGroup from game.ato import Flight, FlightType -from game.callsigns import callsign_for_support_unit +from game.callsigns.callsign import callsign_for_support_unit from game.data.weapons import Pylon from game.missiongenerator.logisticsgenerator import LogisticsGenerator from game.missiongenerator.missiondata import AwacsInfo, MissionData, TankerInfo diff --git a/game/missiongenerator/airsupportgenerator.py b/game/missiongenerator/airsupportgenerator.py index 468196d43..10f16a104 100644 --- a/game/missiongenerator/airsupportgenerator.py +++ b/game/missiongenerator/airsupportgenerator.py @@ -16,7 +16,7 @@ from dcs.unittype import UnitType from game.ato import FlightType -from game.callsigns import callsign_for_support_unit +from game.callsigns.callsign import callsign_for_support_unit from game.naming import namegen from game.radio.radios import RadioRegistry from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage diff --git a/game/missiongenerator/flotgenerator.py b/game/missiongenerator/flotgenerator.py index 3be5fc4c1..4c3d5c786 100644 --- a/game/missiongenerator/flotgenerator.py +++ b/game/missiongenerator/flotgenerator.py @@ -27,7 +27,7 @@ from dcs.unit import Skill, Vehicle from dcs.unitgroup import VehicleGroup -from game.callsigns import callsign_for_support_unit +from game.callsigns.callsign import callsign_for_support_unit from game.data.units import UnitClass from game.dcs.aircrafttype import AircraftType from game.dcs.groundunittype import GroundUnitType From 690c12967203371883ceaa7ac51a4d28f87101da Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:28:11 +1000 Subject: [PATCH 02/15] Introduce flight-specific callsigns --- game/ato/flight.py | 4 + game/callsigns/callsigngenerator.py | 170 ++++++++++++++++++ game/coalition.py | 4 + game/commander/packagebuilder.py | 4 + game/commander/packagefulfiller.py | 1 + .../aircraft/flightgroupconfigurator.py | 15 ++ .../windows/mission/flight/QFlightCreator.py | 2 + .../flight/settings/QFlightTypeTaskInfo.py | 10 ++ 8 files changed, 210 insertions(+) create mode 100644 game/callsigns/callsigngenerator.py diff --git a/game/ato/flight.py b/game/ato/flight.py index 86942f4bf..8ece14f2b 100644 --- a/game/ato/flight.py +++ b/game/ato/flight.py @@ -21,6 +21,7 @@ ) if TYPE_CHECKING: + from game.callsigns.callsigngenerator import Callsign from game.dcs.aircrafttype import AircraftType from game.sim.gameupdateevents import GameUpdateEvents from game.sim.simulationresults import SimulationResults @@ -49,6 +50,7 @@ def __init__( custom_name: Optional[str] = None, cargo: Optional[TransferOrder] = None, roster: Optional[FlightRoster] = None, + callsign: Optional[Callsign] = None, ) -> None: self.id = uuid.uuid4() self.package = package @@ -69,6 +71,8 @@ def __init__( # Only used by transport missions. self.cargo = cargo + self.callsign = callsign + # Flight properties that can be set in the mission editor. This is used for # things like HMD selection, ripple quantity, etc. Any values set here will take # the place of the defaults defined by DCS. diff --git a/game/callsigns/callsigngenerator.py b/game/callsigns/callsigngenerator.py new file mode 100644 index 000000000..fa8551f15 --- /dev/null +++ b/game/callsigns/callsigngenerator.py @@ -0,0 +1,170 @@ +from dataclasses import dataclass +from enum import StrEnum +from collections import deque +from typing import Any, List, Optional + +from dcs.country import Country +from dcs.countries import countries_by_name + +from game.ato.flight import Flight +from game.ato.flighttype import FlightType + + +MAX_GROUP_ID = 999 + + +@dataclass(frozen=True) +class Callsign: + name: str + index: int + group_id: int + unit_id: int + + def __post_init__(self) -> None: + if self.group_id < 1 or self.group_id > MAX_GROUP_ID: + raise ValueError( + f"Invalid group ID {self.group_id}. Group IDs have to be between 1 and 999." + ) + if self.unit_id < 1 or self.unit_id > 9: + raise ValueError( + f"Invalid unit ID {self.unit_id}. Unit IDs have to be between 1 and 9." + ) + + def __str__(self) -> str: + return f"{self.name}{self.group_id}{self.unit_id}" + + def group_callsign(self) -> str: + return f"{self.name}-{self.group_id}" + + def pydcs_dict(self) -> dict[Any, Any]: + return { "name": str(self), + 1: self.index, + 2: self.group_id, + 3: self.unit_id } + + +class CallsignCategory(StrEnum): + AIR = "Air" + TANKERS = "Tankers" + AWACS = "AWACS" + GROUND_UNITS = "GroundUnits" + HELIPADS = "Helipad" + GRASS_AIRFIELDS = "GrassAirfield" + + +class GroupIdRegistry: + + def __init__(self, country: Country): + self._names: dict[str, deque[int]] = {} + for category in [ + CallsignCategory.AIR, + CallsignCategory.TANKERS, + CallsignCategory.AWACS, + CallsignCategory.GROUND_UNITS, + CallsignCategory.HELIPADS, + CallsignCategory.GRASS_AIRFIELDS, + ]: + if category in country.callsign: + for name in country.callsign[category]: + self._names[name] = deque() + self.reset() + + def reset(self) -> None: + for name in self._names: + self._names[name] = deque() + for i in range( + MAX_GROUP_ID, 0, -1 + ): # Put group IDs on FIFO queue so 1 gets popped first + self._names[name].appendleft(i) + + def alloc_group_id(self, name: str) -> int: + return self._names[name].popleft() + + def release_group_id(self, callsign: Callsign) -> None: + self._names[callsign.name].appendleft(callsign.group_id) + + +class RoundRobinNameAllocator: + + def __init__(self, names: List[str]): + self.names = names + self._index = 0 + + def allocate(self) -> tuple[str, int]: + this_index = self._index + if this_index == len(self.names) - 1: + self._index = 0 + else: + self._index += 1 + return self.names[this_index], this_index + 1 + + +class FlightTypeNameAllocator: + def __init__(self, names: List[str]): + self.names = names + + def allocate(self, flight: Flight) -> tuple[str, int]: + index = self.FLIGHT_TYPE_LOOKUP.get(flight.flight_type, 0) + return self.names[index], index + 1 + + FLIGHT_TYPE_LOOKUP: dict[FlightType, int] = { + FlightType.TARCAP: 1, + FlightType.BARCAP: 1, + FlightType.INTERCEPTION: 1, + FlightType.SWEEP: 1, + FlightType.CAS: 2, + FlightType.ANTISHIP: 2, + FlightType.BAI: 2, + FlightType.STRIKE: 3, + FlightType.OCA_RUNWAY: 3, + FlightType.OCA_AIRCRAFT: 3, + FlightType.SEAD: 4, + FlightType.DEAD: 4, + FlightType.ESCORT: 5, + FlightType.AIR_ASSAULT: 6, + FlightType.TRANSPORT: 7, + FlightType.FERRY: 7, + } + + +class FlightCallsignGenerator: + """Generate callsign for lead unit in a group""" + + def __init__(self, country: str): + self._country = countries_by_name[country]() + + self._group_id_registry = GroupIdRegistry(self._country) + self._awacs_name_allocator = None + self._tankers_name_allocator = None + + if CallsignCategory.AWACS in self._country.callsign: + self._awacs_name_allocator = RoundRobinNameAllocator( + self._country.callsign[CallsignCategory.AWACS] + ) + if CallsignCategory.TANKERS in self._country.callsign: + self._tankers_name_allocator = RoundRobinNameAllocator( + self._country.callsign[CallsignCategory.TANKERS] + ) + self._air_name_allocator = FlightTypeNameAllocator( + self._country.callsign[CallsignCategory.AIR] + ) + + def reset(self) -> None: + self._group_id_registry.reset() + + def alloc_callsign(self, flight: Flight) -> Callsign: + if flight.flight_type == FlightType.AEWC: + if self._awacs_name_allocator is None: + raise ValueError(f"{self._country.name} does not have AWACs callsigns") + name, index = self._awacs_name_allocator.allocate() + elif flight.flight_type == FlightType.REFUELING: + if self._tankers_name_allocator is None: + raise ValueError(f"{self._country.name} does not have tanker callsigns") + name, index = self._tankers_name_allocator.allocate() + else: + name, index = self._air_name_allocator.allocate(flight) + group_id = self._group_id_registry.alloc_group_id(name) + return Callsign(name, index, group_id, 1) + + def release_callsign(self, callsign: Callsign) -> None: + self._group_id_registry.release_group_id(callsign) diff --git a/game/coalition.py b/game/coalition.py index f8abad926..f4eb2ae2b 100644 --- a/game/coalition.py +++ b/game/coalition.py @@ -7,6 +7,7 @@ from game.armedforces.armedforces import ArmedForces from game.ato.airtaaskingorder import AirTaskingOrder +from game.callsigns.callsigngenerator import FlightCallsignGenerator from game.campaignloader.defaultsquadronassigner import DefaultSquadronAssigner from game.commander import TheaterCommander from game.commander.missionscheduler import MissionScheduler @@ -46,6 +47,7 @@ def __init__( self.air_wing = AirWing(player, game, self.faction) self.armed_forces = ArmedForces(self.faction) self.transfers = PendingTransfers(game, player) + self.callsign_generator = FlightCallsignGenerator(faction.country) # Late initialized because the two coalitions in the game are mutually # dependent, so must be both constructed before this property can be set. @@ -163,6 +165,8 @@ def end_turn(self) -> None: # is handled correctly. self.transfers.perform_transfers() + self.callsign_generator.reset() + def preinit_turn_0(self) -> None: """Runs final Coalition initialization. diff --git a/game/commander/packagebuilder.py b/game/commander/packagebuilder.py index 2aea54caf..ad20eb72e 100644 --- a/game/commander/packagebuilder.py +++ b/game/commander/packagebuilder.py @@ -2,6 +2,7 @@ from typing import Optional, TYPE_CHECKING +from game.callsigns.callsigngenerator import FlightCallsignGenerator from game.theater import ControlPoint, MissionTarget, OffMapSpawn from game.utils import nautical_miles from ..ato.flight import Flight @@ -26,6 +27,7 @@ def __init__( closest_airfields: ClosestAirfields, air_wing: AirWing, laser_code_registry: LaserCodeRegistry, + callsign_generator: FlightCallsignGenerator, flight_db: Database[Flight], is_player: bool, package_country: str, @@ -38,6 +40,7 @@ def __init__( self.package = Package(location, flight_db, auto_asap=asap) self.air_wing = air_wing self.laser_code_registry = laser_code_registry + self.callsign_generator = callsign_generator self.start_type = start_type def plan_flight(self, plan: ProposedFlight) -> bool: @@ -71,6 +74,7 @@ def plan_flight(self, plan: ProposedFlight) -> bool: member.assign_tgp_laser_code( self.laser_code_registry.alloc_laser_code() ) + flight.callsign = self.callsign_generator.alloc_callsign(flight) self.package.add_flight(flight) return True diff --git a/game/commander/packagefulfiller.py b/game/commander/packagefulfiller.py index 7e66eafa7..b18f9dd82 100644 --- a/game/commander/packagefulfiller.py +++ b/game/commander/packagefulfiller.py @@ -142,6 +142,7 @@ def plan_mission( ObjectiveDistanceCache.get_closest_airfields(mission.location), self.air_wing, self.coalition.laser_code_registry, + self.coalition.callsign_generator, self.flight_db, self.is_player, self.coalition.country_name, diff --git a/game/missiongenerator/aircraft/flightgroupconfigurator.py b/game/missiongenerator/aircraft/flightgroupconfigurator.py index 6eacd0c87..942d0e4eb 100644 --- a/game/missiongenerator/aircraft/flightgroupconfigurator.py +++ b/game/missiongenerator/aircraft/flightgroupconfigurator.py @@ -11,6 +11,7 @@ from game.ato import Flight, FlightType from game.callsigns.callsign import callsign_for_support_unit +from game.callsigns.callsigngenerator import Callsign, FlightCallsignGenerator from game.data.weapons import Pylon from game.missiongenerator.logisticsgenerator import LogisticsGenerator from game.missiongenerator.missiondata import AwacsInfo, MissionData, TankerInfo @@ -115,6 +116,8 @@ def configure(self) -> FlightData: self.flight.flight_plan.waypoints, ) + self.set_callsigns() + return FlightData( package=self.flight.package, aircraft_type=self.flight.unit_type, @@ -269,3 +272,15 @@ def setup_fuel(self) -> None: # our own tracking, so undo that. # https://github.com/pydcs/dcs/commit/303a81a8e0c778599fe136dd22cb2ae8123639a6 unit.fuel = self.flight.unit_type.dcs_unit_type.fuel_max + + def set_callsigns(self) -> None: + if self.flight.callsign is None: + return + for unit_index, unit in enumerate(self.group.units): + unit_callsign = Callsign( + self.flight.callsign.name, + self.flight.callsign.index, + self.flight.callsign.group_id, + unit_index + 1, + ) + unit.callsign_dict = unit_callsign.pydcs_dict() diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index 641a65f85..20207b071 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -202,6 +202,8 @@ def create_flight(self) -> None: self.game.laser_code_registry.alloc_laser_code() ) + flight.callsign = self.game.blue.callsign_generator.alloc_callsign(flight) + # noinspection PyUnresolvedReferences self.created.emit(flight) self.accept() diff --git a/qt_ui/windows/mission/flight/settings/QFlightTypeTaskInfo.py b/qt_ui/windows/mission/flight/settings/QFlightTypeTaskInfo.py index 4aa1fbe9a..cf3118b54 100644 --- a/qt_ui/windows/mission/flight/settings/QFlightTypeTaskInfo.py +++ b/qt_ui/windows/mission/flight/settings/QFlightTypeTaskInfo.py @@ -18,9 +18,19 @@ def __init__(self, flight): self.task_type = QLabel(str(flight.flight_type)) self.task_type.setProperty("style", flight.flight_type.name) + self.callsign_label = QLabel("Callsign:") + if flight.callsign is not None: + callsign = flight.callsign.group_callsign() + else: + callsign = "" + self.callsign = QLabel(callsign) + layout.addWidget(self.aircraft_icon, 0, 0) layout.addWidget(self.task, 1, 0) layout.addWidget(self.task_type, 1, 1) + layout.addWidget(self.callsign_label, 2, 0) + layout.addWidget(self.callsign, 2, 1) + self.setLayout(layout) From 347267c6de3032d07c84bdd43ce9e796d5340d72 Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Thu, 18 Apr 2024 17:15:16 +1000 Subject: [PATCH 03/15] Release callsigns when deleting flights / packages --- qt_ui/models.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qt_ui/models.py b/qt_ui/models.py index 58266f0da..a609a2904 100644 --- a/qt_ui/models.py +++ b/qt_ui/models.py @@ -1,4 +1,5 @@ """Qt data models for game objects.""" + from __future__ import annotations import datetime @@ -176,6 +177,9 @@ def delete_flight(self, flight: Flight) -> None: """Removes the given flight from the package.""" with self.game_model.sim_controller.paused_sim(): index = self.package.flights.index(flight) + self.game_model.game.blue.callsign_generator.release_callsign( + flight.callsign + ) self.beginRemoveRows(QModelIndex(), index, index) self.package.remove_flight(flight) self.endRemoveRows() @@ -301,6 +305,10 @@ def _delete_package(self, package: Package) -> None: self.package_models.release(package) index = self.ato.packages.index(package) self.beginRemoveRows(QModelIndex(), index, index) + for flight in package.flights: + self.game_model.game.blue.callsign_generator.release_callsign( + flight.callsign + ) self.ato.remove_package(package) self.endRemoveRows() # noinspection PyUnresolvedReferences From b08f43a98ee5f0946edfea20c6a4c3c7526badbb Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Sat, 20 Apr 2024 17:06:38 +1000 Subject: [PATCH 04/15] Preprare for build 11.1 --- changelog.md | 7 +++---- docs/conf.py | 4 ++-- game/version.py | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index fbe9927cc..651fdca6e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,8 +1,6 @@ -# 12.0.0 +# 11.1.0 -Saves from 11.x are not compatible with 12.0.0. - -## Features/Improvements +Saves from 11.0.0 are compatible with 11.1.0. ## Fixes @@ -10,6 +8,7 @@ Saves from 11.x are not compatible with 12.0.0. * **[Campaign]** Fixed error where frontline units are not re-deployed when multiple control points were captured in one turn or when control points are captured "out of order" using air-assault missions. * **[Cheat Menu]** Re-deploy frontline units when using cheats to capture control points, so that cheats behave the same way as capturing a control point in-mission. * **[Flight Planning]** Theater refuelling flight plans (those not tied to a particular package) will remain on station for a longer period, specifically the desired mission duration + 30 minutes. By default, this increases the on-station time from 1 hour to 1.5 hours. +* **[Mission Generation]** Patched bug where Liberation crashed when aborting a turn when Fighter Sweep missions were planned. * **[UI]** Naval control points (carriers, LHAs) can no longer be moved onto land. diff --git a/docs/conf.py b/docs/conf.py index 7f220b180..acd47cae6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,9 +7,9 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = "DCS Liberation" -copyright = "2023, DCS Liberation Team" +copyright = "2024, DCS Liberation Team" author = "DCS Liberation Team" -release = "12.0.0" +release = "11.1.0" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/game/version.py b/game/version.py index c100d9bb1..512bbbb04 100644 --- a/game/version.py +++ b/game/version.py @@ -1,8 +1,8 @@ from pathlib import Path -MAJOR_VERSION = 12 -MINOR_VERSION = 0 +MAJOR_VERSION = 11 +MINOR_VERSION = 1 MICRO_VERSION = 0 VERSION_NUMBER = ".".join(str(v) for v in (MAJOR_VERSION, MINOR_VERSION, MICRO_VERSION)) From 4e650ee347e0e407db1b8144a1be81a99cf6a40c Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Thu, 25 Apr 2024 22:19:22 +1000 Subject: [PATCH 05/15] Simplify interface to Callsign class --- game/callsigns/callsigngenerator.py | 74 +++++++++---------- .../aircraft/flightgroupconfigurator.py | 3 +- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/game/callsigns/callsigngenerator.py b/game/callsigns/callsigngenerator.py index fa8551f15..29528fd11 100644 --- a/game/callsigns/callsigngenerator.py +++ b/game/callsigns/callsigngenerator.py @@ -13,12 +13,20 @@ MAX_GROUP_ID = 999 +class CallsignCategory(StrEnum): + AIR = "Air" + TANKERS = "Tankers" + AWACS = "AWACS" + GROUND_UNITS = "GroundUnits" + HELIPADS = "Helipad" + GRASS_AIRFIELDS = "GrassAirfield" + + @dataclass(frozen=True) class Callsign: - name: str - index: int - group_id: int - unit_id: int + name: str # Callsign name e.g. "Enfield" + group_id: int # ID of the group e.g. 2 in Enfield-2-3 + unit_id: int # ID of the unit e.g. 3 in Enfield-2-3 def __post_init__(self) -> None: if self.group_id < 1 or self.group_id > MAX_GROUP_ID: @@ -32,38 +40,30 @@ def __post_init__(self) -> None: def __str__(self) -> str: return f"{self.name}{self.group_id}{self.unit_id}" - + def group_callsign(self) -> str: return f"{self.name}-{self.group_id}" - - def pydcs_dict(self) -> dict[Any, Any]: - return { "name": str(self), - 1: self.index, - 2: self.group_id, - 3: self.unit_id } - -class CallsignCategory(StrEnum): - AIR = "Air" - TANKERS = "Tankers" - AWACS = "AWACS" - GROUND_UNITS = "GroundUnits" - HELIPADS = "Helipad" - GRASS_AIRFIELDS = "GrassAirfield" + def pydcs_dict(self, country: str) -> dict[Any, Any]: + country_obj = countries_by_name[country]() + for category in CallsignCategory: + if category in country_obj.callsign: + for index, name in enumerate(country_obj.callsign[category]): + if name == self.name: + return { + "name": str(self), + 1: index + 1, + 2: self.group_id, + 3: self.unit_id, + } + raise ValueError(f"Could not find callsign {name} in {country}.") class GroupIdRegistry: def __init__(self, country: Country): self._names: dict[str, deque[int]] = {} - for category in [ - CallsignCategory.AIR, - CallsignCategory.TANKERS, - CallsignCategory.AWACS, - CallsignCategory.GROUND_UNITS, - CallsignCategory.HELIPADS, - CallsignCategory.GRASS_AIRFIELDS, - ]: + for category in CallsignCategory: if category in country.callsign: for name in country.callsign[category]: self._names[name] = deque() @@ -90,22 +90,22 @@ def __init__(self, names: List[str]): self.names = names self._index = 0 - def allocate(self) -> tuple[str, int]: + def allocate(self) -> str: this_index = self._index if this_index == len(self.names) - 1: self._index = 0 else: self._index += 1 - return self.names[this_index], this_index + 1 + return self.names[this_index] class FlightTypeNameAllocator: def __init__(self, names: List[str]): self.names = names - def allocate(self, flight: Flight) -> tuple[str, int]: + def allocate(self, flight: Flight) -> str: index = self.FLIGHT_TYPE_LOOKUP.get(flight.flight_type, 0) - return self.names[index], index + 1 + return self.names[index] FLIGHT_TYPE_LOOKUP: dict[FlightType, int] = { FlightType.TARCAP: 1, @@ -132,11 +132,11 @@ class FlightCallsignGenerator: def __init__(self, country: str): self._country = countries_by_name[country]() - + self._group_id_registry = GroupIdRegistry(self._country) self._awacs_name_allocator = None self._tankers_name_allocator = None - + if CallsignCategory.AWACS in self._country.callsign: self._awacs_name_allocator = RoundRobinNameAllocator( self._country.callsign[CallsignCategory.AWACS] @@ -156,15 +156,15 @@ def alloc_callsign(self, flight: Flight) -> Callsign: if flight.flight_type == FlightType.AEWC: if self._awacs_name_allocator is None: raise ValueError(f"{self._country.name} does not have AWACs callsigns") - name, index = self._awacs_name_allocator.allocate() + name = self._awacs_name_allocator.allocate() elif flight.flight_type == FlightType.REFUELING: if self._tankers_name_allocator is None: raise ValueError(f"{self._country.name} does not have tanker callsigns") - name, index = self._tankers_name_allocator.allocate() + name = self._tankers_name_allocator.allocate() else: - name, index = self._air_name_allocator.allocate(flight) + name = self._air_name_allocator.allocate(flight) group_id = self._group_id_registry.alloc_group_id(name) - return Callsign(name, index, group_id, 1) + return Callsign(name, group_id, 1) def release_callsign(self, callsign: Callsign) -> None: self._group_id_registry.release_group_id(callsign) diff --git a/game/missiongenerator/aircraft/flightgroupconfigurator.py b/game/missiongenerator/aircraft/flightgroupconfigurator.py index 942d0e4eb..574c29252 100644 --- a/game/missiongenerator/aircraft/flightgroupconfigurator.py +++ b/game/missiongenerator/aircraft/flightgroupconfigurator.py @@ -279,8 +279,7 @@ def set_callsigns(self) -> None: for unit_index, unit in enumerate(self.group.units): unit_callsign = Callsign( self.flight.callsign.name, - self.flight.callsign.index, self.flight.callsign.group_id, unit_index + 1, ) - unit.callsign_dict = unit_callsign.pydcs_dict() + unit.callsign_dict = unit_callsign.pydcs_dict(country=self.flight.country) From e852279be24a7501dbaa44e53f6743fd607c9579 Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Thu, 25 Apr 2024 23:05:07 +1000 Subject: [PATCH 06/15] Add unit tests for callsigns --- tests/callsigns/test_callsign_generator.py | 62 ++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/callsigns/test_callsign_generator.py diff --git a/tests/callsigns/test_callsign_generator.py b/tests/callsigns/test_callsign_generator.py new file mode 100644 index 000000000..60908aad2 --- /dev/null +++ b/tests/callsigns/test_callsign_generator.py @@ -0,0 +1,62 @@ +import pytest + +from dcs.countries import countries_by_name + +from game.callsigns.callsigngenerator import ( + Callsign, + GroupIdRegistry, + RoundRobinNameAllocator, +) + + +def test_callsign() -> None: + + valid_callsign = Callsign("Enfield", 2, 3) + assert str(valid_callsign) == "Enfield23" + assert valid_callsign.group_callsign() == "Enfield-2" + assert valid_callsign.pydcs_dict("USA") == {"name": "Enfield23", 1: 1, 2: 2, 3: 3} + + # Invalid callsign, group ID too large. + with pytest.raises(ValueError): + Callsign("Enfield", 1000, 3) + + # Invalid callsign, group ID zero. + with pytest.raises(ValueError): + Callsign("Enfield", 0, 3) + + # Invalid callsign, unit ID zero. + with pytest.raises(ValueError): + Callsign("Enfield", 1, 0) + + # Invalid callsign, unit ID too large. + with pytest.raises(ValueError): + Callsign("Enfield", 1, 11) + + +def test_group_id_registry() -> None: + registry = GroupIdRegistry(countries_by_name["USA"]()) + + # Check registry increments group IDs. + assert registry.alloc_group_id("Enfield") == 1 + assert registry.alloc_group_id("Enfield") == 2 + + # Check allocation on a new name Springfield. + assert registry.alloc_group_id("Springfield") == 1 + + # Check release of Enfield-1. + registry.release_group_id(Callsign("Enfield", 1, 1)) + assert registry.alloc_group_id("Enfield") == 1 + + # Reset and check allocation og Enfield-1 and Springfield-1. + registry.reset() + assert registry.alloc_group_id("Enfield") == 1 + assert registry.alloc_group_id("Springfield") == 1 + + +def test_round_robin_allocator() -> None: + allocator = RoundRobinNameAllocator(["A", "B", "C"]) + + assert allocator.allocate() == "A" + assert allocator.allocate() == "B" + assert allocator.allocate() == "C" + assert allocator.allocate() == "A" From 6a9d7b875d58199412e95b545f8ca128902bf505 Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Sat, 15 Jun 2024 08:11:35 +1000 Subject: [PATCH 07/15] Add eastern callsigns --- game/callsigns/callsigngenerator.py | 52 +++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/game/callsigns/callsigngenerator.py b/game/callsigns/callsigngenerator.py index 29528fd11..bc4098be6 100644 --- a/game/callsigns/callsigngenerator.py +++ b/game/callsigns/callsigngenerator.py @@ -1,3 +1,4 @@ +from abc import ABC from dataclasses import dataclass from enum import StrEnum from collections import deque @@ -20,10 +21,43 @@ class CallsignCategory(StrEnum): GROUND_UNITS = "GroundUnits" HELIPADS = "Helipad" GRASS_AIRFIELDS = "GrassAirfield" + + +class AbstractCallsign(ABC): + + def __str__(self) -> str: + ... + + def group_callsign(self) -> str: + ... + + def pydcs_dict(self, country: str) -> dict[Any, Any]: + ... + + +@dataclass(frozen=True) +class EasternCallsign(AbstractCallsign): + number: int + + def __post_init__(self) -> None: + if self.number < 1 or self.number > MAX_GROUP_ID: + raise ValueError( + f"Invalid callsign {numberd}. Callsigns have to be between 1 and {MAX_GROUP_ID}." + ) + if self.unit_id < 1 or self.unit_id > 9: + raise ValueError( + f"Invalid unit ID {self.unit_id}. Unit IDs have to be between 1 and 9." + ) + + def __str__(self): + return str(self.number) + + def group_callsign(self) -> str: + return str(self.number) @dataclass(frozen=True) -class Callsign: +class Callsign(AbstractCallsign): name: str # Callsign name e.g. "Enfield" group_id: int # ID of the group e.g. 2 in Enfield-2-3 unit_id: int # ID of the unit e.g. 3 in Enfield-2-3 @@ -31,7 +65,7 @@ class Callsign: def __post_init__(self) -> None: if self.group_id < 1 or self.group_id > MAX_GROUP_ID: raise ValueError( - f"Invalid group ID {self.group_id}. Group IDs have to be between 1 and 999." + f"Invalid group ID {self.group_id}. Group IDs have to be between 1 and {MAX_GROUP_ID}." ) if self.unit_id < 1 or self.unit_id > 9: raise ValueError( @@ -82,6 +116,20 @@ def alloc_group_id(self, name: str) -> int: def release_group_id(self, callsign: Callsign) -> None: self._names[callsign.name].appendleft(callsign.group_id) + + +class EasternCallsignRegistry: + + def __init__(self): + self.reset() + + def reset(self) -> None: + self.next = 1 + + def alloc_callsign(self) -> int: + callsign = self.next + self.next += 1 + return callsign class RoundRobinNameAllocator: From 2c4905b1641d7ee120b815d019045c46cebf4043 Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:07:30 +1000 Subject: [PATCH 08/15] Introduce Eastern callsigns --- game/callsigns/callsigngenerator.py | 14 +++++--------- .../mission/flight/settings/QFlightTypeTaskInfo.py | 4 ++-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/game/callsigns/callsigngenerator.py b/game/callsigns/callsigngenerator.py index bc4098be6..7db643e69 100644 --- a/game/callsigns/callsigngenerator.py +++ b/game/callsigns/callsigngenerator.py @@ -28,7 +28,7 @@ class AbstractCallsign(ABC): def __str__(self) -> str: ... - def group_callsign(self) -> str: + def lead_callsign(self) -> str: ... def pydcs_dict(self, country: str) -> dict[Any, Any]: @@ -44,15 +44,11 @@ def __post_init__(self) -> None: raise ValueError( f"Invalid callsign {numberd}. Callsigns have to be between 1 and {MAX_GROUP_ID}." ) - if self.unit_id < 1 or self.unit_id > 9: - raise ValueError( - f"Invalid unit ID {self.unit_id}. Unit IDs have to be between 1 and 9." - ) def __str__(self): - return str(self.number) + return str(self.number).zfill(3) - def group_callsign(self) -> str: + def lead_callsign(self) -> str: return str(self.number) @@ -75,8 +71,8 @@ def __post_init__(self) -> None: def __str__(self) -> str: return f"{self.name}{self.group_id}{self.unit_id}" - def group_callsign(self) -> str: - return f"{self.name}-{self.group_id}" + def lead_callsign(self) -> str: + return f"{self.name}-{self.group_id}-1" def pydcs_dict(self, country: str) -> dict[Any, Any]: country_obj = countries_by_name[country]() diff --git a/qt_ui/windows/mission/flight/settings/QFlightTypeTaskInfo.py b/qt_ui/windows/mission/flight/settings/QFlightTypeTaskInfo.py index cf3118b54..5b039cfd3 100644 --- a/qt_ui/windows/mission/flight/settings/QFlightTypeTaskInfo.py +++ b/qt_ui/windows/mission/flight/settings/QFlightTypeTaskInfo.py @@ -18,9 +18,9 @@ def __init__(self, flight): self.task_type = QLabel(str(flight.flight_type)) self.task_type.setProperty("style", flight.flight_type.name) - self.callsign_label = QLabel("Callsign:") + self.callsign_label = QLabel("Flight Lead Callsign:") if flight.callsign is not None: - callsign = flight.callsign.group_callsign() + callsign = flight.callsign.lead_callsign() else: callsign = "" self.callsign = QLabel(callsign) From 065fe59fb1374de3c7152bdd690341a503dbeef2 Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Sun, 29 Sep 2024 14:20:01 +1000 Subject: [PATCH 09/15] Support Eastern numeric callsigns --- game/callsigns/callsigngenerator.py | 148 +++++++++++------- .../aircraft/flightgroupconfigurator.py | 10 +- .../flight/settings/QFlightTypeTaskInfo.py | 2 +- tests/callsigns/test_callsign_generator.py | 25 ++- 4 files changed, 119 insertions(+), 66 deletions(-) diff --git a/game/callsigns/callsigngenerator.py b/game/callsigns/callsigngenerator.py index 7db643e69..7e910862e 100644 --- a/game/callsigns/callsigngenerator.py +++ b/game/callsigns/callsigngenerator.py @@ -1,6 +1,8 @@ +from __future__ import annotations from abc import ABC from dataclasses import dataclass from enum import StrEnum + from collections import deque from typing import Any, List, Optional @@ -11,7 +13,7 @@ from game.ato.flighttype import FlightType -MAX_GROUP_ID = 999 +MAX_GROUP_ID = 99 class CallsignCategory(StrEnum): @@ -21,42 +23,15 @@ class CallsignCategory(StrEnum): GROUND_UNITS = "GroundUnits" HELIPADS = "Helipad" GRASS_AIRFIELDS = "GrassAirfield" - - -class AbstractCallsign(ABC): - - def __str__(self) -> str: - ... - - def lead_callsign(self) -> str: - ... - - def pydcs_dict(self, country: str) -> dict[Any, Any]: - ... - - -@dataclass(frozen=True) -class EasternCallsign(AbstractCallsign): - number: int - - def __post_init__(self) -> None: - if self.number < 1 or self.number > MAX_GROUP_ID: - raise ValueError( - f"Invalid callsign {numberd}. Callsigns have to be between 1 and {MAX_GROUP_ID}." - ) - - def __str__(self): - return str(self.number).zfill(3) - - def lead_callsign(self) -> str: - return str(self.number) @dataclass(frozen=True) -class Callsign(AbstractCallsign): - name: str # Callsign name e.g. "Enfield" - group_id: int # ID of the group e.g. 2 in Enfield-2-3 - unit_id: int # ID of the unit e.g. 3 in Enfield-2-3 +class Callsign: + name: Optional[ + str + ] # Callsign name e.g. "Enfield" for western callsigns. None for eastern callsigns. + group_id: int # ID of the group e.g. 2 in Enfield-2-3 for western callsigns. First two digits of eastern callsigns. + unit_id: int # ID of the unit e.g. 3 in Enfield-2-3 for western callsigns. Last digit of eastern callsigns. def __post_init__(self) -> None: if self.group_id < 1 or self.group_id > MAX_GROUP_ID: @@ -69,12 +44,26 @@ def __post_init__(self) -> None: ) def __str__(self) -> str: - return f"{self.name}{self.group_id}{self.unit_id}" + if self.name is not None: + return f"{self.name}{self.group_id}{self.unit_id}" + else: + return str(self.group_id * 10 + self.unit_id) + + def lead_callsign(self) -> Callsign: + return Callsign(self.name, self.group_id, 1) - def lead_callsign(self) -> str: - return f"{self.name}-{self.group_id}-1" + def unit_callsign(self, unit_id: int) -> Callsign: + return Callsign(self.name, self.group_id, unit_id) + + def group_name(self) -> str: + if self.name is not None: + return f"{self.name}-{self.group_id}" + else: + return str(self.lead_callsign()) - def pydcs_dict(self, country: str) -> dict[Any, Any]: + def pydcs_dict(self, country: str) -> dict[Any, Any] | str: + if self.name is None: + return str(self) country_obj = countries_by_name[country]() for category in CallsignCategory: if category in country_obj.callsign: @@ -89,21 +78,22 @@ def pydcs_dict(self, country: str) -> dict[Any, Any]: raise ValueError(f"Could not find callsign {name} in {country}.") -class GroupIdRegistry: +class WesternGroupIdRegistry: - def __init__(self, country: Country): + def __init__(self, country: Country, max_group_id: int = MAX_GROUP_ID): self._names: dict[str, deque[int]] = {} for category in CallsignCategory: if category in country.callsign: for name in country.callsign[category]: self._names[name] = deque() + self._max_group_id = max_group_id self.reset() def reset(self) -> None: for name in self._names: self._names[name] = deque() for i in range( - MAX_GROUP_ID, 0, -1 + self._max_group_id, 0, -1 ): # Put group IDs on FIFO queue so 1 gets popped first self._names[name].appendleft(i) @@ -111,21 +101,30 @@ def alloc_group_id(self, name: str) -> int: return self._names[name].popleft() def release_group_id(self, callsign: Callsign) -> None: + if callsign.name is None: + raise ValueError("Releasing eastern callsign") self._names[callsign.name].appendleft(callsign.group_id) - - -class EasternCallsignRegistry: - def __init__(self): + +class EasternGroupIdRegistry: + + def __init__(self, max_group_id: int = MAX_GROUP_ID): + self._max_group_id = max_group_id + self._queue: deque[int] = deque() self.reset() def reset(self) -> None: - self.next = 1 - - def alloc_callsign(self) -> int: - callsign = self.next - self.next += 1 - return callsign + self._queue = deque() + for i in range( + self._max_group_id, 0, -1 + ): # Put group IDs on FIFO queue so 1 gets popped first + self._queue.appendleft(i) + + def alloc_group_id(self) -> int: + return self._queue.popleft() + + def release_group_id(self, callsign: Callsign) -> None: + self._queue.appendleft(callsign.group_id) class RoundRobinNameAllocator: @@ -171,13 +170,12 @@ def allocate(self, flight: Flight) -> str: } -class FlightCallsignGenerator: - """Generate callsign for lead unit in a group""" +class WesternFlightCallsignGenerator: + """Generate western callsign for lead unit in a group""" - def __init__(self, country: str): + def __init__(self, country: str) -> None: self._country = countries_by_name[country]() - - self._group_id_registry = GroupIdRegistry(self._country) + self._group_id_registry = WesternGroupIdRegistry(self._country) self._awacs_name_allocator = None self._tankers_name_allocator = None @@ -212,3 +210,41 @@ def alloc_callsign(self, flight: Flight) -> Callsign: def release_callsign(self, callsign: Callsign) -> None: self._group_id_registry.release_group_id(callsign) + + +class EasternFlightCallsignGenerator: + """Generate eastern callsign for lead unit in a group""" + + def __init__(self) -> None: + self._group_id_registry = EasternGroupIdRegistry() + + def reset(self) -> None: + self._group_id_registry.reset() + + def alloc_callsign(self, flight: Flight) -> Callsign: + group_id = self._group_id_registry.alloc_group_id() + return Callsign(None, group_id, 1) + + def release_callsign(self, callsign: Callsign) -> None: + self._group_id_registry.release_group_id(callsign) + + +class FlightCallsignGenerator: + + def __init__(self, country: str): + self._generators: dict[ + bool, WesternFlightCallsignGenerator | EasternFlightCallsignGenerator + ] = { + True: WesternFlightCallsignGenerator(country), + False: EasternFlightCallsignGenerator(), + } + self._use_western_callsigns = countries_by_name[country]().use_western_callsigns + + def reset(self) -> None: + self._generators[self._use_western_callsigns].reset() + + def alloc_callsign(self, flight: Flight) -> Callsign: + return self._generators[self._use_western_callsigns].alloc_callsign(flight) + + def release_callsign(self, callsign: Callsign) -> None: + self._generators[self._use_western_callsigns].release_callsign(callsign) diff --git a/game/missiongenerator/aircraft/flightgroupconfigurator.py b/game/missiongenerator/aircraft/flightgroupconfigurator.py index 574c29252..e7664b13e 100644 --- a/game/missiongenerator/aircraft/flightgroupconfigurator.py +++ b/game/missiongenerator/aircraft/flightgroupconfigurator.py @@ -277,9 +277,9 @@ def set_callsigns(self) -> None: if self.flight.callsign is None: return for unit_index, unit in enumerate(self.group.units): - unit_callsign = Callsign( - self.flight.callsign.name, - self.flight.callsign.group_id, - unit_index + 1, - ) + unit_callsign = self.flight.callsign.unit_callsign(unit_index + 1) + if ( + unit_callsign.name is None + ): # pydcs needs unit.callsign to be set for eastern callsigns + unit.callsign = str(unit_callsign) unit.callsign_dict = unit_callsign.pydcs_dict(country=self.flight.country) diff --git a/qt_ui/windows/mission/flight/settings/QFlightTypeTaskInfo.py b/qt_ui/windows/mission/flight/settings/QFlightTypeTaskInfo.py index 5b039cfd3..568917866 100644 --- a/qt_ui/windows/mission/flight/settings/QFlightTypeTaskInfo.py +++ b/qt_ui/windows/mission/flight/settings/QFlightTypeTaskInfo.py @@ -20,7 +20,7 @@ def __init__(self, flight): self.callsign_label = QLabel("Flight Lead Callsign:") if flight.callsign is not None: - callsign = flight.callsign.lead_callsign() + callsign = flight.callsign.group_name() else: callsign = "" self.callsign = QLabel(callsign) diff --git a/tests/callsigns/test_callsign_generator.py b/tests/callsigns/test_callsign_generator.py index 60908aad2..5a9db7b28 100644 --- a/tests/callsigns/test_callsign_generator.py +++ b/tests/callsigns/test_callsign_generator.py @@ -4,7 +4,8 @@ from game.callsigns.callsigngenerator import ( Callsign, - GroupIdRegistry, + EasternGroupIdRegistry, + WesternGroupIdRegistry, RoundRobinNameAllocator, ) @@ -13,7 +14,7 @@ def test_callsign() -> None: valid_callsign = Callsign("Enfield", 2, 3) assert str(valid_callsign) == "Enfield23" - assert valid_callsign.group_callsign() == "Enfield-2" + assert valid_callsign.group_name() == "Enfield-2" assert valid_callsign.pydcs_dict("USA") == {"name": "Enfield23", 1: 1, 2: 2, 3: 3} # Invalid callsign, group ID too large. @@ -33,8 +34,8 @@ def test_callsign() -> None: Callsign("Enfield", 1, 11) -def test_group_id_registry() -> None: - registry = GroupIdRegistry(countries_by_name["USA"]()) +def test_western_group_id_registry() -> None: + registry = WesternGroupIdRegistry(countries_by_name["USA"]()) # Check registry increments group IDs. assert registry.alloc_group_id("Enfield") == 1 @@ -51,6 +52,22 @@ def test_group_id_registry() -> None: registry.reset() assert registry.alloc_group_id("Enfield") == 1 assert registry.alloc_group_id("Springfield") == 1 + + +def test_eastern_group_id_registry() -> None: + registry = EasternGroupIdRegistry() + + # Check registry increments group IDs. + assert registry.alloc_group_id() == 1 + assert registry.alloc_group_id() == 2 + + # Check release. + registry.release_group_id(Callsign(None, 1, 1)) + assert registry.alloc_group_id() == 1 + + # Reset and check allocation. + registry.reset() + assert registry.alloc_group_id() == 1 def test_round_robin_allocator() -> None: From acf9c84332c3460dca9c60c2f1fb195b7297750b Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Fri, 4 Oct 2024 20:51:52 +1000 Subject: [PATCH 10/15] Update changelog and pydcs --- changelog.md | 3 ++- requirements.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index de02723b3..f412b5155 100644 --- a/changelog.md +++ b/changelog.md @@ -5,11 +5,12 @@ Saves from 11.x are not compatible with 12.0.0. ## Features/Improvements * **[Campaign]** Removed deprecated settings for generating persistent and invulnerable AWACs and tankers. -* **[Mods]** F/A-18 E/F/G Super Hornet mod version updated to 2.3. * **[Campaign]** Do not allow aircraft from a captured control point to retreat if the captured control point has a damaged runway. +* **[Mods]** F/A-18 E/F/G Super Hornet mod version updated to 2.3. ## Fixes +* **[Campaign]** Flights are assigned different callsigns appropriate to the faction. # 11.1.1 diff --git a/requirements.txt b/requirements.txt index a07cd7bf0..314fa55d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -36,7 +36,7 @@ pre-commit==3.5.0 pydantic==2.5.2 pydantic-settings==2.1.0 pydantic_core==2.14.5 -pydcs @ git+https://github.com/dcs-liberation/dcs@47b524e3cfaa945d1f42717ee00f949b3354125c +pydcs @ git+https://github.com/dcs-liberation/dcs@52b1a3510829dd46d52bbb79ba18e85e8608f25c pyinstaller==5.13.1 pyinstaller-hooks-contrib==2023.6 pyproj==3.6.1 From d6870f44fbc916d1cf1685b64b1519d26b835790 Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:31:37 +1000 Subject: [PATCH 11/15] Update black version for github workflow to line up with requirements.txt --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 557b9b42d..ca3b8b226 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/setup-python@v2 - uses: psf/black@stable with: - version: ~=23.11 + version: ~=24.3.0 src: "." options: "--check" From e38b7ed436677f9d12258a13e6b04aea4d2e7677 Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:31:57 +1000 Subject: [PATCH 12/15] Update changelog with DCS engine version --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index f412b5155..62a2e1dfd 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,7 @@ Saves from 11.x are not compatible with 12.0.0. ## Features/Improvements +* **[Engine]** Support for DCS 2.9.8.1214. * **[Campaign]** Removed deprecated settings for generating persistent and invulnerable AWACs and tankers. * **[Campaign]** Do not allow aircraft from a captured control point to retreat if the captured control point has a damaged runway. * **[Mods]** F/A-18 E/F/G Super Hornet mod version updated to 2.3. From eefff9d5031cc71bf606ecf2053052da2f8fda69 Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Sat, 5 Oct 2024 16:50:26 +1000 Subject: [PATCH 13/15] Fix typing --- game/callsigns/callsigngenerator.py | 4 +--- game/missiongenerator/aircraft/flightgroupconfigurator.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/game/callsigns/callsigngenerator.py b/game/callsigns/callsigngenerator.py index 7e910862e..caa2fac97 100644 --- a/game/callsigns/callsigngenerator.py +++ b/game/callsigns/callsigngenerator.py @@ -61,9 +61,7 @@ def group_name(self) -> str: else: return str(self.lead_callsign()) - def pydcs_dict(self, country: str) -> dict[Any, Any] | str: - if self.name is None: - return str(self) + def pydcs_dict(self, country: str) -> dict[Any, Any]: country_obj = countries_by_name[country]() for category in CallsignCategory: if category in country_obj.callsign: diff --git a/game/missiongenerator/aircraft/flightgroupconfigurator.py b/game/missiongenerator/aircraft/flightgroupconfigurator.py index e7664b13e..172df47fd 100644 --- a/game/missiongenerator/aircraft/flightgroupconfigurator.py +++ b/game/missiongenerator/aircraft/flightgroupconfigurator.py @@ -281,5 +281,5 @@ def set_callsigns(self) -> None: if ( unit_callsign.name is None ): # pydcs needs unit.callsign to be set for eastern callsigns - unit.callsign = str(unit_callsign) + unit.callsign = str(unit_callsign) # type: ignore unit.callsign_dict = unit_callsign.pydcs_dict(country=self.flight.country) From 37546d4f494dd170e9c996c05cf2580ce8d5f690 Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Sat, 5 Oct 2024 17:02:43 +1000 Subject: [PATCH 14/15] Fix bug --- game/missiongenerator/aircraft/flightgroupconfigurator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/game/missiongenerator/aircraft/flightgroupconfigurator.py b/game/missiongenerator/aircraft/flightgroupconfigurator.py index 172df47fd..951d37560 100644 --- a/game/missiongenerator/aircraft/flightgroupconfigurator.py +++ b/game/missiongenerator/aircraft/flightgroupconfigurator.py @@ -282,4 +282,7 @@ def set_callsigns(self) -> None: unit_callsign.name is None ): # pydcs needs unit.callsign to be set for eastern callsigns unit.callsign = str(unit_callsign) # type: ignore - unit.callsign_dict = unit_callsign.pydcs_dict(country=self.flight.country) + else: # Use western callsign + unit.callsign_dict = unit_callsign.pydcs_dict( + country=self.flight.country + ) From 0c83b81a053d4a4d9a62dd32781f02c50cf904a1 Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Sat, 5 Oct 2024 17:04:57 +1000 Subject: [PATCH 15/15] Fix linting --- tests/callsigns/test_callsign_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/callsigns/test_callsign_generator.py b/tests/callsigns/test_callsign_generator.py index 5a9db7b28..d113d5aa4 100644 --- a/tests/callsigns/test_callsign_generator.py +++ b/tests/callsigns/test_callsign_generator.py @@ -52,8 +52,8 @@ def test_western_group_id_registry() -> None: registry.reset() assert registry.alloc_group_id("Enfield") == 1 assert registry.alloc_group_id("Springfield") == 1 - - + + def test_eastern_group_id_registry() -> None: registry = EasternGroupIdRegistry()