diff --git a/changelog.md b/changelog.md index 4b794305..e8c4df03 100644 --- a/changelog.md +++ b/changelog.md @@ -49,6 +49,7 @@ Saves from 4.0.0 are compatible with 4.1.0. * **[Mission Generation]** Prevent the creation of a transfer order with 0 units for a rare situtation when a point was captured. * **[Mission Generation]** Planned transfers which will be impossible after a base capture will no longer prevent the mission result submit. * **[Mission Generation]** Fix occasional KeyError preventing mission generation when all units of the same type in a convoy were killed. +* **[Mission Generation]** Fixed a potential bug with laser code generation where it would generate invalid codes. * **[UI]** Statistics window tick marks are now always integers. * **[UI]** Statistics window now shows the correct info for the turn * **[UI]** Toggling custom loadout for an aircraft with no preset loadouts no longer breaks the flight. diff --git a/game/operation/operation.py b/game/operation/operation.py index 6bcd022c..05a0de0a 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -29,6 +29,7 @@ from gen.environmentgen import EnvironmentGenerator from gen.forcedoptionsgen import ForcedOptionsGenerator from gen.groundobjectsgen import GroundObjectsGenerator from gen.kneeboard import KneeboardGenerator +from gen.lasercoderegistry import LaserCodeRegistry from gen.naming import namegen from gen.radios import RadioFrequency, RadioRegistry from gen.tacan import TacanRegistry @@ -50,6 +51,7 @@ class Operation: groundobjectgen: GroundObjectsGenerator radio_registry: RadioRegistry tacan_registry: TacanRegistry + laser_code_registry: LaserCodeRegistry game: Game trigger_radius = TRIGGER_RADIUS_MEDIUM is_quick = None @@ -192,6 +194,10 @@ class Operation: for frequency in unique_map_frequencies: cls.radio_registry.reserve(frequency) + @classmethod + def create_laser_code_registry(cls) -> None: + cls.laser_code_registry = LaserCodeRegistry() + @classmethod def assign_channels_to_flights( cls, flights: List[FlightData], air_support: AirSupport @@ -282,6 +288,7 @@ class Operation: cls.air_support = AirSupport() cls.create_unit_map() cls.create_radio_registries() + cls.create_laser_code_registry() # Set mission time and weather conditions. EnvironmentGenerator(cls.current_mission, cls.game.conditions).generate() cls._generate_ground_units() @@ -399,6 +406,7 @@ class Operation: cls.unit_map, cls.radio_registry, cls.air_support, + cls.laser_code_registry, ) ground_conflict_gen.generate() diff --git a/gen/armor.py b/gen/armor.py index b17785f0..e13827a8 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -44,6 +44,7 @@ from .airsupport import AirSupport, JtacInfo from .callsigns import callsign_for_support_unit from .conflictgen import Conflict from .ground_forces.combat_stance import CombatStance +from .lasercoderegistry import LaserCodeRegistry from .naming import namegen from .radios import MHz, RadioFrequency, RadioRegistry @@ -81,6 +82,7 @@ class GroundConflictGenerator: unit_map: UnitMap, radio_registry: RadioRegistry, air_support: AirSupport, + laser_code_registry: LaserCodeRegistry, ) -> None: self.mission = mission self.conflict = conflict @@ -92,6 +94,7 @@ class GroundConflictGenerator: self.unit_map = unit_map self.radio_registry = radio_registry self.air_support = air_support + self.laser_code_registry = laser_code_registry def generate(self) -> None: position = Conflict.frontline_position( @@ -140,7 +143,7 @@ class GroundConflictGenerator: # Add JTAC if self.game.blue.faction.has_jtac: n = "JTAC" + str(self.conflict.blue_cp.id) + str(self.conflict.red_cp.id) - code = 1688 - len(self.air_support.jtacs) + code: int = self.laser_code_registry.get_next_laser_code() freq = self.radio_registry.alloc_uhf() utype = self.game.blue.faction.jtac_unit diff --git a/gen/lasercoderegistry.py b/gen/lasercoderegistry.py new file mode 100644 index 00000000..6872cb30 --- /dev/null +++ b/gen/lasercoderegistry.py @@ -0,0 +1,37 @@ +from collections import deque +from typing import Iterator + + +class OutOfLaserCodesError(RuntimeError): + def __init__(self) -> None: + super().__init__( + f"All JTAC laser codes have been allocated. No available codes." + ) + + +class LaserCodeRegistry: + def __init__(self) -> None: + self.allocated_codes: set[int] = set() + self.allocator: Iterator[int] = LaserCodeRegistry.__laser_code_generator() + + def get_next_laser_code(self) -> int: + try: + while (code := next(self.allocator)) in self.allocated_codes: + pass + self.allocated_codes.add(code) + return code + except StopIteration: + raise OutOfLaserCodesError + + @staticmethod + def __laser_code_generator() -> Iterator[int]: + # Valid laser codes are as follows + # First digit is always 1 + # Second digit is 5-7 + # Third and fourth digits are 1 - 8 + # We iterate backward (reversed()) so that 1687 follows 1688 + q = deque(int(oct(code)[2:]) + 11 for code in reversed(range(0o1500, 0o2000))) + + # We start with the default of 1688 and wrap around when we reach the end + q.rotate(-q.index(1688)) + return iter(q)