Pre-allocate laser codes for FLOTs and flights.

This commit is contained in:
Dan Albert 2023-07-22 14:31:38 -07:00 committed by Raffson
parent 723e191f10
commit 177f357492
No known key found for this signature in database
GPG Key ID: B0402B2C9B764D99
13 changed files with 89 additions and 34 deletions

View File

@ -1,8 +1,10 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Any
from game.ato.loadouts import Loadout from game.ato.loadouts import Loadout
from game.lasercodes import LaserCode
from game.savecompat import has_save_compat_for
if TYPE_CHECKING: if TYPE_CHECKING:
from game.squadrons import Pilot from game.squadrons import Pilot
@ -13,8 +15,30 @@ class FlightMember:
self.pilot = pilot self.pilot = pilot
self.loadout = loadout self.loadout = loadout
self.use_custom_loadout = False self.use_custom_loadout = False
self.tgp_laser_code: LaserCode | None = None
self.properties: dict[str, bool | float | int] = {} 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 @property
def is_player(self) -> bool: def is_player(self) -> bool:
if self.pilot is None: if self.pilot is None:

View File

@ -53,9 +53,11 @@ class FlightMembers(IFlightRoster):
def resize(self, new_size: int) -> None: def resize(self, new_size: int) -> None:
if self.max_size > new_size: if self.max_size > new_size:
self.flight.squadron.return_pilots( for member in self.members[new_size:]:
[m.pilot for m in self.members[new_size:] if m.pilot is not None] 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] self.members = self.members[:new_size]
return return
if self.max_size: if self.max_size:
@ -80,6 +82,9 @@ class FlightMembers(IFlightRoster):
self.flight.squadron.return_pilots( self.flight.squadron.return_pilots(
[p for p in self.iter_pilots() if p is not None] [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: def use_same_loadout_for_all_members(self) -> None:
if not self.members: if not self.members:

View File

@ -25,6 +25,7 @@ if TYPE_CHECKING:
from .data.doctrine import Doctrine from .data.doctrine import Doctrine
from .factions.faction import Faction from .factions.faction import Faction
from .game import Game from .game import Game
from .lasercodes import LaserCodeRegistry
from .sim import GameUpdateEvents from .sim import GameUpdateEvents
@ -85,6 +86,10 @@ class Coalition:
assert self._navmesh is not None assert self._navmesh is not None
return self._navmesh return self._navmesh
@property
def laser_code_registry(self) -> LaserCodeRegistry:
return self.game.laser_code_registry
def __getstate__(self) -> dict[str, Any]: def __getstate__(self) -> dict[str, Any]:
state = self.__dict__.copy() state = self.__dict__.copy()
# Avoid persisting any volatile types that can be deterministically # Avoid persisting any volatile types that can be deterministically

View File

@ -10,9 +10,10 @@ from ..ato.starttype import StartType
from ..db.database import Database from ..db.database import Database
if TYPE_CHECKING: if TYPE_CHECKING:
from game.dcs.aircrafttype import AircraftType
from game.squadrons.airwing import AirWing
from game.ato.closestairfields import ClosestAirfields 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 from .missionproposals import ProposedFlight
@ -24,6 +25,7 @@ class PackageBuilder:
location: MissionTarget, location: MissionTarget,
closest_airfields: ClosestAirfields, closest_airfields: ClosestAirfields,
air_wing: AirWing, air_wing: AirWing,
laser_code_registry: LaserCodeRegistry,
flight_db: Database[Flight], flight_db: Database[Flight],
is_player: bool, is_player: bool,
start_type: StartType, start_type: StartType,
@ -33,6 +35,7 @@ class PackageBuilder:
self.is_player = is_player self.is_player = is_player
self.package = Package(location, flight_db, auto_asap=asap) self.package = Package(location, flight_db, auto_asap=asap)
self.air_wing = air_wing self.air_wing = air_wing
self.laser_code_registry = laser_code_registry
self.start_type = start_type self.start_type = start_type
def plan_flight(self, plan: ProposedFlight) -> bool: def plan_flight(self, plan: ProposedFlight) -> bool:
@ -62,6 +65,11 @@ class PackageBuilder:
start_type, start_type,
divert=self.find_divert_field(squadron.aircraft, squadron.location), 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) self.package.add_flight(flight)
return True return True

View File

@ -142,6 +142,7 @@ class PackageFulfiller:
mission.location, mission.location,
ObjectiveDistanceCache.get_closest_airfields(mission.location), ObjectiveDistanceCache.get_closest_airfields(mission.location),
self.air_wing, self.air_wing,
self.coalition.laser_code_registry,
self.flight_db, self.flight_db,
self.is_player, self.is_player,
self.default_start_type, self.default_start_type,

View File

@ -28,7 +28,9 @@ from .coalition import Coalition
from .db.gamedb import GameDb from .db.gamedb import GameDb
from .dcs.countries import country_with_name from .dcs.countries import country_with_name
from .infos.information import Information from .infos.information import Information
from .lasercodes.lasercoderegistry import LaserCodeRegistry
from .profiling import logged_duration from .profiling import logged_duration
from .savecompat import has_save_compat_for
from .settings import Settings from .settings import Settings
from .theater import ConflictTheater from .theater import ConflictTheater
from .theater.bullseye import Bullseye from .theater.bullseye import Bullseye
@ -121,6 +123,7 @@ class Game:
self.current_unit_id = 0 self.current_unit_id = 0
self.current_group_id = 0 self.current_group_id = 0
self.name_generator = naming.namegen self.name_generator = naming.namegen
self.laser_code_registry = LaserCodeRegistry()
self.db = GameDb() self.db = GameDb()
@ -148,8 +151,13 @@ class Game:
self.on_load(game_still_initializing=True) self.on_load(game_still_initializing=True)
@has_save_compat_for(9)
def __setstate__(self, state: dict[str, Any]) -> None: def __setstate__(self, state: dict[str, Any]) -> None:
self.__dict__.update(state) 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. # Regenerate any state that was not persisted.
self.on_load() self.on_load()
@ -305,7 +313,7 @@ class Game:
self.theater.iads_network.initialize_network(self.theater.ground_objects) self.theater.iads_network.initialize_network(self.theater.ground_objects)
for control_point in self.theater.controlpoints: 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: for tgo in control_point.connected_objectives:
self.db.tgos.add(tgo.id, tgo) self.db.tgos.add(tgo.id, tgo)

View File

@ -21,7 +21,6 @@ from game.ato.flightstate import Completed, WaitingForStart
from game.ato.flighttype import FlightType from game.ato.flighttype import FlightType
from game.ato.package import Package from game.ato.package import Package
from game.ato.starttype import StartType from game.ato.starttype import StartType
from game.lasercodes import LaserCodeRegistry
from game.missiongenerator.missiondata import MissionData from game.missiongenerator.missiondata import MissionData
from game.radio.radios import RadioRegistry from game.radio.radios import RadioRegistry
from game.radio.tacan import TacanRegistry from game.radio.tacan import TacanRegistry
@ -53,7 +52,6 @@ class AircraftGenerator:
time: datetime, time: datetime,
radio_registry: RadioRegistry, radio_registry: RadioRegistry,
tacan_registry: TacanRegistry, tacan_registry: TacanRegistry,
laser_code_registry: LaserCodeRegistry,
unit_map: UnitMap, unit_map: UnitMap,
mission_data: MissionData, mission_data: MissionData,
helipads: dict[ControlPoint, list[StaticGroup]], helipads: dict[ControlPoint, list[StaticGroup]],
@ -66,7 +64,6 @@ class AircraftGenerator:
self.time = time self.time = time
self.radio_registry = radio_registry self.radio_registry = radio_registry
self.tacan_registy = tacan_registry self.tacan_registy = tacan_registry
self.laser_code_registry = laser_code_registry
self.unit_map = unit_map self.unit_map = unit_map
self.flights: List[FlightData] = [] self.flights: List[FlightData] = []
self.mission_data = mission_data self.mission_data = mission_data
@ -254,7 +251,6 @@ class AircraftGenerator:
self.time, self.time,
self.radio_registry, self.radio_registry,
self.tacan_registy, self.tacan_registy,
self.laser_code_registry,
self.mission_data, self.mission_data,
dynamic_runways, dynamic_runways,
self.use_client, self.use_client,

View File

@ -15,8 +15,7 @@ from dcs.unitgroup import FlyingGroup
from game.ato import Flight, FlightType from game.ato import Flight, FlightType
from game.callsigns import callsign_for_support_unit from game.callsigns import callsign_for_support_unit
from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum from game.data.weapons import Pylon
from game.lasercodes import LaserCodeRegistry
from game.missiongenerator.logisticsgenerator import LogisticsGenerator from game.missiongenerator.logisticsgenerator import LogisticsGenerator
from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo
from game.radio.radios import RadioFrequency, RadioRegistry from game.radio.radios import RadioFrequency, RadioRegistry
@ -47,7 +46,6 @@ class FlightGroupConfigurator:
time: datetime, time: datetime,
radio_registry: RadioRegistry, radio_registry: RadioRegistry,
tacan_registry: TacanRegistry, tacan_registry: TacanRegistry,
laser_code_registry: LaserCodeRegistry,
mission_data: MissionData, mission_data: MissionData,
dynamic_runways: dict[str, RunwayData], dynamic_runways: dict[str, RunwayData],
use_client: bool, use_client: bool,
@ -59,7 +57,6 @@ class FlightGroupConfigurator:
self.time = time self.time = time
self.radio_registry = radio_registry self.radio_registry = radio_registry
self.tacan_registry = tacan_registry self.tacan_registry = tacan_registry
self.laser_code_registry = laser_code_registry
self.mission_data = mission_data self.mission_data = mission_data
self.dynamic_runways = dynamic_runways self.dynamic_runways = dynamic_runways
self.use_client = use_client self.use_client = use_client
@ -147,8 +144,8 @@ class FlightGroupConfigurator:
self, unit: FlyingUnit, member: FlightMember, laser_codes: list[Optional[int]] self, unit: FlyingUnit, member: FlightMember, laser_codes: list[Optional[int]]
) -> None: ) -> None:
self.set_skill(unit, member) self.set_skill(unit, member)
if member.loadout.has_weapon_of_type(WeaponTypeEnum.TGP) and member.is_player: if (code := member.tgp_laser_code) is not None:
laser_codes.append(self.laser_code_registry.alloc_laser_code().code) laser_codes.append(code.code)
else: else:
laser_codes.append(None) laser_codes.append(None)
settings = self.flight.coalition.game.settings settings = self.flight.coalition.game.settings

