diff --git a/game/operation/operation.py b/game/operation/operation.py index fc190510..59822312 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -166,6 +166,7 @@ class Operation: airgen: AircraftConflictGenerator, ): """Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)""" + gens: List[MissionInfoGenerator] = [ KneeboardGenerator(cls.current_mission, cls.game), BriefingGenerator(cls.current_mission, cls.game), @@ -177,9 +178,8 @@ class Operation: for tanker in airsupportgen.air_support.tankers: gen.add_tanker(tanker) - if cls.player_awacs_enabled: - for awacs in airsupportgen.air_support.awacs: - gen.add_awacs(awacs) + for aewc in airsupportgen.air_support.awacs: + gen.add_awacs(aewc) for jtac in jtacs: gen.add_jtac(jtac) @@ -378,7 +378,9 @@ class Operation: cls.game, cls.radio_registry, cls.unit_map, + air_support=cls.airsupportgen.air_support, ) + cls.airgen.clear_parking_slots() cls.airgen.generate_flights( diff --git a/gen/aircraft.py b/gen/aircraft.py index 7363668e..db3bdb65 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging import random -from dataclasses import dataclass +from dataclasses import dataclass, field from datetime import timedelta from functools import cached_property from typing import Dict, List, Optional, TYPE_CHECKING, Type, Union @@ -67,6 +67,8 @@ from dcs.task import ( Targets, Task, WeaponType, + AWACSTaskAction, + SetFrequencyCommand, ) from dcs.terrain.terrain import Airport, NoParkingSlotError from dcs.triggers import Event, TriggerOnce, TriggerRule @@ -88,7 +90,6 @@ from game.theater.controlpoint import ( from game.theater.theatergroundobject import TheaterGroundObject from game.unitmap import UnitMap from game.utils import Distance, meters, nautical_miles -from gen.airsupportgen import AirSupport from gen.ato import AirTaskingOrder, Package from gen.callsigns import create_group_callsign_from_unit from gen.flights.flight import ( @@ -104,9 +105,12 @@ from .flights.flightplan import ( LoiterFlightPlan, PatrollingFlightPlan, SweepFlightPlan, + AwacsFlightPlan, ) from .flights.traveltime import GroundSpeed, TotEstimator from .naming import namegen +from .airsupportgen import AirSupport, AwacsInfo +from .callsigns import callsign_for_support_unit if TYPE_CHECKING: from game import Game @@ -666,6 +670,7 @@ class AircraftConflictGenerator: game: Game, radio_registry: RadioRegistry, unit_map: UnitMap, + air_support: AirSupport, ) -> None: self.m = mission self.game = game @@ -673,6 +678,7 @@ class AircraftConflictGenerator: self.radio_registry = radio_registry self.unit_map = unit_map self.flights: List[FlightData] = [] + self.air_support = air_support @cached_property def use_client(self) -> bool: @@ -787,7 +793,10 @@ class AircraftConflictGenerator: OptReactOnThreat(OptReactOnThreat.Values.EvadeFire) ) - channel = self.get_intra_flight_channel(unit_type) + if flight.flight_type == FlightType.AEWC: + channel = self.radio_registry.alloc_uhf() + else: + channel = self.get_intra_flight_channel(unit_type) group.set_frequency(channel.mhz) divert = None @@ -824,6 +833,20 @@ class AircraftConflictGenerator: if unit_type in [Su_33, C_101EB, C_101CC]: self.set_reduced_fuel(flight, group, unit_type) + if isinstance(flight.flight_plan, AwacsFlightPlan): + callsign = callsign_for_support_unit(group) + + self.air_support.awacs.append( + AwacsInfo( + dcsGroupName=str(group.name), + callsign=callsign, + freq=channel, + depature_location=flight.departure.name, + end_time=flight.flight_plan.mission_departure_time, + start_time=flight.flight_plan.mission_start_time, + ) + ) + def _generate_at_airport( self, name: str, @@ -1356,7 +1379,16 @@ class AircraftConflictGenerator: dynamic_runways: Dict[str, RunwayData], ) -> None: group.task = AWACS.name + + if not isinstance(flight.flight_plan, AwacsFlightPlan): + logging.error( + f"Cannot configure AEW&C tasks for {flight} because it does not have an AEW&C flight plan." + ) + return + self._setup_group(group, AWACS, package, flight, dynamic_runways) + + # Awacs task action self.configure_behavior( group, react_on_threat=OptReactOnThreat.Values.EvadeFire, @@ -1364,6 +1396,8 @@ class AircraftConflictGenerator: restrict_jettison=True, ) + group.points[0].tasks.append(AWACSTaskAction()) + def configure_escort( self, group: FlyingGroup, diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py index 88520374..a0d9f75e 100644 --- a/gen/airsupportgen.py +++ b/gen/airsupportgen.py @@ -1,6 +1,7 @@ import logging from dataclasses import dataclass, field -from typing import List, Type, Tuple +from datetime import timedelta +from typing import List, Type, Tuple, Optional from dcs.mission import Mission, StartType from dcs.planes import IL_78M, KC130, KC135MPRS, KC_135 @@ -37,6 +38,9 @@ class AwacsInfo: dcsGroupName: str callsign: str freq: RadioFrequency + depature_location: Optional[str] + start_time: Optional[timedelta] + end_time: Optional[timedelta] @dataclass @@ -192,9 +196,12 @@ class AirSupportConflictGenerator: self.air_support.awacs.append( AwacsInfo( - str(awacs_flight.name), - callsign_for_support_unit(awacs_flight), - freq, + dcsGroupName=str(awacs_flight.name), + callsign=callsign_for_support_unit(awacs_flight), + freq=freq, + depature_location=None, + start_time=None, + end_time=None, ) ) else: diff --git a/gen/briefinggen.py b/gen/briefinggen.py index b1246c78..017c4e4e 100644 --- a/gen/briefinggen.py +++ b/gen/briefinggen.py @@ -20,6 +20,7 @@ from .ground_forces.combat_stance import CombatStance from .radios import RadioFrequency from .runways import RunwayData + if TYPE_CHECKING: from game import Game diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index acd95d65..2b929387 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -713,6 +713,10 @@ class AwacsFlightPlan(LoiterFlightPlan): if self.divert is not None: yield self.divert + @property + def mission_start_time(self) -> Optional[timedelta]: + return self.takeoff_time() + def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]: if waypoint == self.hold: return self.package.time_over_target diff --git a/gen/kneeboard.py b/gen/kneeboard.py index 14b4c9d4..b87f91cb 100644 --- a/gen/kneeboard.py +++ b/gen/kneeboard.py @@ -41,6 +41,7 @@ from .flights.flight import FlightWaypoint, FlightWaypointType from .radios import RadioFrequency from .runways import RunwayData + if TYPE_CHECKING: from game import Game @@ -285,6 +286,34 @@ class BriefingPage(KneeboardPage): ["Bingo", "Joker"], ) + # AEW&C + writer.heading("AEW&C") + aewc_ladder = [] + + for single_aewc in self.awacs: + + if single_aewc.depature_location is None: + dep = "-" + arr = "-" + else: + dep = self._format_time(single_aewc.start_time) + arr = self._format_time(single_aewc.end_time) + + aewc_ladder.append( + [ + str(single_aewc.callsign), + str(single_aewc.freq), + str(single_aewc.depature_location), + str(dep), + str(arr), + ] + ) + + writer.table( + aewc_ladder, + headers=["Callsign", "FREQ", "Depature", "ETD", "ETA"], + ) + # Package Section writer.heading("Comm ladder") comm_ladder = [] @@ -293,10 +322,6 @@ class BriefingPage(KneeboardPage): [comm.name, "", "", "", self.format_frequency(comm.freq)] ) - for a in self.awacs: - comm_ladder.append( - [a.callsign, "AWACS", "", "", self.format_frequency(a.freq)] - ) for tanker in self.tankers: comm_ladder.append( [ @@ -365,6 +390,12 @@ class BriefingPage(KneeboardPage): channel_name = namer.channel_name(channel.radio_id, channel.channel) return f"{channel_name} {frequency}" + def _format_time(self, time: Optional[datetime.timedelta]) -> str: + if time is None: + return "" + local_time = self.start_time + time + return local_time.strftime(f"%H:%M:%S") + class KneeboardGenerator(MissionInfoGenerator): """Creates kneeboard pages for each client flight in the mission."""