From 177f357492ce147a56252413628edaa8377a0e26 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 22 Jul 2023 14:31:38 -0700 Subject: [PATCH] Pre-allocate laser codes for FLOTs and flights. --- game/ato/flightmember.py | 26 ++++++++++++++++++- game/ato/flightmembers.py | 11 +++++--- game/coalition.py | 5 ++++ game/commander/packagebuilder.py | 12 +++++++-- game/commander/packagefulfiller.py | 1 + game/game.py | 10 ++++++- .../aircraft/aircraftgenerator.py | 4 --- .../aircraft/flightgroupconfigurator.py | 9 +++---- game/missiongenerator/flotgenerator.py | 7 ++--- game/missiongenerator/missiongenerator.py | 4 --- game/theater/controlpoint.py | 25 ++++++++++++------ game/theater/frontline.py | 3 +++ .../windows/mission/flight/QFlightCreator.py | 6 +++++ 13 files changed, 89 insertions(+), 34 deletions(-) diff --git a/game/ato/flightmember.py b/game/ato/flightmember.py index 7a9a3410..ddb2cca4 100644 --- a/game/ato/flightmember.py +++ b/game/ato/flightmember.py @@ -1,8 +1,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from game.ato.loadouts import Loadout +from game.lasercodes import LaserCode +from game.savecompat import has_save_compat_for if TYPE_CHECKING: from game.squadrons import Pilot @@ -13,8 +15,30 @@ class FlightMember: self.pilot = pilot self.loadout = loadout self.use_custom_loadout = False + self.tgp_laser_code: LaserCode | None = None self.properties: dict[str, bool | float | int] = {} + @has_save_compat_for(9) + def __setstate__(self, state: dict[str, Any]) -> None: + if "tgp_laser_code" not in state: + state["tgp_laser_code"] = None + self.__dict__.update(state) + + def assign_tgp_laser_code(self, code: LaserCode) -> None: + if self.tgp_laser_code is not None: + raise RuntimeError( + f"{self.pilot} already has already been assigned laser code " + f"{self.tgp_laser_code}" + ) + self.tgp_laser_code = code + + def release_tgp_laser_code(self) -> None: + if self.tgp_laser_code is None: + raise RuntimeError(f"{self.pilot} has no assigned laser code") + + self.tgp_laser_code.release() + self.tgp_laser_code = None + @property def is_player(self) -> bool: if self.pilot is None: diff --git a/game/ato/flightmembers.py b/game/ato/flightmembers.py index d76c569c..f8d34190 100644 --- a/game/ato/flightmembers.py +++ b/game/ato/flightmembers.py @@ -53,9 +53,11 @@ class FlightMembers(IFlightRoster): def resize(self, new_size: int) -> None: if self.max_size > new_size: - self.flight.squadron.return_pilots( - [m.pilot for m in self.members[new_size:] if m.pilot is not None] - ) + for member in self.members[new_size:]: + if (pilot := member.pilot) is not None: + self.flight.squadron.return_pilot(pilot) + if (code := member.tgp_laser_code) is not None: + code.release() self.members = self.members[:new_size] return if self.max_size: @@ -80,6 +82,9 @@ class FlightMembers(IFlightRoster): self.flight.squadron.return_pilots( [p for p in self.iter_pilots() if p is not None] ) + for member in self.members: + if (code := member.tgp_laser_code) is not None: + code.release() def use_same_loadout_for_all_members(self) -> None: if not self.members: diff --git a/game/coalition.py b/game/coalition.py index f142de8b..5ac2f0ed 100644 --- a/game/coalition.py +++ b/game/coalition.py @@ -25,6 +25,7 @@ if TYPE_CHECKING: from .data.doctrine import Doctrine from .factions.faction import Faction from .game import Game + from .lasercodes import LaserCodeRegistry from .sim import GameUpdateEvents @@ -85,6 +86,10 @@ class Coalition: assert self._navmesh is not None return self._navmesh + @property + def laser_code_registry(self) -> LaserCodeRegistry: + return self.game.laser_code_registry + def __getstate__(self) -> dict[str, Any]: state = self.__dict__.copy() # Avoid persisting any volatile types that can be deterministically diff --git a/game/commander/packagebuilder.py b/game/commander/packagebuilder.py index be6fb785..6ea28ac7 100644 --- a/game/commander/packagebuilder.py +++ b/game/commander/packagebuilder.py @@ -10,9 +10,10 @@ from ..ato.starttype import StartType from ..db.database import Database if TYPE_CHECKING: - from game.dcs.aircrafttype import AircraftType - from game.squadrons.airwing import AirWing from game.ato.closestairfields import ClosestAirfields + from game.dcs.aircrafttype import AircraftType + from game.lasercodes import LaserCodeRegistry + from game.squadrons.airwing import AirWing from .missionproposals import ProposedFlight @@ -24,6 +25,7 @@ class PackageBuilder: location: MissionTarget, closest_airfields: ClosestAirfields, air_wing: AirWing, + laser_code_registry: LaserCodeRegistry, flight_db: Database[Flight], is_player: bool, start_type: StartType, @@ -33,6 +35,7 @@ class PackageBuilder: self.is_player = is_player self.package = Package(location, flight_db, auto_asap=asap) self.air_wing = air_wing + self.laser_code_registry = laser_code_registry self.start_type = start_type def plan_flight(self, plan: ProposedFlight) -> bool: @@ -62,6 +65,11 @@ class PackageBuilder: start_type, divert=self.find_divert_field(squadron.aircraft, squadron.location), ) + for member in flight.iter_members(): + if member.is_player: + member.assign_tgp_laser_code( + self.laser_code_registry.alloc_laser_code() + ) self.package.add_flight(flight) return True diff --git a/game/commander/packagefulfiller.py b/game/commander/packagefulfiller.py index d3b5a9e2..622e1535 100644 --- a/game/commander/packagefulfiller.py +++ b/game/commander/packagefulfiller.py @@ -142,6 +142,7 @@ class PackageFulfiller: mission.location, ObjectiveDistanceCache.get_closest_airfields(mission.location), self.air_wing, + self.coalition.laser_code_registry, self.flight_db, self.is_player, self.default_start_type, diff --git a/game/game.py b/game/game.py index 53463be0..2efc7233 100644 --- a/game/game.py +++ b/game/game.py @@ -28,7 +28,9 @@ from .coalition import Coalition from .db.gamedb import GameDb from .dcs.countries import country_with_name from .infos.information import Information +from .lasercodes.lasercoderegistry import LaserCodeRegistry from .profiling import logged_duration +from .savecompat import has_save_compat_for from .settings import Settings from .theater import ConflictTheater from .theater.bullseye import Bullseye @@ -121,6 +123,7 @@ class Game: self.current_unit_id = 0 self.current_group_id = 0 self.name_generator = naming.namegen + self.laser_code_registry = LaserCodeRegistry() self.db = GameDb() @@ -148,8 +151,13 @@ class Game: self.on_load(game_still_initializing=True) + @has_save_compat_for(9) def __setstate__(self, state: dict[str, Any]) -> None: self.__dict__.update(state) + if not hasattr(self, "laser_code_registry"): + self.laser_code_registry = LaserCodeRegistry() + for front_line in self.theater.conflicts(): + front_line.laser_code = self.laser_code_registry.alloc_laser_code() # Regenerate any state that was not persisted. self.on_load() @@ -305,7 +313,7 @@ class Game: self.theater.iads_network.initialize_network(self.theater.ground_objects) for control_point in self.theater.controlpoints: - control_point.initialize_turn_0() + control_point.initialize_turn_0(self.laser_code_registry) for tgo in control_point.connected_objectives: self.db.tgos.add(tgo.id, tgo) diff --git a/game/missiongenerator/aircraft/aircraftgenerator.py b/game/missiongenerator/aircraft/aircraftgenerator.py index 553506ae..bd520486 100644 --- a/game/missiongenerator/aircraft/aircraftgenerator.py +++ b/game/missiongenerator/aircraft/aircraftgenerator.py @@ -21,7 +21,6 @@ from game.ato.flightstate import Completed, WaitingForStart from game.ato.flighttype import FlightType from game.ato.package import Package from game.ato.starttype import StartType -from game.lasercodes import LaserCodeRegistry from game.missiongenerator.missiondata import MissionData from game.radio.radios import RadioRegistry from game.radio.tacan import TacanRegistry @@ -53,7 +52,6 @@ class AircraftGenerator: time: datetime, radio_registry: RadioRegistry, tacan_registry: TacanRegistry, - laser_code_registry: LaserCodeRegistry, unit_map: UnitMap, mission_data: MissionData, helipads: dict[ControlPoint, list[StaticGroup]], @@ -66,7 +64,6 @@ class AircraftGenerator: self.time = time self.radio_registry = radio_registry self.tacan_registy = tacan_registry - self.laser_code_registry = laser_code_registry self.unit_map = unit_map self.flights: List[FlightData] = [] self.mission_data = mission_data @@ -254,7 +251,6 @@ class AircraftGenerator: self.time, self.radio_registry, self.tacan_registy, - self.laser_code_registry, self.mission_data, dynamic_runways, self.use_client, diff --git a/game/missiongenerator/aircraft/flightgroupconfigurator.py b/game/missiongenerator/aircraft/flightgroupconfigurator.py index 457c48df..1935a48f 100644 --- a/game/missiongenerator/aircraft/flightgroupconfigurator.py +++ b/game/missiongenerator/aircraft/flightgroupconfigurator.py @@ -15,8 +15,7 @@ from dcs.unitgroup import FlyingGroup from game.ato import Flight, FlightType from game.callsigns import callsign_for_support_unit -from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum -from game.lasercodes import LaserCodeRegistry +from game.data.weapons import Pylon from game.missiongenerator.logisticsgenerator import LogisticsGenerator from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo from game.radio.radios import RadioFrequency, RadioRegistry @@ -47,7 +46,6 @@ class FlightGroupConfigurator: time: datetime, radio_registry: RadioRegistry, tacan_registry: TacanRegistry, - laser_code_registry: LaserCodeRegistry, mission_data: MissionData, dynamic_runways: dict[str, RunwayData], use_client: bool, @@ -59,7 +57,6 @@ class FlightGroupConfigurator: self.time = time self.radio_registry = radio_registry self.tacan_registry = tacan_registry - self.laser_code_registry = laser_code_registry self.mission_data = mission_data self.dynamic_runways = dynamic_runways self.use_client = use_client @@ -147,8 +144,8 @@ class FlightGroupConfigurator: self, unit: FlyingUnit, member: FlightMember, laser_codes: list[Optional[int]] ) -> None: self.set_skill(unit, member) - if member.loadout.has_weapon_of_type(WeaponTypeEnum.TGP) and member.is_player: - laser_codes.append(self.laser_code_registry.alloc_laser_code().code) + if (code := member.tgp_laser_code) is not None: + laser_codes.append(code.code) else: laser_codes.append(None) settings = self.flight.coalition.game.settings diff --git a/game/missiongenerator/flotgenerator.py b/game/missiongenerator/flotgenerator.py index 978815b0..95b02734 100644 --- a/game/missiongenerator/flotgenerator.py +++ b/game/missiongenerator/flotgenerator.py @@ -37,7 +37,6 @@ from game.ground_forces.ai_ground_planner import ( DISTANCE_FROM_FRONTLINE, ) from game.ground_forces.combat_stance import CombatStance -from game.lasercodes import LaserCodeRegistry from game.naming import namegen from game.radio.radios import RadioRegistry from game.theater.controlpoint import ControlPoint @@ -82,7 +81,6 @@ class FlotGenerator: unit_map: UnitMap, radio_registry: RadioRegistry, mission_data: MissionData, - laser_code_registry: LaserCodeRegistry, ) -> None: self.mission = mission self.conflict = conflict @@ -94,7 +92,6 @@ class FlotGenerator: self.unit_map = unit_map self.radio_registry = radio_registry self.mission_data = mission_data - self.laser_code_registry = laser_code_registry def generate(self) -> None: position = FrontLineConflictDescription.frontline_position( @@ -149,9 +146,9 @@ class FlotGenerator: # laser codes to 1113 to allow lasing for Su-25 Frogfoots and A-10A Warthogs. # Otherwise use 1688 for the first JTAC, 1687 for the second etc. if self.game.settings.plugins.get("ctld.fc3LaserCode"): - code = self.laser_code_registry.fc3_code + code = self.game.laser_code_registry.fc3_code else: - code = self.laser_code_registry.alloc_laser_code() + code = self.conflict.front_line.laser_code utype = self.game.blue.faction.jtac_unit if utype is None: diff --git a/game/missiongenerator/missiongenerator.py b/game/missiongenerator/missiongenerator.py index 3e2df47b..a66c9d45 100644 --- a/game/missiongenerator/missiongenerator.py +++ b/game/missiongenerator/missiongenerator.py @@ -32,7 +32,6 @@ from .flotgenerator import FlotGenerator from .forcedoptionsgenerator import ForcedOptionsGenerator from .frontlineconflictdescription import FrontLineConflictDescription from .kneeboard import KneeboardGenerator -from game.lasercodes import LaserCodeRegistry from .luagenerator import LuaGenerator from .missiondata import MissionData from .tgogenerator import TgoGenerator @@ -53,7 +52,6 @@ class MissionGenerator: self.mission_data = MissionData() - self.laser_code_registry = LaserCodeRegistry() self.radio_registry = RadioRegistry() self.tacan_registry = TacanRegistry() @@ -224,7 +222,6 @@ class MissionGenerator: self.unit_map, self.radio_registry, self.mission_data, - self.laser_code_registry, ) ground_conflict_gen.generate() @@ -239,7 +236,6 @@ class MissionGenerator: self.time, self.radio_registry, self.tacan_registry, - self.laser_code_registry, self.unit_map, mission_data=self.mission_data, helipads=tgo_generator.helipads, diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 567a7be9..1295b5bf 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -87,6 +87,7 @@ if TYPE_CHECKING: from game import Game from game.ato.flighttype import FlightType from game.coalition import Coalition + from game.lasercodes.lasercoderegistry import LaserCodeRegistry from game.sim import GameUpdateEvents from game.squadrons.squadron import Squadron from game.transfers import PendingTransfers @@ -430,42 +431,50 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC): assert self._front_line_db is None self._front_line_db = game.db.front_lines - def initialize_turn_0(self) -> None: + def initialize_turn_0(self, laser_code_registry: LaserCodeRegistry) -> None: # We don't need to send events for turn 0. The UI isn't up yet, and it'll fetch # the entire game state when it comes up. from game.sim import GameUpdateEvents - self._create_missing_front_lines(GameUpdateEvents()) + self._create_missing_front_lines(laser_code_registry, GameUpdateEvents()) @property def front_line_db(self) -> Database[FrontLine]: assert self._front_line_db is not None return self._front_line_db - def _create_missing_front_lines(self, events: GameUpdateEvents) -> None: + def _create_missing_front_lines( + self, laser_code_registry: LaserCodeRegistry, events: GameUpdateEvents + ) -> None: for connection in self.convoy_routes.keys(): if not connection.front_line_active_with( self ) and not connection.is_friendly_to(self): - self._create_front_line_with(connection, events) + self._create_front_line_with(laser_code_registry, connection, events) def _create_front_line_with( - self, connection: ControlPoint, events: GameUpdateEvents + self, + laser_code_registry: LaserCodeRegistry, + connection: ControlPoint, + events: GameUpdateEvents, ) -> None: blue, red = FrontLine.sort_control_points(self, connection) - front = FrontLine(blue, red) + front = FrontLine(blue, red, laser_code_registry.alloc_laser_code()) self.front_lines[connection] = front connection.front_lines[self] = front self.front_line_db.add(front.id, front) events.update_front_line(front) def _remove_front_line_with( - self, connection: ControlPoint, events: GameUpdateEvents + self, + connection: ControlPoint, + events: GameUpdateEvents, ) -> None: front = self.front_lines[connection] del self.front_lines[connection] del connection.front_lines[self] self.front_line_db.remove(front.id) + front.laser_code.release() events.delete_front_line(front) def _clear_front_lines(self, events: GameUpdateEvents) -> None: @@ -929,7 +938,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC): self._coalition = new_coalition self.base.set_strength_to_minimum() self._clear_front_lines(events) - self._create_missing_front_lines(events) + self._create_missing_front_lines(game.laser_code_registry, events) events.update_control_point(self) # All the attached TGOs have either been depopulated or captured. Tell the UI to diff --git a/game/theater/frontline.py b/game/theater/frontline.py index 8b13b302..a1dfe807 100644 --- a/game/theater/frontline.py +++ b/game/theater/frontline.py @@ -8,6 +8,7 @@ from typing import Any, Iterator, List, TYPE_CHECKING, Tuple from dcs.mapping import Point from .missiontarget import MissionTarget +from ..lasercodes.lasercode import LaserCode from ..utils import Heading, pairwise if TYPE_CHECKING: @@ -49,10 +50,12 @@ class FrontLine(MissionTarget): self, blue_point: ControlPoint, red_point: ControlPoint, + laser_code: LaserCode, ) -> None: self.id = uuid.uuid4() self.blue_cp = blue_point self.red_cp = red_point + self.laser_code = laser_code try: route = list(blue_point.convoy_route_to(red_point)) except KeyError: diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index ddd826e9..309f58d6 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -194,6 +194,12 @@ class QFlightCreator(QDialog): roster=roster, ) + for member in flight.iter_members(): + if member.is_player: + member.assign_tgp_laser_code( + self.game.laser_code_registry.alloc_laser_code() + ) + # noinspection PyUnresolvedReferences self.created.emit(flight) self.accept()