diff --git a/game/operation/operation.py b/game/operation/operation.py index 5e259fb9..6bcd022c 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -16,11 +16,11 @@ from dcs.triggers import TriggerStart from game.plugins import LuaPluginManager 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.airfields import AIRFIELD_DATA -from gen.airsupportgen import AirSupport, AirSupportConflictGenerator -from gen.armor import GroundConflictGenerator, JtacInfo +from gen.airsupportgen import AirSupportConflictGenerator +from gen.armor import GroundConflictGenerator from gen.beacons import load_beacons_for_terrain from gen.briefinggen import BriefingGenerator, MissionInfoGenerator from gen.cargoshipgen import CargoShipGenerator @@ -58,8 +58,8 @@ class Operation: enemy_awacs_enabled = True ca_slots = 1 unit_map: UnitMap - jtacs: List[JtacInfo] = [] plugin_scripts: List[str] = [] + air_support = AirSupport() @classmethod def prepare(cls, game: Game) -> None: @@ -146,8 +146,7 @@ class Operation: def notify_info_generators( cls, groundobjectgen: GroundObjectsGenerator, - airsupportgen: AirSupportConflictGenerator, - jtacs: List[JtacInfo], + air_support: AirSupport, airgen: AircraftConflictGenerator, ) -> None: """Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)""" @@ -160,15 +159,15 @@ class Operation: for dynamic_runway in groundobjectgen.runways.values(): gen.add_dynamic_runway(dynamic_runway) - for tanker in airsupportgen.air_support.tankers: + for tanker in air_support.tankers: if tanker.blue: gen.add_tanker(tanker) - for aewc in airsupportgen.air_support.awacs: + for aewc in air_support.awacs: if aewc.blue: gen.add_awacs(aewc) - for jtac in jtacs: + for jtac in air_support.jtacs: if jtac.blue: gen.add_jtac(jtac) @@ -280,6 +279,7 @@ class Operation: @classmethod def generate(cls) -> UnitMap: """Build the final Mission to be exported""" + cls.air_support = AirSupport() cls.create_unit_map() cls.create_radio_registries() # Set mission time and weather conditions. @@ -288,10 +288,10 @@ class Operation: cls._generate_transports() cls._generate_destroyed_units() cls._generate_air_units() + cls._generate_ground_conflicts() cls.assign_channels_to_flights( cls.airgen.flights, cls.airsupportgen.air_support ) - cls._generate_ground_conflicts() # Triggers triggersgen = TriggersGenerator(cls.current_mission, cls.game) @@ -311,7 +311,7 @@ class Operation: if cls.game.settings.perf_smoke_gen: 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 cls.plugin_scripts.clear() @@ -323,9 +323,7 @@ class Operation: cls.assign_channels_to_flights( cls.airgen.flights, cls.airsupportgen.air_support ) - cls.notify_info_generators( - cls.groundobjectgen, cls.airsupportgen, cls.jtacs, cls.airgen - ) + cls.notify_info_generators(cls.groundobjectgen, cls.air_support, cls.airgen) cls.reset_naming_ids() return cls.unit_map @@ -341,6 +339,7 @@ class Operation: cls.game, cls.radio_registry, cls.tacan_registry, + cls.air_support, ) cls.airsupportgen.generate() @@ -375,7 +374,6 @@ class Operation: @classmethod def _generate_ground_conflicts(cls) -> None: """For each frontline in the Operation, generate the ground conflicts and JTACs""" - cls.jtacs = [] for front_line in cls.game.theater.conflicts(): player_cp = front_line.blue_cp enemy_cp = front_line.red_cp @@ -400,9 +398,9 @@ class Operation: enemy_cp.stances[player_cp.id], cls.unit_map, cls.radio_registry, + cls.air_support, ) ground_conflict_gen.generate() - cls.jtacs.extend(ground_conflict_gen.jtacs) @classmethod def _generate_transports(cls) -> None: @@ -416,10 +414,7 @@ class Operation: @classmethod def generate_lua( - cls, - airgen: AircraftConflictGenerator, - airsupportgen: AirSupportConflictGenerator, - jtacs: List[JtacInfo], + cls, airgen: AircraftConflictGenerator, air_support: AirSupport ) -> None: # TODO: Refactor this luaData = { @@ -432,7 +427,7 @@ class Operation: "BlueAA": {}, } # type: ignore - for i, tanker in enumerate(airsupportgen.air_support.tankers): + for i, tanker in enumerate(air_support.tankers): luaData["Tankers"][i] = { "dcsGroupName": tanker.group_name, "callsign": tanker.callsign, @@ -441,14 +436,14 @@ class Operation: "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] = { "dcsGroupName": awacs.group_name, "callsign": awacs.callsign, "radio": awacs.freq.mhz, } - for i, jtac in enumerate(jtacs): + for i, jtac in enumerate(air_support.jtacs): luaData["JTACs"][i] = { "dcsGroupName": jtac.group_name, "callsign": jtac.callsign, diff --git a/game/radio/channels.py b/game/radio/channels.py index 83df8e6c..4fbf7e23 100644 --- a/game/radio/channels.py +++ b/game/radio/channels.py @@ -72,6 +72,9 @@ class CommonRadioChannelAllocator(RadioChannelAllocator): for awacs in air_support.awacs: 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: flight.assign_channel(radio_id, next(channel_alloc), flight.arrival.atc) diff --git a/gen/aircraft.py b/gen/aircraft.py index 998696ac..e82893e1 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -81,7 +81,7 @@ from game.theater.missiontarget import MissionTarget from game.theater.theatergroundobject import TheaterGroundObject from game.transfers import MultiGroupTransport 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.callsigns import create_group_callsign_from_unit from gen.flights.flight import ( @@ -93,7 +93,7 @@ from gen.flights.flight import ( from gen.radios import RadioFrequency, RadioRegistry from gen.runways import RunwayData 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 .flights.flightplan import ( AwacsFlightPlan, diff --git a/gen/airsupport.py b/gen/airsupport.py new file mode 100644 index 00000000..1ce520de --- /dev/null +++ b/gen/airsupport.py @@ -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) diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py index 72f4fecc..2f20a7c3 100644 --- a/gen/airsupportgen.py +++ b/gen/airsupportgen.py @@ -1,9 +1,7 @@ from __future__ import annotations import logging -from dataclasses import dataclass, field -from datetime import timedelta -from typing import List, Type, Tuple, Optional, TYPE_CHECKING +from typing import List, Type, Tuple, TYPE_CHECKING from dcs.mission import Mission, StartType 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 game.utils import Heading -from .flights.ai_flight_planner_db import AEWC_CAPABLE -from .naming import namegen +from . import AirSupport +from .airsupport import TankerInfo, AwacsInfo from .callsigns import callsign_for_support_unit from .conflictgen import Conflict from .flights.ai_flight_planner_db import AEWC_CAPABLE from .naming import namegen -from .radios import RadioFrequency, RadioRegistry -from .tacan import TacanBand, TacanChannel, TacanRegistry +from .radios import RadioRegistry +from .tacan import TacanBand, TacanRegistry if TYPE_CHECKING: from game import Game @@ -38,39 +36,6 @@ AWACS_DISTANCE = 150000 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: def __init__( self, @@ -79,13 +44,14 @@ class AirSupportConflictGenerator: game: Game, radio_registry: RadioRegistry, tacan_registry: TacanRegistry, + air_support: AirSupport, ) -> None: self.mission = mission self.conflict = conflict self.game = game - self.air_support = AirSupport() self.radio_registry = radio_registry self.tacan_registry = tacan_registry + self.air_support = air_support @classmethod def support_tasks(cls) -> List[Type[MainTask]]: @@ -94,12 +60,12 @@ class AirSupportConflictGenerator: @staticmethod def _get_tanker_params(unit_type: Type[UnitType]) -> Tuple[int, int]: if unit_type is KC130: - return (TANKER_ALT - 500, 596) + return TANKER_ALT - 500, 596 elif unit_type is KC_135: - return (TANKER_ALT, 770) + return TANKER_ALT, 770 elif unit_type is KC135MPRS: - return (TANKER_ALT + 500, 596) - return (TANKER_ALT, 574) + return TANKER_ALT + 500, 596 + return TANKER_ALT, 574 def generate(self) -> None: player_cp = ( diff --git a/gen/armor.py b/gen/armor.py index 18570913..b17785f0 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -40,6 +40,7 @@ from gen.ground_forces.ai_ground_planner import ( CombatGroup, CombatGroupRole, ) +from .airsupport import AirSupport, JtacInfo from .callsigns import callsign_for_support_unit from .conflictgen import Conflict from .ground_forces.combat_stance import CombatStance @@ -67,19 +68,6 @@ RANDOM_OFFSET_ATTACK = 250 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: def __init__( self, @@ -92,6 +80,7 @@ class GroundConflictGenerator: enemy_stance: CombatStance, unit_map: UnitMap, radio_registry: RadioRegistry, + air_support: AirSupport, ) -> None: self.mission = mission self.conflict = conflict @@ -102,7 +91,7 @@ class GroundConflictGenerator: self.game = game self.unit_map = unit_map self.radio_registry = radio_registry - self.jtacs: List[JtacInfo] = [] + self.air_support = air_support def generate(self) -> None: position = Conflict.frontline_position( @@ -151,7 +140,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.jtacs) + code = 1688 - len(self.air_support.jtacs) freq = self.radio_registry.alloc_uhf() utype = self.game.blue.faction.jtac_unit @@ -168,7 +157,7 @@ class GroundConflictGenerator: maintask=AFAC, ) 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(SetImmortalCommand(True)) @@ -180,7 +169,7 @@ class GroundConflictGenerator: ) # Note: Will need to change if we ever add ground based JTAC. callsign = callsign_for_support_unit(jtac) - self.jtacs.append( + self.air_support.jtacs.append( JtacInfo( str(jtac.name), n, diff --git a/gen/kneeboard.py b/gen/kneeboard.py index 9c35d662..94dbb3c2 100644 --- a/gen/kneeboard.py +++ b/gen/kneeboard.py @@ -408,6 +408,8 @@ class BriefingPage(KneeboardPage): class SupportPage(KneeboardPage): """A kneeboard page containing information about support units.""" + JTAC_REGION_MAX_LEN = 25 + def __init__( self, flight: FlightData, @@ -490,7 +492,10 @@ class SupportPage(KneeboardPage): jtacs.append( [ jtac.callsign, - jtac.region, + KneeboardPageWriter.wrap_line( + jtac.region, + self.JTAC_REGION_MAX_LEN, + ), jtac.code, self.format_frequency(jtac.freq), ]