Set up JTAC channel assignments.

This commit is contained in:
Dan Albert 2021-07-31 12:59:42 -07:00
parent 119d4b9514
commit 8d68c10905
7 changed files with 101 additions and 88 deletions

View File

@ -16,11 +16,11 @@ from dcs.triggers import TriggerStart
from game.plugins import LuaPluginManager from game.plugins import LuaPluginManager
from game.theater.theatergroundobject import TheaterGroundObject from game.theater.theatergroundobject import TheaterGroundObject
from gen import Conflict, FlightType, VisualGenerator from gen import Conflict, FlightType, VisualGenerator, AirSupport
from gen.aircraft import AircraftConflictGenerator, FlightData from gen.aircraft import AircraftConflictGenerator, FlightData
from gen.airfields import AIRFIELD_DATA from gen.airfields import AIRFIELD_DATA
from gen.airsupportgen import AirSupport, AirSupportConflictGenerator from gen.airsupportgen import AirSupportConflictGenerator
from gen.armor import GroundConflictGenerator, JtacInfo from gen.armor import GroundConflictGenerator
from gen.beacons import load_beacons_for_terrain from gen.beacons import load_beacons_for_terrain
from gen.briefinggen import BriefingGenerator, MissionInfoGenerator from gen.briefinggen import BriefingGenerator, MissionInfoGenerator
from gen.cargoshipgen import CargoShipGenerator from gen.cargoshipgen import CargoShipGenerator
@ -58,8 +58,8 @@ class Operation:
enemy_awacs_enabled = True enemy_awacs_enabled = True
ca_slots = 1 ca_slots = 1
unit_map: UnitMap unit_map: UnitMap
jtacs: List[JtacInfo] = []
plugin_scripts: List[str] = [] plugin_scripts: List[str] = []
air_support = AirSupport()
@classmethod @classmethod
def prepare(cls, game: Game) -> None: def prepare(cls, game: Game) -> None:
@ -146,8 +146,7 @@ class Operation:
def notify_info_generators( def notify_info_generators(
cls, cls,
groundobjectgen: GroundObjectsGenerator, groundobjectgen: GroundObjectsGenerator,
airsupportgen: AirSupportConflictGenerator, air_support: AirSupport,
jtacs: List[JtacInfo],
airgen: AircraftConflictGenerator, airgen: AircraftConflictGenerator,
) -> None: ) -> None:
"""Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)""" """Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)"""
@ -160,15 +159,15 @@ class Operation:
for dynamic_runway in groundobjectgen.runways.values(): for dynamic_runway in groundobjectgen.runways.values():
gen.add_dynamic_runway(dynamic_runway) gen.add_dynamic_runway(dynamic_runway)
for tanker in airsupportgen.air_support.tankers: for tanker in air_support.tankers:
if tanker.blue: if tanker.blue:
gen.add_tanker(tanker) gen.add_tanker(tanker)
for aewc in airsupportgen.air_support.awacs: for aewc in air_support.awacs:
if aewc.blue: if aewc.blue:
gen.add_awacs(aewc) gen.add_awacs(aewc)
for jtac in jtacs: for jtac in air_support.jtacs:
if jtac.blue: if jtac.blue:
gen.add_jtac(jtac) gen.add_jtac(jtac)
@ -280,6 +279,7 @@ class Operation:
@classmethod @classmethod
def generate(cls) -> UnitMap: def generate(cls) -> UnitMap:
"""Build the final Mission to be exported""" """Build the final Mission to be exported"""
cls.air_support = AirSupport()
cls.create_unit_map() cls.create_unit_map()
cls.create_radio_registries() cls.create_radio_registries()
# Set mission time and weather conditions. # Set mission time and weather conditions.
@ -288,10 +288,10 @@ class Operation:
cls._generate_transports() cls._generate_transports()
cls._generate_destroyed_units() cls._generate_destroyed_units()
cls._generate_air_units() cls._generate_air_units()
cls._generate_ground_conflicts()
cls.assign_channels_to_flights( cls.assign_channels_to_flights(
cls.airgen.flights, cls.airsupportgen.air_support cls.airgen.flights, cls.airsupportgen.air_support
) )
cls._generate_ground_conflicts()
# Triggers # Triggers
triggersgen = TriggersGenerator(cls.current_mission, cls.game) triggersgen = TriggersGenerator(cls.current_mission, cls.game)
@ -311,7 +311,7 @@ class Operation:
if cls.game.settings.perf_smoke_gen: if cls.game.settings.perf_smoke_gen:
visualgen.generate() visualgen.generate()
cls.generate_lua(cls.airgen, cls.airsupportgen, cls.jtacs) cls.generate_lua(cls.airgen, cls.air_support)
# Inject Plugins Lua Scripts and data # Inject Plugins Lua Scripts and data
cls.plugin_scripts.clear() cls.plugin_scripts.clear()
@ -323,9 +323,7 @@ class Operation:
cls.assign_channels_to_flights( cls.assign_channels_to_flights(
cls.airgen.flights, cls.airsupportgen.air_support cls.airgen.flights, cls.airsupportgen.air_support
) )
cls.notify_info_generators( cls.notify_info_generators(cls.groundobjectgen, cls.air_support, cls.airgen)
cls.groundobjectgen, cls.airsupportgen, cls.jtacs, cls.airgen
)
cls.reset_naming_ids() cls.reset_naming_ids()
return cls.unit_map return cls.unit_map
@ -341,6 +339,7 @@ class Operation:
cls.game, cls.game,
cls.radio_registry, cls.radio_registry,
cls.tacan_registry, cls.tacan_registry,
cls.air_support,
) )
cls.airsupportgen.generate() cls.airsupportgen.generate()
@ -375,7 +374,6 @@ class Operation:
@classmethod @classmethod
def _generate_ground_conflicts(cls) -> None: def _generate_ground_conflicts(cls) -> None:
"""For each frontline in the Operation, generate the ground conflicts and JTACs""" """For each frontline in the Operation, generate the ground conflicts and JTACs"""
cls.jtacs = []
for front_line in cls.game.theater.conflicts(): for front_line in cls.game.theater.conflicts():
player_cp = front_line.blue_cp player_cp = front_line.blue_cp
enemy_cp = front_line.red_cp enemy_cp = front_line.red_cp
@ -400,9 +398,9 @@ class Operation:
enemy_cp.stances[player_cp.id], enemy_cp.stances[player_cp.id],
cls.unit_map, cls.unit_map,
cls.radio_registry, cls.radio_registry,
cls.air_support,
) )
ground_conflict_gen.generate() ground_conflict_gen.generate()
cls.jtacs.extend(ground_conflict_gen.jtacs)
@classmethod @classmethod
def _generate_transports(cls) -> None: def _generate_transports(cls) -> None:
@ -416,10 +414,7 @@ class Operation:
@classmethod @classmethod
def generate_lua( def generate_lua(
cls, cls, airgen: AircraftConflictGenerator, air_support: AirSupport
airgen: AircraftConflictGenerator,
airsupportgen: AirSupportConflictGenerator,
jtacs: List[JtacInfo],
) -> None: ) -> None:
# TODO: Refactor this # TODO: Refactor this
luaData = { luaData = {
@ -432,7 +427,7 @@ class Operation:
"BlueAA": {}, "BlueAA": {},
} # type: ignore } # type: ignore
for i, tanker in enumerate(airsupportgen.air_support.tankers): for i, tanker in enumerate(air_support.tankers):
luaData["Tankers"][i] = { luaData["Tankers"][i] = {
"dcsGroupName": tanker.group_name, "dcsGroupName": tanker.group_name,
"callsign": tanker.callsign, "callsign": tanker.callsign,
@ -441,14 +436,14 @@ class Operation:
"tacan": str(tanker.tacan.number) + tanker.tacan.band.name, "tacan": str(tanker.tacan.number) + tanker.tacan.band.name,
} }
for i, awacs in enumerate(airsupportgen.air_support.awacs): for i, awacs in enumerate(air_support.awacs):
luaData["AWACs"][i] = { luaData["AWACs"][i] = {
"dcsGroupName": awacs.group_name, "dcsGroupName": awacs.group_name,
"callsign": awacs.callsign, "callsign": awacs.callsign,
"radio": awacs.freq.mhz, "radio": awacs.freq.mhz,
} }
for i, jtac in enumerate(jtacs): for i, jtac in enumerate(air_support.jtacs):
luaData["JTACs"][i] = { luaData["JTACs"][i] = {
"dcsGroupName": jtac.group_name, "dcsGroupName": jtac.group_name,
"callsign": jtac.callsign, "callsign": jtac.callsign,

View File

@ -72,6 +72,9 @@ class CommonRadioChannelAllocator(RadioChannelAllocator):
for awacs in air_support.awacs: for awacs in air_support.awacs:
flight.assign_channel(radio_id, next(channel_alloc), awacs.freq) flight.assign_channel(radio_id, next(channel_alloc), awacs.freq)
for jtac in air_support.jtacs:
flight.assign_channel(radio_id, next(channel_alloc), jtac.freq)
if flight.arrival != flight.departure and flight.arrival.atc is not None: if flight.arrival != flight.departure and flight.arrival.atc is not None:
flight.assign_channel(radio_id, next(channel_alloc), flight.arrival.atc) flight.assign_channel(radio_id, next(channel_alloc), flight.arrival.atc)

View File

@ -81,7 +81,7 @@ from game.theater.missiontarget import MissionTarget
from game.theater.theatergroundobject import TheaterGroundObject from game.theater.theatergroundobject import TheaterGroundObject
from game.transfers import MultiGroupTransport from game.transfers import MultiGroupTransport
from game.unitmap import UnitMap from game.unitmap import UnitMap
from game.utils import Distance, Heading, meters, nautical_miles, pairwise from game.utils import Distance, meters, nautical_miles, pairwise
from gen.ato import AirTaskingOrder, Package from gen.ato import AirTaskingOrder, Package
from gen.callsigns import create_group_callsign_from_unit from gen.callsigns import create_group_callsign_from_unit
from gen.flights.flight import ( from gen.flights.flight import (
@ -93,7 +93,7 @@ from gen.flights.flight import (
from gen.radios import RadioFrequency, RadioRegistry from gen.radios import RadioFrequency, RadioRegistry
from gen.runways import RunwayData from gen.runways import RunwayData
from gen.tacan import TacanBand, TacanRegistry from gen.tacan import TacanBand, TacanRegistry
from .airsupportgen import AirSupport, AwacsInfo, TankerInfo from .airsupport import AirSupport, AwacsInfo, TankerInfo
from .callsigns import callsign_for_support_unit from .callsigns import callsign_for_support_unit
from .flights.flightplan import ( from .flights.flightplan import (
AwacsFlightPlan, AwacsFlightPlan,

55
gen/airsupport.py Normal file
View File

@ -0,0 +1,55 @@
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import timedelta
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from gen import RadioFrequency, TacanChannel
@dataclass
class AwacsInfo:
"""AWACS information for the kneeboard."""
group_name: str
callsign: str
freq: RadioFrequency
depature_location: Optional[str]
start_time: Optional[timedelta]
end_time: Optional[timedelta]
blue: bool
@dataclass
class TankerInfo:
"""Tanker information for the kneeboard."""
group_name: str
callsign: str
variant: str
freq: RadioFrequency
tacan: TacanChannel
start_time: Optional[timedelta]
end_time: Optional[timedelta]
blue: bool
@dataclass(frozen=True)
class JtacInfo:
"""JTAC information."""
group_name: str
unit_name: str
callsign: str
region: str
code: str
blue: bool
freq: RadioFrequency
@dataclass
class AirSupport:
awacs: list[AwacsInfo] = field(default_factory=list)
tankers: list[TankerInfo] = field(default_factory=list)
jtacs: list[JtacInfo] = field(default_factory=list)

View File

@ -1,9 +1,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from dataclasses import dataclass, field from typing import List, Type, Tuple, TYPE_CHECKING
from datetime import timedelta
from typing import List, Type, Tuple, Optional, TYPE_CHECKING
from dcs.mission import Mission, StartType from dcs.mission import Mission, StartType
from dcs.planes import IL_78M, KC130, KC135MPRS, KC_135, PlaneType from dcs.planes import IL_78M, KC130, KC135MPRS, KC_135, PlaneType
@ -18,14 +16,14 @@ from dcs.task import (
from dcs.unittype import UnitType from dcs.unittype import UnitType
from game.utils import Heading from game.utils import Heading
from .flights.ai_flight_planner_db import AEWC_CAPABLE from . import AirSupport
from .naming import namegen from .airsupport import TankerInfo, AwacsInfo
from .callsigns import callsign_for_support_unit from .callsigns import callsign_for_support_unit
from .conflictgen import Conflict from .conflictgen import Conflict
from .flights.ai_flight_planner_db import AEWC_CAPABLE from .flights.ai_flight_planner_db import AEWC_CAPABLE
from .naming import namegen from .naming import namegen
from .radios import RadioFrequency, RadioRegistry from .radios import RadioRegistry
from .tacan import TacanBand, TacanChannel, TacanRegistry from .tacan import TacanBand, TacanRegistry
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -38,39 +36,6 @@ AWACS_DISTANCE = 150000
AWACS_ALT = 13000 AWACS_ALT = 13000
@dataclass
class AwacsInfo:
"""AWACS information for the kneeboard."""
group_name: str
callsign: str
freq: RadioFrequency
depature_location: Optional[str]
start_time: Optional[timedelta]
end_time: Optional[timedelta]
blue: bool
@dataclass
class TankerInfo:
"""Tanker information for the kneeboard."""
group_name: str
callsign: str
variant: str
freq: RadioFrequency
tacan: TacanChannel
start_time: Optional[timedelta]
end_time: Optional[timedelta]
blue: bool
@dataclass
class AirSupport:
awacs: List[AwacsInfo] = field(default_factory=list)
tankers: List[TankerInfo] = field(default_factory=list)
class AirSupportConflictGenerator: class AirSupportConflictGenerator:
def __init__( def __init__(
self, self,
@ -79,13 +44,14 @@ class AirSupportConflictGenerator:
game: Game, game: Game,
radio_registry: RadioRegistry, radio_registry: RadioRegistry,
tacan_registry: TacanRegistry, tacan_registry: TacanRegistry,
air_support: AirSupport,
) -> None: ) -> None:
self.mission = mission self.mission = mission
self.conflict = conflict self.conflict = conflict
self.game = game self.game = game
self.air_support = AirSupport()
self.radio_registry = radio_registry self.radio_registry = radio_registry
self.tacan_registry = tacan_registry self.tacan_registry = tacan_registry
self.air_support = air_support
@classmethod @classmethod
def support_tasks(cls) -> List[Type[MainTask]]: def support_tasks(cls) -> List[Type[MainTask]]:
@ -94,12 +60,12 @@ class AirSupportConflictGenerator:
@staticmethod @staticmethod
def _get_tanker_params(unit_type: Type[UnitType]) -> Tuple[int, int]: def _get_tanker_params(unit_type: Type[UnitType]) -> Tuple[int, int]:
if unit_type is KC130: if unit_type is KC130:
return (TANKER_ALT - 500, 596) return TANKER_ALT - 500, 596
elif unit_type is KC_135: elif unit_type is KC_135:
return (TANKER_ALT, 770) return TANKER_ALT, 770
elif unit_type is KC135MPRS: elif unit_type is KC135MPRS:
return (TANKER_ALT + 500, 596) return TANKER_ALT + 500, 596
return (TANKER_ALT, 574) return TANKER_ALT, 574
def generate(self) -> None: def generate(self) -> None:
player_cp = ( player_cp = (

View File

@ -40,6 +40,7 @@ from gen.ground_forces.ai_ground_planner import (
CombatGroup, CombatGroup,
CombatGroupRole, CombatGroupRole,
) )
from .airsupport import AirSupport, JtacInfo
from .callsigns import callsign_for_support_unit from .callsigns import callsign_for_support_unit
from .conflictgen import Conflict from .conflictgen import Conflict
from .ground_forces.combat_stance import CombatStance from .ground_forces.combat_stance import CombatStance
@ -67,19 +68,6 @@ RANDOM_OFFSET_ATTACK = 250
INFANTRY_GROUP_SIZE = 5 INFANTRY_GROUP_SIZE = 5
@dataclass(frozen=True)
class JtacInfo:
"""JTAC information."""
group_name: str
unit_name: str
callsign: str
region: str
code: str
blue: bool
freq: RadioFrequency
class GroundConflictGenerator: class GroundConflictGenerator:
def __init__( def __init__(
self, self,
@ -92,6 +80,7 @@ class GroundConflictGenerator:
enemy_stance: CombatStance, enemy_stance: CombatStance,
unit_map: UnitMap, unit_map: UnitMap,
radio_registry: RadioRegistry, radio_registry: RadioRegistry,
air_support: AirSupport,
) -> None: ) -> None:
self.mission = mission self.mission = mission
self.conflict = conflict self.conflict = conflict
@ -102,7 +91,7 @@ class GroundConflictGenerator:
self.game = game self.game = game
self.unit_map = unit_map self.unit_map = unit_map
self.radio_registry = radio_registry self.radio_registry = radio_registry
self.jtacs: List[JtacInfo] = [] self.air_support = air_support
def generate(self) -> None: def generate(self) -> None:
position = Conflict.frontline_position( position = Conflict.frontline_position(
@ -151,7 +140,7 @@ class GroundConflictGenerator:
# Add JTAC # Add JTAC
if self.game.blue.faction.has_jtac: if self.game.blue.faction.has_jtac:
n = "JTAC" + str(self.conflict.blue_cp.id) + str(self.conflict.red_cp.id) n = "JTAC" + str(self.conflict.blue_cp.id) + str(self.conflict.red_cp.id)
code = 1688 - len(self.jtacs) code = 1688 - len(self.air_support.jtacs)
freq = self.radio_registry.alloc_uhf() freq = self.radio_registry.alloc_uhf()
utype = self.game.blue.faction.jtac_unit utype = self.game.blue.faction.jtac_unit
@ -168,7 +157,7 @@ class GroundConflictGenerator:
maintask=AFAC, maintask=AFAC,
) )
jtac.points[0].tasks.append( jtac.points[0].tasks.append(
FAC(callsign=len(self.jtacs) + 1, frequency=int(freq.mhz)) FAC(callsign=len(self.air_support.jtacs) + 1, frequency=int(freq.mhz))
) )
jtac.points[0].tasks.append(SetInvisibleCommand(True)) jtac.points[0].tasks.append(SetInvisibleCommand(True))
jtac.points[0].tasks.append(SetImmortalCommand(True)) jtac.points[0].tasks.append(SetImmortalCommand(True))
@ -180,7 +169,7 @@ class GroundConflictGenerator:
) )
# Note: Will need to change if we ever add ground based JTAC. # Note: Will need to change if we ever add ground based JTAC.
callsign = callsign_for_support_unit(jtac) callsign = callsign_for_support_unit(jtac)
self.jtacs.append( self.air_support.jtacs.append(
JtacInfo( JtacInfo(
str(jtac.name), str(jtac.name),
n, n,

View File

@ -408,6 +408,8 @@ class BriefingPage(KneeboardPage):
class SupportPage(KneeboardPage): class SupportPage(KneeboardPage):
"""A kneeboard page containing information about support units.""" """A kneeboard page containing information about support units."""
JTAC_REGION_MAX_LEN = 25
def __init__( def __init__(
self, self,
flight: FlightData, flight: FlightData,
@ -490,7 +492,10 @@ class SupportPage(KneeboardPage):
jtacs.append( jtacs.append(
[ [
jtac.callsign, jtac.callsign,
jtac.region, KneeboardPageWriter.wrap_line(
jtac.region,
self.JTAC_REGION_MAX_LEN,
),
jtac.code, jtac.code,
self.format_frequency(jtac.freq), self.format_frequency(jtac.freq),
] ]