mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Create a checked, releasable type for laser codes.
The release behavior isn't used yet, but I'm working on pre-allocating laser codes for front lines and flights to make it easier for players to pick the laser codes for their weapons.
This commit is contained in:
parent
d3269bca93
commit
31289adb50
@ -1 +1,3 @@
|
||||
from .ilasercoderegistry import ILaserCodeRegistry
|
||||
from .lasercode import LaserCode
|
||||
from .lasercoderegistry import LaserCodeRegistry
|
||||
|
||||
17
game/lasercodes/ilasercoderegistry.py
Normal file
17
game/lasercodes/ilasercoderegistry.py
Normal file
@ -0,0 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .lasercode import LaserCode
|
||||
|
||||
|
||||
class ILaserCodeRegistry(ABC):
|
||||
@abstractmethod
|
||||
def alloc_laser_code(self) -> LaserCode:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def release_code(self, code: LaserCode) -> None:
|
||||
...
|
||||
56
game/lasercodes/lasercode.py
Normal file
56
game/lasercodes/lasercode.py
Normal file
@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .ilasercoderegistry import ILaserCodeRegistry
|
||||
|
||||
|
||||
class LaserCode:
|
||||
def __init__(self, code: int, registry: ILaserCodeRegistry) -> None:
|
||||
self.verify_laser_code(code)
|
||||
self.code = code
|
||||
self.registry = registry
|
||||
|
||||
def release(self) -> None:
|
||||
self.registry.release_code(self)
|
||||
|
||||
@staticmethod
|
||||
def verify_laser_code(code: int) -> None:
|
||||
# https://forum.dcs.world/topic/211574-valid-laser-codes/
|
||||
# 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
|
||||
|
||||
# Special case used by FC3 aircraft like the A-10A that is not valid for other
|
||||
# aircraft.
|
||||
if code == 1113:
|
||||
return
|
||||
|
||||
# Must be 4 digits with no leading 0
|
||||
if code < 1000 or code >= 2000:
|
||||
raise ValueError
|
||||
|
||||
# The first digit was already verified above. Isolate the remaining three
|
||||
# digits. The resulting list is ordered by significance, not printed position.
|
||||
digits = [code // 10**i % 10 for i in range(3)]
|
||||
|
||||
if digits[0] < 1 or digits[0] > 8:
|
||||
raise ValueError
|
||||
if digits[1] < 1 or digits[1] > 8:
|
||||
raise ValueError
|
||||
if digits[2] < 5 or digits[2] > 7:
|
||||
raise ValueError
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.code}"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, LaserCode):
|
||||
return False
|
||||
return self.code == other.code
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.code)
|
||||
@ -1,19 +1,33 @@
|
||||
import logging
|
||||
from collections import deque
|
||||
|
||||
from .ilasercoderegistry import ILaserCodeRegistry
|
||||
from .lasercode import LaserCode
|
||||
|
||||
class LaserCodeRegistry:
|
||||
|
||||
class LaserCodeRegistry(ILaserCodeRegistry):
|
||||
def __init__(self) -> None:
|
||||
self.allocated_codes: set[int] = set()
|
||||
self.available_codes = LaserCodeRegistry._all_valid_laser_codes()
|
||||
self.fc3_code = LaserCode(1113, self)
|
||||
|
||||
def alloc_laser_code(self) -> int:
|
||||
def alloc_laser_code(self) -> LaserCode:
|
||||
try:
|
||||
code = self.available_codes.popleft()
|
||||
self.allocated_codes.add(code)
|
||||
return code
|
||||
return LaserCode(code, self)
|
||||
except IndexError:
|
||||
raise RuntimeError("All laser codes have been allocated")
|
||||
|
||||
def release_code(self, code: LaserCode) -> None:
|
||||
if code.code in self.allocated_codes:
|
||||
self.allocated_codes.remove(code.code)
|
||||
self.available_codes.appendleft(code.code)
|
||||
else:
|
||||
logging.error(
|
||||
"attempted to release laser code %d which was not allocated", code.code
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _all_valid_laser_codes() -> deque[int]:
|
||||
# Valid laser codes are as follows
|
||||
|
||||
@ -133,7 +133,7 @@ class FlightGroupConfigurator:
|
||||
) -> 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())
|
||||
laser_codes.append(self.laser_code_registry.alloc_laser_code().code)
|
||||
else:
|
||||
laser_codes.append(None)
|
||||
|
||||
|
||||
@ -37,13 +37,13 @@ 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
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import Heading
|
||||
from .frontlineconflictdescription import FrontLineConflictDescription
|
||||
from game.lasercodes import LaserCodeRegistry
|
||||
from .missiondata import JtacInfo, MissionData
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -136,13 +136,12 @@ class FlotGenerator:
|
||||
|
||||
# Add JTAC
|
||||
if self.game.blue.faction.has_jtac:
|
||||
code: int
|
||||
freq = self.radio_registry.alloc_uhf()
|
||||
# If the option fc3LaserCode is enabled, force all JTAC
|
||||
# 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.lua_plugin_manager.is_option_enabled("ctld", "fc3LaserCode"):
|
||||
code = 1113
|
||||
code = self.laser_code_registry.fc3_code
|
||||
else:
|
||||
code = self.laser_code_registry.alloc_laser_code()
|
||||
|
||||
|
||||
75
tests/lasercodes/test_lasercode.py
Normal file
75
tests/lasercodes/test_lasercode.py
Normal file
@ -0,0 +1,75 @@
|
||||
import pytest
|
||||
|
||||
from game.lasercodes import ILaserCodeRegistry
|
||||
from game.lasercodes.lasercode import LaserCode
|
||||
|
||||
|
||||
class MockRegistry(ILaserCodeRegistry):
|
||||
def __init__(self) -> None:
|
||||
self.release_count = 0
|
||||
|
||||
def alloc_laser_code(self) -> LaserCode:
|
||||
raise NotImplementedError
|
||||
|
||||
def release_code(self, code: LaserCode) -> None:
|
||||
self.release_count += 1
|
||||
|
||||
|
||||
@pytest.fixture(name="registry")
|
||||
def mock_registry() -> MockRegistry:
|
||||
return MockRegistry()
|
||||
|
||||
|
||||
def test_lasercode_code(registry: ILaserCodeRegistry) -> None:
|
||||
|
||||
assert LaserCode(1688, registry).code == 1688
|
||||
|
||||
# 1113 doesn't comply to the rules, but is the only code valid for FC3 aircraft like
|
||||
# the A-10A.
|
||||
assert LaserCode(1113, registry).code == 1113
|
||||
|
||||
# The first digit must be 1
|
||||
with pytest.raises(ValueError):
|
||||
# And be exactly 4 digits
|
||||
LaserCode(2688, registry)
|
||||
|
||||
# The code must be exactly 4 digits
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(888, registry)
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(18888, registry)
|
||||
|
||||
# 0 and 9 are invalid digits
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(1088, registry)
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(1608, registry)
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(1680, registry)
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(1988, registry)
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(1698, registry)
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(1689, registry)
|
||||
|
||||
# The second digit is further constrained to be 5, 6, or 7.
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(1188, registry)
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(1288, registry)
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(1388, registry)
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(1488, registry)
|
||||
with pytest.raises(ValueError):
|
||||
LaserCode(1888, registry)
|
||||
|
||||
|
||||
def test_lasercode_release(registry: MockRegistry) -> None:
|
||||
code = LaserCode(1688, registry)
|
||||
assert registry.release_count == 0
|
||||
code.release()
|
||||
assert registry.release_count == 1
|
||||
code.release()
|
||||
assert registry.release_count == 2
|
||||
@ -10,6 +10,16 @@ def test_initial_laser_codes() -> None:
|
||||
|
||||
def test_alloc_laser_code() -> None:
|
||||
reg = LaserCodeRegistry()
|
||||
assert reg.alloc_laser_code() == 1688
|
||||
assert reg.alloc_laser_code().code == 1688
|
||||
assert 1688 not in reg.available_codes
|
||||
assert len(reg.available_codes) == 191
|
||||
|
||||
|
||||
def test_release_code() -> None:
|
||||
reg = LaserCodeRegistry()
|
||||
code = reg.alloc_laser_code()
|
||||
code.release()
|
||||
assert code.code in reg.available_codes
|
||||
assert len(reg.available_codes) == 192
|
||||
code.release()
|
||||
assert len(reg.available_codes) == 192
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user