From 56a58646004d42b47429eaa1618e94eca8a63ea2 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 29 Sep 2020 01:51:00 -0700 Subject: [PATCH] Generate common ingress/egress points. This still isn't very good because it doesn't work well for anything but the automatically planned package. Instead, should be a part of the Package itself, generated the first time it is needed, and resettable by the user. --- gen/ato.py | 19 +++++-- gen/flights/ai_flight_planner.py | 2 +- gen/flights/flightplan.py | 96 ++++++++++++++++---------------- 3 files changed, 61 insertions(+), 56 deletions(-) diff --git a/gen/ato.py b/gen/ato.py index e82930fe..2ad8b6ec 100644 --- a/gen/ato.py +++ b/gen/ato.py @@ -11,7 +11,7 @@ the single CAP flight. from collections import defaultdict from dataclasses import dataclass, field import logging -from typing import Dict, List +from typing import Dict, Iterator, List, Optional from .flights.flight import Flight, FlightType from theater.missiontarget import MissionTarget @@ -48,10 +48,9 @@ class Package: self.flights.remove(flight) @property - def package_description(self) -> str: - """Generates a package description based on flight composition.""" + def primary_task(self) -> Optional[FlightType]: if not self.flights: - return "No mission" + return None flight_counts: Dict[FlightType, int] = defaultdict(lambda: 0) for flight in self.flights: @@ -84,13 +83,21 @@ class Package: ] for task in task_priorities: if flight_counts[task]: - return task.name + return task # If we get here, our task_priorities list above is incomplete. Log the # issue and return the type of *any* flight in the package. some_mission = next(iter(self.flights)).flight_type logging.warning(f"Unhandled mission type: {some_mission}") - return some_mission.name + return some_mission + + @property + def package_description(self) -> str: + """Generates a package description based on flight composition.""" + task = self.primary_task + if task is None: + return "No mission" + return task.name def __hash__(self) -> int: # TODO: Far from perfect. Number packages? diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 98ef2bd2..d2abd7fd 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -414,8 +414,8 @@ class CoalitionMissionPlanner: return package = builder.build() + builder = FlightPlanBuilder(self.game, self.is_player, package) for flight in package.flights: - builder = FlightPlanBuilder(self.game, self.is_player) builder.populate_flight_plan(flight, package.target) self.ato.add_package(package) diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index 6a213f7a..5e3dea03 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -11,10 +11,12 @@ import logging import random from typing import List, Optional, TYPE_CHECKING +from dcs.mapping import Point from dcs.unit import Unit from game.data.doctrine import Doctrine, MODERN_DOCTRINE from game.utils import nm_to_meter +from gen.ato import Package from theater import ControlPoint, FrontLine, MissionTarget, TheaterGroundObject from .closestairfields import ObjectiveDistanceCache from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType @@ -36,8 +38,10 @@ class InvalidObjectiveLocation(RuntimeError): class FlightPlanBuilder: """Generates flight plans for flights.""" - def __init__(self, game: Game, is_player: bool) -> None: + def __init__(self, game: Game, is_player: bool, + package: Optional[Package] = None) -> None: self.game = game + self.package = package self.is_player = is_player if is_player: faction = self.game.player_faction @@ -110,23 +114,9 @@ class FlightPlanBuilder: # TODO: Stop clobbering flight type. flight.flight_type = FlightType.STRIKE - heading = flight.from_cp.position.heading_between_point( - location.position - ) - ingress_heading = heading - 180 + 25 - - ingress_pos = location.position.point_from_heading( - ingress_heading, self.doctrine.ingress_egress_distance - ) - - egress_heading = heading - 180 - 25 - egress_pos = location.position.point_from_heading( - egress_heading, self.doctrine.ingress_egress_distance - ) - builder = WaypointBuilder(self.doctrine) builder.ascent(flight.from_cp) - builder.ingress_strike(ingress_pos, location) + builder.ingress_strike(self.ingress_point(flight, location), location) if len(location.groups) > 0 and location.dcs_identifier == "AA": # TODO: Replace with DEAD? @@ -153,7 +143,7 @@ class FlightPlanBuilder: location ) - builder.egress(egress_pos, location) + builder.egress(self.egress_point(flight, location), location) builder.rtb(flight.from_cp) flight.points = builder.build() @@ -267,23 +257,9 @@ class FlightPlanBuilder: flight.flight_type = random.choice([FlightType.SEAD, FlightType.DEAD]) - heading = flight.from_cp.position.heading_between_point( - location.position - ) - ingress_heading = heading - 180 + 25 - - ingress_pos = location.position.point_from_heading( - ingress_heading, self.doctrine.ingress_egress_distance - ) - - egress_heading = heading - 180 - 25 - egress_pos = location.position.point_from_heading( - egress_heading, self.doctrine.ingress_egress_distance - ) - builder = WaypointBuilder(self.doctrine) builder.ascent(flight.from_cp) - builder.ingress_sead(ingress_pos, location) + builder.ingress_sead(self.ingress_point(flight, location), location) # TODO: Unify these. # There doesn't seem to be any reason to treat the UI fragged missions @@ -307,7 +283,7 @@ class FlightPlanBuilder: else: builder.sead_area(location) - builder.egress(egress_pos, location) + builder.egress(self.egress_point(flight, location), location) builder.rtb(flight.from_cp) flight.points = builder.build() @@ -319,19 +295,6 @@ class FlightPlanBuilder: # Packages should determine some common points like push, ingress, # egress, and split points ahead of time so they can be shared by all # flights. - heading = flight.from_cp.position.heading_between_point( - location.position - ) - ingress_heading = heading - 180 + 25 - - ingress_pos = location.position.point_from_heading( - ingress_heading, self.doctrine.ingress_egress_distance - ) - - egress_heading = heading - 180 - 25 - egress_pos = location.position.point_from_heading( - egress_heading, self.doctrine.ingress_egress_distance - ) patrol_alt = random.randint( self.doctrine.min_patrol_altitude, @@ -340,7 +303,8 @@ class FlightPlanBuilder: builder = WaypointBuilder(self.doctrine) builder.ascent(flight.from_cp) - builder.race_track(ingress_pos, egress_pos, patrol_alt) + builder.race_track(self.ingress_point(flight, location), + self.egress_point(flight, location), patrol_alt) builder.rtb(flight.from_cp) flight.points = builder.build() @@ -396,8 +360,7 @@ class FlightPlanBuilder: builder.descent(arrival) return builder.build()[0] - @staticmethod - def generate_rtb_waypoint(arrival: ControlPoint) -> FlightWaypoint: + def generate_rtb_waypoint(self, arrival: ControlPoint) -> FlightWaypoint: """Generate RTB landing point. Args: @@ -406,3 +369,38 @@ class FlightPlanBuilder: builder = WaypointBuilder(self.doctrine) builder.land(arrival) return builder.build()[0] + + def ingress_point(self, flight: Flight, target: MissionTarget) -> Point: + heading = self._heading_to_package_airfield(flight, target) + return target.position.point_from_heading( + heading - 180 + 25, self.doctrine.ingress_egress_distance + ) + + def egress_point(self, flight: Flight, target: MissionTarget) -> Point: + heading = self._heading_to_package_airfield(flight, target) + return target.position.point_from_heading( + heading - 180 - 25, self.doctrine.ingress_egress_distance + ) + + def _heading_to_package_airfield(self, flight: Flight, + target: MissionTarget) -> int: + airfield = self.package_airfield(flight, target) + return airfield.position.heading_between_point(target.position) + + # TODO: Set ingress/egress/join/split points in the Package. + def package_airfield(self, flight: Flight, + target: MissionTarget) -> ControlPoint: + # The package airfield is either the flight's airfield (when there is no + # package) or the closest airfield to the objective that is the + # departure airfield for some flight in the package. + if self.package is None: + return flight.from_cp + + cache = ObjectiveDistanceCache.get_closest_airfields(target) + for airfield in cache.closest_airfields: + for flight in self.package.flights: + if flight.from_cp == airfield: + return airfield + raise RuntimeError( + "Could not find any airfield assigned to this package" + )