View File

@ -37,7 +37,6 @@ from game.ground_forces.ai_ground_planner import (
DISTANCE_FROM_FRONTLINE, DISTANCE_FROM_FRONTLINE,
) )
from game.ground_forces.combat_stance import CombatStance from game.ground_forces.combat_stance import CombatStance
from game.lasercodes import LaserCodeRegistry
from game.naming import namegen from game.naming import namegen
from game.radio.radios import RadioRegistry from game.radio.radios import RadioRegistry
from game.theater.controlpoint import ControlPoint from game.theater.controlpoint import ControlPoint
@ -82,7 +81,6 @@ class FlotGenerator:
unit_map: UnitMap, unit_map: UnitMap,
radio_registry: RadioRegistry, radio_registry: RadioRegistry,
mission_data: MissionData, mission_data: MissionData,
laser_code_registry: LaserCodeRegistry,
) -> None: ) -> None:
self.mission = mission self.mission = mission
self.conflict = conflict self.conflict = conflict
@ -94,7 +92,6 @@ class FlotGenerator:
self.unit_map = unit_map self.unit_map = unit_map
self.radio_registry = radio_registry self.radio_registry = radio_registry
self.mission_data = mission_data self.mission_data = mission_data
self.laser_code_registry = laser_code_registry
def generate(self) -> None: def generate(self) -> None:
position = FrontLineConflictDescription.frontline_position( 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. # 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. # Otherwise use 1688 for the first JTAC, 1687 for the second etc.
if self.game.settings.plugins.get("ctld.fc3LaserCode"): if self.game.settings.plugins.get("ctld.fc3LaserCode"):
code = self.laser_code_registry.fc3_code code = self.game.laser_code_registry.fc3_code
else: else:
code = self.laser_code_registry.alloc_laser_code() code = self.conflict.front_line.laser_code
utype = self.game.blue.faction.jtac_unit utype = self.game.blue.faction.jtac_unit
if utype is None: if utype is None:

View File

@ -32,7 +32,6 @@ from .flotgenerator import FlotGenerator
from .forcedoptionsgenerator import ForcedOptionsGenerator from .forcedoptionsgenerator import ForcedOptionsGenerator
from .frontlineconflictdescription import FrontLineConflictDescription from .frontlineconflictdescription import FrontLineConflictDescription
from .kneeboard import KneeboardGenerator from .kneeboard import KneeboardGenerator
from game.lasercodes import LaserCodeRegistry
from .luagenerator import LuaGenerator from .luagenerator import LuaGenerator
from .missiondata import MissionData from .missiondata import MissionData
from .tgogenerator import TgoGenerator from .tgogenerator import TgoGenerator
@ -53,7 +52,6 @@ class MissionGenerator:
self.mission_data = MissionData() self.mission_data = MissionData()
self.laser_code_registry = LaserCodeRegistry()
self.radio_registry = RadioRegistry() self.radio_registry = RadioRegistry()
self.tacan_registry = TacanRegistry() self.tacan_registry = TacanRegistry()
@ -224,7 +222,6 @@ class MissionGenerator:
self.unit_map, self.unit_map,
self.radio_registry, self.radio_registry,
self.mission_data, self.mission_data,
self.laser_code_registry,
) )
ground_conflict_gen.generate() ground_conflict_gen.generate()
@ -239,7 +236,6 @@ class MissionGenerator:
self.time, self.time,
self.radio_registry, self.radio_registry,
self.tacan_registry, self.tacan_registry,
self.laser_code_registry,
self.unit_map, self.unit_map,
mission_data=self.mission_data, mission_data=self.mission_data,
helipads=tgo_generator.helipads, helipads=tgo_generator.helipads,

