mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Add basic implementation for APP-6 SIDCs.
These will be used in conjunction with https://github.com/spatialillusions/milsymbol to simplify the specification of unit type to the front-end.
This commit is contained in:
parent
05f3f3636b
commit
41158543cf
276
game/sidc.py
Normal file
276
game/sidc.py
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
"""Implements Symbol Identification Codes (SIDCs) as defined by NATO APP-6(D).
|
||||||
|
|
||||||
|
This implementation only covers assembly of the identifier strings. The front-end is
|
||||||
|
responsible for drawing the icons.
|
||||||
|
|
||||||
|
The third ten digits (used for national modifications and additions not covered by
|
||||||
|
APP-6) are not implemented. The third set of ten digits are optional and will be omitted
|
||||||
|
from the output.
|
||||||
|
|
||||||
|
https://nso.nato.int/nso/nsdd/main/standards/ap-details/1912/EN
|
||||||
|
https://www.spatialillusions.com/milsymbol/docs/milsymbol-2525d.html
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import IntEnum, unique
|
||||||
|
|
||||||
|
# Version field defined by A.5.
|
||||||
|
VERSION = 10
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class Context(IntEnum):
|
||||||
|
"""Context field defined by A.6.."""
|
||||||
|
|
||||||
|
REALITY = 0
|
||||||
|
EXERCISE = 1
|
||||||
|
SIMULATION = 2
|
||||||
|
# 3-9 are reserved for future use.
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class StandardIdentity(IntEnum):
|
||||||
|
"""Standard identity field defined by A.6."""
|
||||||
|
|
||||||
|
PENDING = 0
|
||||||
|
UNKNOWN = 1
|
||||||
|
ASSUMED_FRIEND = 2
|
||||||
|
FRIEND = 3
|
||||||
|
NEUTRAL = 4
|
||||||
|
SUSPECT_JOKER = 5
|
||||||
|
HOSTILE_FAKER = 6
|
||||||
|
# 7-9 are reserved for future use.
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class SymbolSet(IntEnum):
|
||||||
|
"""Symbol set field defined by A.7."""
|
||||||
|
|
||||||
|
UNKNOWN = 0
|
||||||
|
AIR = 1
|
||||||
|
AIR_MISSILE = 2
|
||||||
|
SPACE = 5
|
||||||
|
SPACE_MISSILE = 6
|
||||||
|
LAND_UNIT = 10
|
||||||
|
LAND_CIVILIAN_UNIT_ORGANIZATION = 11
|
||||||
|
LAND_EQUIPMENT = 15
|
||||||
|
LAND_INSTALLATIONS = 20
|
||||||
|
CONTROL_MEASURE = 25
|
||||||
|
DISMOUNTED_INDIVIDUAL = 27
|
||||||
|
SEA_SURFACE = 30
|
||||||
|
SEA_SUBSURFACE = 35
|
||||||
|
MINE_WARFARE = 36
|
||||||
|
ACTIVITY_EVENT = 40
|
||||||
|
ATMOSPHERIC = 45
|
||||||
|
OCEANOGRAPHIC = 46
|
||||||
|
METEOROLOGICAL_SPACE = 47
|
||||||
|
SIGNALS_INTELLIGENCE_SPACE = 50
|
||||||
|
SIGNALS_INTELLIGENCE_AIR = 51
|
||||||
|
SIGNALS_INTELLIGENCE_LAND = 52
|
||||||
|
SIGNALS_INTELLIGENCE_SURFACE = 53
|
||||||
|
SIGNALS_INTELLIGENCE_SUBSURFACE = 54
|
||||||
|
CYBERSPACE_SPACE = 60
|
||||||
|
CYBERSPACE_AIR = 61
|
||||||
|
CYBERSPACE_LAND = 62
|
||||||
|
CYBERSPACE_SURFACE = 63
|
||||||
|
CYBERSPACE_SUBSURFACE = 64
|
||||||
|
VERSION_EXTENSION_FLAG = 99
|
||||||
|
# All other values reserved for future use.
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.value:02}"
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class Status(IntEnum):
|
||||||
|
"""Status field defined by A.8 Status."""
|
||||||
|
|
||||||
|
PRESENT = 0
|
||||||
|
PLANNED_ANTICIPATED_SUSPECT = 1
|
||||||
|
PRESENT_FULLY_CAPABLE = 2
|
||||||
|
PRESENT_DAMAGED = 3
|
||||||
|
PRESENT_DESTROYED = 4
|
||||||
|
PRESENT_FULL_TO_CAPACITY = 5
|
||||||
|
# 6-8 reserved for future use.
|
||||||
|
VERSION_EXTENSION_FLAG = 9
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class HeadquartersTaskForceDummy(IntEnum):
|
||||||
|
"""Headquarters/Task Force/Dummy field defined by A.9."""
|
||||||
|
|
||||||
|
NOT_APPLICABLE = 0
|
||||||
|
FEINT_DUMMY = 1
|
||||||
|
HEADQUARTERS = 2
|
||||||
|
FEINT_DUMMY_HEADQUARTERS = 3
|
||||||
|
TASK_FORCE = 4
|
||||||
|
FEINT_DUMMY_TASK_FORCE = 5
|
||||||
|
TASK_FORCE_HEADQUARTERS = 6
|
||||||
|
FEINT_DUMMY_TASK_FORCE_HEADQUARTERS = 7
|
||||||
|
# 8 reserved for future use.
|
||||||
|
VERSION_EXTENSION_FLAG = 9
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class Amplifier(IntEnum):
|
||||||
|
"""Unit Echelon/Equipment Mobility/Naval Towed Array Amplifier defined by A.10"""
|
||||||
|
|
||||||
|
UNKNOWN = 0
|
||||||
|
|
||||||
|
# Echelon at brigade and below
|
||||||
|
TEAM_CREW = 11
|
||||||
|
SQUAD = 12
|
||||||
|
SECTION = 13
|
||||||
|
PLATOON_DETACHMENT = 14
|
||||||
|
COMPANY_BATTERY_TROOP = 15
|
||||||
|
BATTALION_SQUADRON = 16
|
||||||
|
REGIMENT_GROUP = 17
|
||||||
|
BRIGADE = 18
|
||||||
|
VERSION_EXTENSION_FLAG = 19
|
||||||
|
|
||||||
|
# Echelon at brigade and above
|
||||||
|
DIVISION = 21
|
||||||
|
CORP_MARINE_EXPEDITIONARY_FORCE = 22
|
||||||
|
ARMY = 23
|
||||||
|
ARMY_GROUP_FRONT = 24
|
||||||
|
REGION_THEATRE = 25
|
||||||
|
COMMAND = 26
|
||||||
|
# 27-28 reserved for future use.
|
||||||
|
VERSION_EXTENSION_FLAG2 = 29
|
||||||
|
|
||||||
|
# Equipment mobility on land
|
||||||
|
WHEELED_LIMITED_CROSS_COUNTRY = 31
|
||||||
|
WHEELED_CROSS_COUNTRY = 32
|
||||||
|
TRACKED = 33
|
||||||
|
WHEELED_AND_TRAKCED_COMBINATION = 34
|
||||||
|
TOWED = 35
|
||||||
|
RAIL = 36
|
||||||
|
PACK_ANIMALS = 37
|
||||||
|
# 38 reserved for future use.
|
||||||
|
VERSION_EXTENSION_FLAG3 = 39
|
||||||
|
|
||||||
|
# Equipment mobility on snow
|
||||||
|
OVER_SNOW = 41
|
||||||
|
SLED = 42
|
||||||
|
# 3-8 reserved for future use.
|
||||||
|
VERSION_EXTENSION_FLAG4 = 49
|
||||||
|
|
||||||
|
# Equipment mobility on water
|
||||||
|
BARGE = 51
|
||||||
|
AMPHIBIOUS = 52
|
||||||
|
# 3-8 reserved for future use.
|
||||||
|
VERSION_EXTENSION_FLAG5 = 59
|
||||||
|
|
||||||
|
# Naval towed array
|
||||||
|
SHORT_TOWED_ARRAY = 61
|
||||||
|
LONG_TOWED_ARRAY = 62
|
||||||
|
# 3-8 reserved for future use.
|
||||||
|
VERSION_EXTENSION_FLAG6 = 69
|
||||||
|
|
||||||
|
# Leadership indicator
|
||||||
|
LEADER_INDIVIDUAL = 71
|
||||||
|
DEPUTY_INDIVIDUAL = 72
|
||||||
|
# 3-8 reserved for future use.
|
||||||
|
VERSION_EXTENSION_FLAG7 = 79
|
||||||
|
|
||||||
|
# 80-89 reserved for future use.
|
||||||
|
# 90-99 version extension flag.
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.value:02}"
|
||||||
|
|
||||||
|
|
||||||
|
class Entity(IntEnum):
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.value:06}"
|
||||||
|
|
||||||
|
|
||||||
|
# Entity types (the second set of ten digits are implemented as-needed. These are
|
||||||
|
# defined by section A.13. Entity/Entity Type/Entity Subtype and Sector 1 and Sector 2
|
||||||
|
# Modifiers. The specific entity enum used by the SIDC depends on the symbol set used.
|
||||||
|
@unique
|
||||||
|
class AirEntity(Entity):
|
||||||
|
"""Air Entity/Entity Type/Entity Subtype defined by table A-10."""
|
||||||
|
|
||||||
|
UNSPECIFIED = 0
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class LandInstallationEntity(Entity):
|
||||||
|
"""Land Installation Entity/Entity Type/Entity Subtype defined by table A-27."""
|
||||||
|
|
||||||
|
UNSPECIFIED = 0
|
||||||
|
|
||||||
|
MILITARY_BASE = 120802
|
||||||
|
AIPORT_AIR_BASE = 121301
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class SeaSurfaceEntity(Entity):
|
||||||
|
"""Sea Surface Entity/Entity Type/Entity Subtype defined by table A-34."""
|
||||||
|
|
||||||
|
UNSPECIFIED = 0
|
||||||
|
|
||||||
|
CARRIER = 120100
|
||||||
|
AMPHIBIOUS_ASSAULT_SHIP_GENERAL = 120303
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class UnknownEntity(Entity):
|
||||||
|
"""Fallback entity type used when the symbol set is not known."""
|
||||||
|
|
||||||
|
UNSPECIFIED = 0
|
||||||
|
|
||||||
|
|
||||||
|
class Modifier(IntEnum):
|
||||||
|
"""Fallback modifier used when the symbol set is not known."""
|
||||||
|
|
||||||
|
UNSPECIFIED = 0
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.value:02}"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SymbolIdentificationCode:
|
||||||
|
version = VERSION
|
||||||
|
context: Context = Context.REALITY
|
||||||
|
standard_identity: StandardIdentity = StandardIdentity.UNKNOWN
|
||||||
|
symbol_set: SymbolSet = SymbolSet.UNKNOWN
|
||||||
|
status: Status = Status.PRESENT
|
||||||
|
headquarters_task_force_dummy: HeadquartersTaskForceDummy = (
|
||||||
|
HeadquartersTaskForceDummy.NOT_APPLICABLE
|
||||||
|
)
|
||||||
|
amplifier: Amplifier = Amplifier.UNKNOWN
|
||||||
|
entity: Entity = UnknownEntity.UNSPECIFIED
|
||||||
|
sector_one_modifier = Modifier.UNSPECIFIED
|
||||||
|
sector_two_modifier = Modifier.UNSPECIFIED
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return "".join(
|
||||||
|
[
|
||||||
|
f"{self.version:02}",
|
||||||
|
str(self.context),
|
||||||
|
str(self.standard_identity),
|
||||||
|
str(self.symbol_set),
|
||||||
|
str(self.status),
|
||||||
|
str(self.headquarters_task_force_dummy),
|
||||||
|
str(self.amplifier),
|
||||||
|
str(self.entity),
|
||||||
|
str(self.sector_one_modifier),
|
||||||
|
str(self.sector_two_modifier),
|
||||||
|
]
|
||||||
|
)
|
||||||
17
tests/test_sidc.py
Normal file
17
tests/test_sidc.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from game.sidc import (
|
||||||
|
LandInstallationEntity,
|
||||||
|
StandardIdentity,
|
||||||
|
Status,
|
||||||
|
SymbolIdentificationCode,
|
||||||
|
SymbolSet,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sidc() -> None:
|
||||||
|
sidc = SymbolIdentificationCode(
|
||||||
|
standard_identity=StandardIdentity.FRIEND,
|
||||||
|
symbol_set=SymbolSet.LAND_INSTALLATIONS,
|
||||||
|
status=Status.PRESENT_DAMAGED,
|
||||||
|
entity=LandInstallationEntity.AIPORT_AIR_BASE,
|
||||||
|
)
|
||||||
|
assert str(sidc) == "10032030001213010000"
|
||||||
Loading…
x
Reference in New Issue
Block a user