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.
This commit is contained in:
Dan Albert 2020-09-29 01:51:00 -07:00
parent 07cbaa3e70
commit 56a5864600
3 changed files with 61 additions and 56 deletions

View File

@ -11,7 +11,7 @@ the single CAP flight.
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass, field from dataclasses import dataclass, field
import logging import logging
from typing import Dict, List from typing import Dict, Iterator, List, Optional
from .flights.flight import Flight, FlightType from .flights.flight import Flight, FlightType
from theater.missiontarget import MissionTarget from theater.missiontarget import MissionTarget
@ -48,10 +48,9 @@ class Package:
self.flights.remove(flight) self.flights.remove(flight)
@property @property
def package_description(self) -> str: def primary_task(self) -> Optional[FlightType]:
"""Generates a package description based on flight composition."""
if not self.flights: if not self.flights:
return "No mission" return None
flight_counts: Dict[FlightType, int] = defaultdict(lambda: 0) flight_counts: Dict[FlightType, int] = defaultdict(lambda: 0)
for flight in self.flights: for flight in self.flights:
@ -84,13 +83,21 @@ class Package:
] ]
for task in task_priorities: for task in task_priorities:
if flight_counts[task]: if flight_counts[task]:
return task.name return task
# If we get here, our task_priorities list above is incomplete. Log the # If we get here, our task_priorities list above is incomplete. Log the
# issue and return the type of *any* flight in the package. # issue and return the type of *any* flight in the package.
some_mission = next(iter(self.flights)).flight_type some_mission = next(iter(self.flights)).flight_type
logging.warning(f"Unhandled mission type: {some_mission}") 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: def __hash__(self) -> int:
# TODO: Far from perfect. Number packages? # TODO: Far from perfect. Number packages?

View File

@ -414,8 +414,8 @@ class CoalitionMissionPlanner:
return return
package = builder.build() package = builder.build()
builder = FlightPlanBuilder(self.game, self.is_player, package)
for flight in package.flights: for flight in package.flights:
builder = FlightPlanBuilder(self.game, self.is_player)
builder.populate_flight_plan(flight, package.target) builder.populate_flight_plan(flight, package.target)
self.ato.add_package(package) self.ato.add_package(package)

View File

@ -11,10 +11,12 @@ import logging
import random import random
from typing import List, Optional, TYPE_CHECKING from typing import List, Optional, TYPE_CHECKING
from dcs.mapping import Point
from dcs.unit import Unit from dcs.unit import Unit
from game.data.doctrine import Doctrine, MODERN_DOCTRINE from game.data.doctrine import Doctrine, MODERN_DOCTRINE
from game.utils import nm_to_meter from game.utils import nm_to_meter
from gen.ato import Package
from theater import ControlPoint, FrontLine, MissionTarget, TheaterGroundObject from theater import ControlPoint, FrontLine, MissionTarget, TheaterGroundObject
from .closestairfields import ObjectiveDistanceCache from .closestairfields import ObjectiveDistanceCache
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
@ -36,8 +38,10 @@ class InvalidObjectiveLocation(RuntimeError):
class FlightPlanBuilder: class FlightPlanBuilder:
"""Generates flight plans for flights.""" """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.game = game
self.package = package
self.is_player = is_player self.is_player = is_player
if is_player: if is_player:
faction = self.game.player_faction faction = self.game.player_faction
@ -110,23 +114,9 @@ class FlightPlanBuilder:
# TODO: Stop clobbering flight type. # TODO: Stop clobbering flight type.
flight.flight_type = FlightType.STRIKE 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 = WaypointBuilder(self.doctrine)
builder.ascent(flight.from_cp) 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": if len(location.groups) > 0 and location.dcs_identifier == "AA":
# TODO: Replace with DEAD? # TODO: Replace with DEAD?
@ -153,7 +143,7 @@ class FlightPlanBuilder:
location location
) )
builder.egress(egress_pos, location) builder.egress(self.egress_point(flight, location), location)
builder.rtb(flight.from_cp) builder.rtb(flight.from_cp)
flight.points = builder.build() flight.points = builder.build()
@ -267,23 +257,9 @@ class FlightPlanBuilder:
flight.flight_type = random.choice([FlightType.SEAD, FlightType.DEAD]) 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 = WaypointBuilder(self.doctrine)
builder.ascent(flight.from_cp) builder.ascent(flight.from_cp)
builder.ingress_sead(ingress_pos, location) builder.ingress_sead(self.ingress_point(flight, location), location)
# TODO: Unify these. # TODO: Unify these.
# There doesn't seem to be any reason to treat the UI fragged missions # There doesn't seem to be any reason to treat the UI fragged missions
@ -307,7 +283,7 @@ class FlightPlanBuilder:
else: else:
builder.sead_area(location) builder.sead_area(location)
builder.egress(egress_pos, location) builder.egress(self.egress_point(flight, location), location)
builder.rtb(flight.from_cp) builder.rtb(flight.from_cp)
flight.points = builder.build() flight.points = builder.build()
@ -319,19 +295,6 @@ class FlightPlanBuilder:
# Packages should determine some common points like push, ingress, # Packages should determine some common points like push, ingress,
# egress, and split points ahead of time so they can be shared by all # egress, and split points ahead of time so they can be shared by all
# flights. # 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( patrol_alt = random.randint(
self.doctrine.min_patrol_altitude, self.doctrine.min_patrol_altitude,
@ -340,7 +303,8 @@ class FlightPlanBuilder:
builder = WaypointBuilder(self.doctrine) builder = WaypointBuilder(self.doctrine)
builder.ascent(flight.from_cp) 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) builder.rtb(flight.from_cp)
flight.points = builder.build() flight.points = builder.build()
@ -396,8 +360,7 @@ class FlightPlanBuilder:
builder.descent(arrival) builder.descent(arrival)
return builder.build()[0] return builder.build()[0]
@staticmethod def generate_rtb_waypoint(self, arrival: ControlPoint) -> FlightWaypoint:
def generate_rtb_waypoint(arrival: ControlPoint) -> FlightWaypoint:
"""Generate RTB landing point. """Generate RTB landing point.
Args: Args:
@ -406,3 +369,38 @@ class FlightPlanBuilder:
builder = WaypointBuilder(self.doctrine) builder = WaypointBuilder(self.doctrine)
builder.land(arrival) builder.land(arrival)
return builder.build()[0] 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"
)