View File

@ -87,6 +87,7 @@ if TYPE_CHECKING:
from game import Game from game import Game
from game.ato.flighttype import FlightType from game.ato.flighttype import FlightType
from game.coalition import Coalition from game.coalition import Coalition
from game.lasercodes.lasercoderegistry import LaserCodeRegistry
from game.sim import GameUpdateEvents from game.sim import GameUpdateEvents
from game.squadrons.squadron import Squadron from game.squadrons.squadron import Squadron
from game.transfers import PendingTransfers from game.transfers import PendingTransfers
@ -430,42 +431,50 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
assert self._front_line_db is None assert self._front_line_db is None
self._front_line_db = game.db.front_lines 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 # 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. # the entire game state when it comes up.
from game.sim import GameUpdateEvents from game.sim import GameUpdateEvents
self._create_missing_front_lines(GameUpdateEvents()) self._create_missing_front_lines(laser_code_registry, GameUpdateEvents())
@property @property
def front_line_db(self) -> Database[FrontLine]: def front_line_db(self) -> Database[FrontLine]:
assert self._front_line_db is not None assert self._front_line_db is not None
return self._front_line_db 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(): for connection in self.convoy_routes.keys():
if not connection.front_line_active_with( if not connection.front_line_active_with(
self self
) and not connection.is_friendly_to(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( def _create_front_line_with(
self, connection: ControlPoint, events: GameUpdateEvents self,
laser_code_registry: LaserCodeRegistry,
connection: ControlPoint,
events: GameUpdateEvents,
) -> None: ) -> None:
blue, red = FrontLine.sort_control_points(self, connection) 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 self.front_lines[connection] = front
connection.front_lines[self] = front connection.front_lines[self] = front
self.front_line_db.add(front.id, front) self.front_line_db.add(front.id, front)
events.update_front_line(front) events.update_front_line(front)
def _remove_front_line_with( def _remove_front_line_with(
self, connection: ControlPoint, events: GameUpdateEvents self,
connection: ControlPoint,
events: GameUpdateEvents,
) -> None: ) -> None:
front = self.front_lines[connection] front = self.front_lines[connection]
del self.front_lines[connection] del self.front_lines[connection]
del connection.front_lines[self] del connection.front_lines[self]
self.front_line_db.remove(front.id) self.front_line_db.remove(front.id)
front.laser_code.release()
events.delete_front_line(front) events.delete_front_line(front)
def _clear_front_lines(self, events: GameUpdateEvents) -> None: def _clear_front_lines(self, events: GameUpdateEvents) -> None:
@ -929,7 +938,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
self._coalition = new_coalition self._coalition = new_coalition
self.base.set_strength_to_minimum() self.base.set_strength_to_minimum()
self._clear_front_lines(events) 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) events.update_control_point(self)
# All the attached TGOs have either been depopulated or captured. Tell the UI to # All the attached TGOs have either been depopulated or captured. Tell the UI to

View File

@ -8,6 +8,7 @@ from typing import Any, Iterator, List, TYPE_CHECKING, Tuple
from dcs.mapping import Point from dcs.mapping import Point
from .missiontarget import MissionTarget from .missiontarget import MissionTarget
from ..lasercodes.lasercode import LaserCode
from ..utils import Heading, pairwise from ..utils import Heading, pairwise
if TYPE_CHECKING: if TYPE_CHECKING:
@ -49,10 +50,12 @@ class FrontLine(MissionTarget):
self, self,
blue_point: ControlPoint, blue_point: ControlPoint,
red_point: ControlPoint, red_point: ControlPoint,
laser_code: LaserCode,
) -> None: ) -> None:
self.id = uuid.uuid4() self.id = uuid.uuid4()
self.blue_cp = blue_point self.blue_cp = blue_point
self.red_cp = red_point self.red_cp = red_point
self.laser_code = laser_code
try: try:
route = list(blue_point.convoy_route_to(red_point)) route = list(blue_point.convoy_route_to(red_point))
except KeyError: except KeyError:

View File

@ -194,6 +194,12 @@ class QFlightCreator(QDialog):
roster=roster, 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 # noinspection PyUnresolvedReferences
self.created.emit(flight) self.created.emit(flight)
self.accept() self.accept()