Refactor flight plan generation.

This commit is contained in:
Dan Albert 2020-09-29 00:58:19 -07:00
parent 8b717c4f4c
commit cc7c2cc707
8 changed files with 433 additions and 359 deletions

View File

@ -5,6 +5,7 @@ from game.inventory import GlobalAircraftInventory
from game.models.game_stats import GameStats
from gen.ato import AirTaskingOrder
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.ground_forces.ai_ground_planner import GroundPlanner
from .event import *
from .settings import Settings
@ -204,6 +205,7 @@ class Game:
return event and event.name and event.name == self.player_name
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint] = None):
ObjectiveDistanceCache.set_theater(self.theater)
logging.info("Pass turn")
self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0))

View File

@ -185,20 +185,16 @@ class Operation:
self.airsupportgen.generate(self.is_awacs_enabled)
# Generate Activity on the map
for cp in self.game.theater.controlpoints:
side = cp.captured
if side:
country = self.current_mission.country(self.game.player_country)
ato = self.game.blue_ato
else:
country = self.current_mission.country(self.game.enemy_country)
ato = self.game.red_ato
self.airgen.generate_flights(
cp,
country,
ato,
self.groundobjectgen.runways
)
self.airgen.generate_flights(
self.current_mission.country(self.game.player_country),
self.game.blue_ato,
self.groundobjectgen.runways
)
self.airgen.generate_flights(
self.current_mission.country(self.game.enemy_country),
self.game.red_ato,
self.groundobjectgen.runways
)
# Generate ground units on frontline everywhere
jtacs: List[JtacInfo] = []

View File

@ -757,7 +757,7 @@ class AircraftConflictGenerator:
for parking_slot in cp.airport.parking_slots:
parking_slot.unit_id = None
def generate_flights(self, cp, country, ato: AirTaskingOrder,
def generate_flights(self, country, ato: AirTaskingOrder,
dynamic_runways: Dict[str, RunwayData]) -> None:
self.clear_parking_slots()
@ -768,7 +768,8 @@ class AircraftConflictGenerator:
logging.info("Flight not generated: culled")
continue
logging.info(f"Generating flight: {flight.unit_type}")
group = self.generate_planned_flight(cp, country, flight)
group = self.generate_planned_flight(flight.from_cp, country,
flight)
self.setup_flight_group(group, flight, flight.flight_type,
dynamic_runways)
self.setup_group_activation_trigger(flight, group)

View File

@ -106,7 +106,7 @@ class BriefingGenerator(MissionInfoGenerator):
aircraft = flight.aircraft_type
flight_unit_name = db.unit_type_name(aircraft)
self.description += "-" * 50 + "\n"
self.description += f"{flight_unit_name} x {flight.size + 2}\n\n"
self.description += f"{flight_unit_name} x {flight.size}\n\n"
for i, wpt in enumerate(flight.waypoints):
self.description += f"#{i + 1} -- {wpt.name} : {wpt.description}\n"

View File

@ -19,6 +19,10 @@ from gen.flights.ai_flight_planner_db import (
SEAD_CAPABLE,
STRIKE_CAPABLE,
)
from gen.flights.closestairfields import (
ClosestAirfields,
ObjectiveDistanceCache,
)
from gen.flights.flight import (
Flight,
FlightType,
@ -37,29 +41,6 @@ if TYPE_CHECKING:
from game.inventory import GlobalAircraftInventory
class ClosestAirfields:
"""Precalculates which control points are closes to the given target."""
def __init__(self, target: MissionTarget,
all_control_points: List[ControlPoint]) -> None:
self.target = target
self.closest_airfields: List[ControlPoint] = sorted(
all_control_points, key=lambda c: self.target.distance_to(c)
)
def airfields_within(self, meters: int) -> Iterator[ControlPoint]:
"""Iterates over all airfields within the given range of the target.
Note that this iterates over *all* airfields, not just friendly
airfields.
"""
for cp in self.closest_airfields:
if cp.distance_to(self.target) < meters:
yield cp
else:
break
@dataclass(frozen=True)
class ProposedFlight:
"""A flight outline proposed by the mission planner.
@ -208,11 +189,6 @@ class ObjectiveFinder:
def __init__(self, game: Game, is_player: bool) -> None:
self.game = game
self.is_player = is_player
# TODO: Cache globally at startup to avoid generating twice per turn?
self.closest_airfields: Dict[str, ClosestAirfields] = {
t.name: ClosestAirfields(t, self.game.theater.controlpoints)
for t in self.all_possible_targets()
}
def enemy_sams(self) -> Iterator[TheaterGroundObject]:
"""Iterates over all enemy SAM sites."""
@ -303,7 +279,7 @@ class ObjectiveFinder:
CP.
"""
for cp in self.friendly_control_points():
airfields_in_proximity = self.closest_airfields[cp.name]
airfields_in_proximity = self.closest_airfields_to(cp)
airfields_in_threat_range = airfields_in_proximity.airfields_within(
self.AIRFIELD_THREAT_RANGE
)
@ -336,7 +312,7 @@ class ObjectiveFinder:
def closest_airfields_to(self, location: MissionTarget) -> ClosestAirfields:
"""Returns the closest airfields to the given location."""
return self.closest_airfields[location.name]
return ObjectiveDistanceCache.get_closest_airfields(location)
class CoalitionMissionPlanner:

View File

@ -0,0 +1,51 @@
"""Objective adjacency lists."""
from typing import Dict, Iterator, List, Optional
from theater import ConflictTheater, ControlPoint, MissionTarget
class ClosestAirfields:
"""Precalculates which control points are closes to the given target."""
def __init__(self, target: MissionTarget,
all_control_points: List[ControlPoint]) -> None:
self.target = target
self.closest_airfields: List[ControlPoint] = sorted(
all_control_points, key=lambda c: self.target.distance_to(c)
)
def airfields_within(self, meters: int) -> Iterator[ControlPoint]:
"""Iterates over all airfields within the given range of the target.
Note that this iterates over *all* airfields, not just friendly
airfields.
"""
for cp in self.closest_airfields:
if cp.distance_to(self.target) < meters:
yield cp
else:
break
class ObjectiveDistanceCache:
theater: Optional[ConflictTheater] = None
closest_airfields: Dict[str, ClosestAirfields] = {}
@classmethod
def set_theater(cls, theater: ConflictTheater) -> None:
if cls.theater is not None:
cls.closest_airfields = {}
cls.theater = theater
@classmethod
def get_closest_airfields(cls, location: MissionTarget) -> ClosestAirfields:
if cls.theater is None:
raise RuntimeError(
"Call ObjectiveDistanceCache.set_theater before using"
)
if location.name not in cls.closest_airfields:
cls.closest_airfields[location.name] = ClosestAirfields(
location, cls.theater.controlpoints
)
return cls.closest_airfields[location.name]

View File

@ -11,13 +11,15 @@ import logging
import random
from typing import List, Optional, TYPE_CHECKING
from game.data.doctrine import Doctrine, MODERN_DOCTRINE
from .flight import Flight, FlightType, FlightWaypointType, FlightWaypoint
from ..conflictgen import Conflict
from theater import ControlPoint, FrontLine, MissionTarget, TheaterGroundObject
from game.utils import nm_to_meter
from dcs.unit import Unit
from game.data.doctrine import Doctrine, MODERN_DOCTRINE
from game.utils import nm_to_meter
from theater import ControlPoint, FrontLine, MissionTarget, TheaterGroundObject
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
from .waypointbuilder import WaypointBuilder
from ..conflictgen import Conflict
if TYPE_CHECKING:
from game import Game
@ -103,108 +105,54 @@ class FlightPlanBuilder:
# TODO: Stop clobbering flight type.
flight.flight_type = FlightType.STRIKE
ascend = self.generate_ascend_point(flight.from_cp)
flight.points.append(ascend)
heading = flight.from_cp.position.heading_between_point(
location.position
)
ingress_heading = heading - 180 + 25
egress_heading = heading - 180 - 25
ingress_pos = location.position.point_from_heading(
ingress_heading, self.doctrine.ingress_egress_distance
)
ingress_point = FlightWaypoint(
FlightWaypointType.INGRESS_STRIKE,
ingress_pos.x,
ingress_pos.y,
self.doctrine.ingress_altitude
)
ingress_point.pretty_name = "INGRESS on " + location.name
ingress_point.description = "INGRESS on " + location.name
ingress_point.name = "INGRESS"
flight.points.append(ingress_point)
if len(location.groups) > 0 and location.dcs_identifier == "AA":
for g in location.groups:
for j, u in enumerate(g.units):
point = FlightWaypoint(
FlightWaypointType.TARGET_POINT,
u.position.x,
u.position.y,
0
)
point.description = (
f"STRIKE [{location.name}] : {u.type} #{j}"
)
point.pretty_name = (
f"STRIKE [{location.name}] : {u.type} #{j}"
)
point.name = f"{location.name} #{j}"
point.only_for_player = True
ingress_point.targets.append(location)
flight.points.append(point)
else:
if hasattr(location, "obj_name"):
buildings = self.game.theater.find_ground_objects_by_obj_name(
location.obj_name
)
for building in buildings:
if building.is_dead:
continue
point = FlightWaypoint(
FlightWaypointType.TARGET_POINT,
building.position.x,
building.position.y,
0
)
point.description = (
f"STRIKE on {building.obj_name} {building.category} "
f"[{building.dcs_identifier}]"
)
point.pretty_name = (
f"STRIKE on {building.obj_name} {building.category} "
f"[{building.dcs_identifier}]"
)
point.name = building.obj_name
point.only_for_player = True
ingress_point.targets.append(building)
flight.points.append(point)
else:
point = FlightWaypoint(
FlightWaypointType.TARGET_GROUP_LOC,
location.position.x,
location.position.y,
0
)
point.description = "STRIKE on " + location.name
point.pretty_name = "STRIKE on " + location.name
point.name = location.name
point.only_for_player = True
ingress_point.targets.append(location)
flight.points.append(point)
egress_heading = heading - 180 - 25
egress_pos = location.position.point_from_heading(
egress_heading, self.doctrine.ingress_egress_distance
)
egress_point = FlightWaypoint(
FlightWaypointType.EGRESS,
egress_pos.x,
egress_pos.y,
self.doctrine.egress_altitude
)
egress_point.name = "EGRESS"
egress_point.pretty_name = "EGRESS from " + location.name
egress_point.description = "EGRESS from " + location.name
flight.points.append(egress_point)
descend = self.generate_descend_point(flight.from_cp)
flight.points.append(descend)
builder = WaypointBuilder(self.doctrine)
builder.ascent(flight.from_cp)
builder.ingress_strike(ingress_pos, location)
rtb = self.generate_rtb_waypoint(flight.from_cp)
flight.points.append(rtb)
if len(location.groups) > 0 and location.dcs_identifier == "AA":
# TODO: Replace with DEAD?
# Strike missions on SEAD targets target units.
for g in location.groups:
for j, u in enumerate(g.units):
builder.strike_point(u, f"{u.type} #{j}", location)
else:
# TODO: Does this actually happen?
# ConflictTheater is built with the belief that multiple ground
# objects have the same name. If that's the case,
# TheaterGroundObject needs some refactoring because it behaves very
# differently for SAM sites than it does for strike targets.
buildings = self.game.theater.find_ground_objects_by_obj_name(
location.obj_name
)
for building in buildings:
if building.is_dead:
continue
builder.strike_point(
building,
f"{building.obj_name} {building.category}",
location
)
builder.egress(egress_pos, location)
builder.rtb(flight.from_cp)
flight.points = builder.build()
def generate_barcap(self, flight: Flight, location: MissionTarget) -> None:
"""Generate a BARCAP flight at a given location.
@ -239,39 +187,11 @@ class FlightPlanBuilder:
orbit0p = loc.point_from_heading(hdg - 90, radius)
orbit1p = loc.point_from_heading(hdg + 90, radius)
# Create points
ascend = self.generate_ascend_point(flight.from_cp)
flight.points.append(ascend)
orbit0 = FlightWaypoint(
FlightWaypointType.PATROL_TRACK,
orbit0p.x,
orbit0p.y,
patrol_alt
)
orbit0.name = "ORBIT 0"
orbit0.description = "Standby between this point and the next one"
orbit0.pretty_name = "Race-track start"
flight.points.append(orbit0)
orbit1 = FlightWaypoint(
FlightWaypointType.PATROL,
orbit1p.x,
orbit1p.y,
patrol_alt
)
orbit1.name = "ORBIT 1"
orbit1.description = "Standby between this point and the previous one"
orbit1.pretty_name = "Race-track end"
flight.points.append(orbit1)
orbit0.targets.append(location)
descend = self.generate_descend_point(flight.from_cp)
flight.points.append(descend)
rtb = self.generate_rtb_waypoint(flight.from_cp)
flight.points.append(rtb)
builder = WaypointBuilder(self.doctrine)
builder.ascent(flight.from_cp)
builder.race_track(orbit0p, orbit1p, patrol_alt)
builder.rtb(flight.from_cp)
flight.points = builder.build()
def generate_frontline_cap(self, flight: Flight,
location: MissionTarget) -> None:
@ -309,40 +229,11 @@ class FlightPlanBuilder:
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
# Create points
ascend = self.generate_ascend_point(flight.from_cp)
flight.points.append(ascend)
orbit0 = FlightWaypoint(
FlightWaypointType.PATROL_TRACK,
orbit0p.x,
orbit0p.y,
patrol_alt
)
orbit0.name = "ORBIT 0"
orbit0.description = "Standby between this point and the next one"
orbit0.pretty_name = "Race-track start"
flight.points.append(orbit0)
orbit1 = FlightWaypoint(
FlightWaypointType.PATROL,
orbit1p.x,
orbit1p.y,
patrol_alt
)
orbit1.name = "ORBIT 1"
orbit1.description = "Standby between this point and the previous one"
orbit1.pretty_name = "Race-track end"
flight.points.append(orbit1)
# Note: Targets of PATROL TRACK waypoints are the points to be defended.
orbit0.targets.append(flight.from_cp)
orbit0.targets.append(center)
descend = self.generate_descend_point(flight.from_cp)
flight.points.append(descend)
rtb = self.generate_rtb_waypoint(flight.from_cp)
flight.points.append(rtb)
builder = WaypointBuilder(self.doctrine)
builder.ascent(flight.from_cp)
builder.race_track(orbit0p, orbit1p, patrol_alt)
builder.rtb(flight.from_cp)
flight.points = builder.build()
def generate_sead(self, flight: Flight, location: MissionTarget,
custom_targets: Optional[List[Unit]] = None) -> None:
@ -359,33 +250,30 @@ class FlightPlanBuilder:
if custom_targets is None:
custom_targets = []
flight.points = []
flight.flight_type = random.choice([FlightType.SEAD, FlightType.DEAD])
ascend = self.generate_ascend_point(flight.from_cp)
flight.points.append(ascend)
heading = flight.from_cp.position.heading_between_point(
location.position
)
ingress_heading = heading - 180 + 25
egress_heading = heading - 180 - 25
ingress_pos = location.position.point_from_heading(
ingress_heading, self.doctrine.ingress_egress_distance
)
ingress_point = FlightWaypoint(
FlightWaypointType.INGRESS_SEAD,
ingress_pos.x,
ingress_pos.y,
self.doctrine.ingress_altitude
)
ingress_point.name = "INGRESS"
ingress_point.pretty_name = "INGRESS on " + location.name
ingress_point.description = "INGRESS on " + location.name
flight.points.append(ingress_point)
if len(custom_targets) > 0:
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)
# TODO: Unify these.
# There doesn't seem to be any reason to treat the UI fragged missions
# different from the automatic missions.
if custom_targets:
for target in custom_targets:
point = FlightWaypoint(
FlightWaypointType.TARGET_POINT,
@ -395,55 +283,19 @@ class FlightPlanBuilder:
)
point.alt_type = "RADIO"
if flight.flight_type == FlightType.DEAD:
point.description = "DEAD on " + target.type
point.pretty_name = "DEAD on " + location.name
point.only_for_player = True
builder.dead_point(target, location.name, location)
else:
point.description = "SEAD on " + location.name
point.pretty_name = "SEAD on " + location.name
point.only_for_player = True
flight.points.append(point)
ingress_point.targets.append(location)
ingress_point.targetGroup = location
builder.sead_point(target, location.name, location)
else:
point = FlightWaypoint(
FlightWaypointType.TARGET_GROUP_LOC,
location.position.x,
location.position.y,
0
)
point.alt_type = "RADIO"
if flight.flight_type == FlightType.DEAD:
point.description = "DEAD on " + location.name
point.pretty_name = "DEAD on " + location.name
point.only_for_player = True
builder.dead_area(location)
else:
point.description = "SEAD on " + location.name
point.pretty_name = "SEAD on " + location.name
point.only_for_player = True
ingress_point.targets.append(location)
ingress_point.targetGroup = location
flight.points.append(point)
builder.sead_area(location)
egress_pos = location.position.point_from_heading(
egress_heading, self.doctrine.ingress_egress_distance
)
egress_point = FlightWaypoint(
FlightWaypointType.EGRESS,
egress_pos.x,
egress_pos.y,
self.doctrine.egress_altitude
)
egress_point.name = "EGRESS"
egress_point.pretty_name = "EGRESS from " + location.name
egress_point.description = "EGRESS from " + location.name
flight.points.append(egress_point)
builder.egress(egress_pos, location)
builder.rtb(flight.from_cp)
descend = self.generate_descend_point(flight.from_cp)
flight.points.append(descend)
rtb = self.generate_rtb_waypoint(flight.from_cp)
flight.points.append(rtb)
flight.points = builder.build()
def generate_cas(self, flight: Flight, location: MissionTarget) -> None:
"""Generate a CAS flight plan for the given target.
@ -455,89 +307,36 @@ class FlightPlanBuilder:
if not isinstance(location, FrontLine):
raise InvalidObjectiveLocation(flight.flight_type, location)
from_cp, location = location.control_points
is_helo = getattr(flight.unit_type, "helicopter", False)
cap_alt = 1000
flight.points = []
cap_alt = 500 if is_helo else 1000
flight.flight_type = FlightType.CAS
ingress, heading, distance = Conflict.frontline_vector(
from_cp, location, self.game.theater
location.control_points[0], location.control_points[1],
self.game.theater
)
center = ingress.point_from_heading(heading, distance / 2)
egress = ingress.point_from_heading(heading, distance)
ascend = self.generate_ascend_point(flight.from_cp)
if is_helo:
cap_alt = 500
ascend.alt = 500
flight.points.append(ascend)
builder = WaypointBuilder(self.doctrine)
builder.ascent(flight.from_cp, is_helo)
builder.ingress_cas(ingress, location)
builder.cas(center, cap_alt)
builder.egress(egress, location)
builder.rtb(flight.from_cp, is_helo)
ingress_point = FlightWaypoint(
FlightWaypointType.INGRESS_CAS,
ingress.x,
ingress.y,
cap_alt
)
ingress_point.alt_type = "RADIO"
ingress_point.name = "INGRESS"
ingress_point.pretty_name = "INGRESS"
ingress_point.description = "Ingress into CAS area"
flight.points.append(ingress_point)
center_point = FlightWaypoint(
FlightWaypointType.CAS,
center.x,
center.y,
cap_alt
)
center_point.alt_type = "RADIO"
center_point.description = "Provide CAS"
center_point.name = "CAS"
center_point.pretty_name = "CAS"
flight.points.append(center_point)
egress_point = FlightWaypoint(
FlightWaypointType.EGRESS,
egress.x,
egress.y,
cap_alt
)
egress_point.alt_type = "RADIO"
egress_point.description = "Egress from CAS area"
egress_point.name = "EGRESS"
egress_point.pretty_name = "EGRESS"
flight.points.append(egress_point)
descend = self.generate_descend_point(flight.from_cp)
if is_helo:
descend.alt = 300
flight.points.append(descend)
rtb = self.generate_rtb_waypoint(flight.from_cp)
flight.points.append(rtb)
flight.points = builder.build()
# TODO: Make a model for the waypoint builder and use that in the UI.
def generate_ascend_point(self, departure: ControlPoint) -> FlightWaypoint:
"""Generate ascend point.
Args:
departure: Departure airfield or carrier.
"""
ascend_heading = departure.heading
pos_ascend = departure.position.point_from_heading(
ascend_heading, 10000
)
ascend = FlightWaypoint(
FlightWaypointType.ASCEND_POINT,
pos_ascend.x,
pos_ascend.y,
self.doctrine.pattern_altitude
)
ascend.name = "ASCEND"
ascend.alt_type = "RADIO"
ascend.description = "Ascend"
ascend.pretty_name = "Ascend"
return ascend
builder = WaypointBuilder(self.doctrine)
builder.ascent(departure)
return builder.build()[0]
def generate_descend_point(self, arrival: ControlPoint) -> FlightWaypoint:
"""Generate approach/descend point.
@ -545,21 +344,9 @@ class FlightPlanBuilder:
Args:
arrival: Arrival airfield or carrier.
"""
ascend_heading = arrival.heading
descend = arrival.position.point_from_heading(
ascend_heading - 180, 10000
)
descend = FlightWaypoint(
FlightWaypointType.DESCENT_POINT,
descend.x,
descend.y,
self.doctrine.pattern_altitude
)
descend.name = "DESCEND"
descend.alt_type = "RADIO"
descend.description = "Descend to pattern alt"
descend.pretty_name = "Descend to pattern alt"
return descend
builder = WaypointBuilder(self.doctrine)
builder.descent(arrival)
return builder.build()[0]
@staticmethod
def generate_rtb_waypoint(arrival: ControlPoint) -> FlightWaypoint:
@ -568,15 +355,6 @@ class FlightPlanBuilder:
Args:
arrival: Arrival airfield or carrier.
"""
rtb = arrival.position
rtb = FlightWaypoint(
FlightWaypointType.LANDING_POINT,
rtb.x,
rtb.y,
0
)
rtb.name = "LANDING"
rtb.alt_type = "RADIO"
rtb.description = "RTB"
rtb.pretty_name = "RTB"
return rtb
builder = WaypointBuilder(self.doctrine)
builder.land(arrival)
return builder.build()[0]

View File

@ -0,0 +1,270 @@
from __future__ import annotations
from typing import List, Optional, Union
from dcs.mapping import Point
from dcs.unit import Unit
from game.data.doctrine import Doctrine
from game.utils import nm_to_meter
from theater import ControlPoint, MissionTarget, TheaterGroundObject
from .flight import FlightWaypoint, FlightWaypointType
class WaypointBuilder:
def __init__(self, doctrine: Doctrine) -> None:
self.doctrine = doctrine
self.waypoints: List[FlightWaypoint] = []
self.ingress_point: Optional[FlightWaypoint] = None
def build(self) -> List[FlightWaypoint]:
return self.waypoints
def ascent(self, departure: ControlPoint, is_helo: bool = False) -> None:
"""Create ascent waypoint for the given departure airfield or carrier.
Args:
departure: Departure airfield or carrier.
"""
# TODO: Pick runway based on wind direction.
heading = departure.heading
position = departure.position.point_from_heading(
heading, nm_to_meter(5)
)
waypoint = FlightWaypoint(
FlightWaypointType.ASCEND_POINT,
position.x,
position.y,
500 if is_helo else self.doctrine.pattern_altitude
)
waypoint.name = "ASCEND"
waypoint.alt_type = "RADIO"
waypoint.description = "Ascend"
waypoint.pretty_name = "Ascend"
self.waypoints.append(waypoint)
def descent(self, arrival: ControlPoint, is_helo: bool = False) -> None:
"""Create descent waypoint for the given arrival airfield or carrier.
Args:
arrival: Arrival airfield or carrier.
"""
# TODO: Pick runway based on wind direction.
# ControlPoint.heading is the departure heading.
heading = (arrival.heading + 180) % 360
position = arrival.position.point_from_heading(
heading, nm_to_meter(5)
)
waypoint = FlightWaypoint(
FlightWaypointType.DESCENT_POINT,
position.x,
position.y,
300 if is_helo else self.doctrine.pattern_altitude
)
waypoint.name = "DESCEND"
waypoint.alt_type = "RADIO"
waypoint.description = "Descend to pattern altitude"
waypoint.pretty_name = "Ascend"
self.waypoints.append(waypoint)
def land(self, arrival: ControlPoint) -> None:
"""Create descent waypoint for the given arrival airfield or carrier.
Args:
arrival: Arrival airfield or carrier.
"""
position = arrival.position
waypoint = FlightWaypoint(
FlightWaypointType.LANDING_POINT,
position.x,
position.y,
0
)
waypoint.name = "LANDING"
waypoint.alt_type = "RADIO"
waypoint.description = "Land"
waypoint.pretty_name = "Land"
self.waypoints.append(waypoint)
def ingress_cas(self, position: Point, objective: MissionTarget) -> None:
self._ingress(FlightWaypointType.INGRESS_CAS, position, objective)
def ingress_sead(self, position: Point, objective: MissionTarget) -> None:
self._ingress(FlightWaypointType.INGRESS_SEAD, position, objective)
def ingress_strike(self, position: Point, objective: MissionTarget) -> None:
self._ingress(FlightWaypointType.INGRESS_STRIKE, position, objective)
def _ingress(self, ingress_type: FlightWaypointType, position: Point,
objective: MissionTarget) -> None:
if self.ingress_point is not None:
raise RuntimeError("A flight plan can have only one ingress point.")
waypoint = FlightWaypoint(
ingress_type,
position.x,
position.y,
self.doctrine.ingress_altitude
)
waypoint.pretty_name = "INGRESS on " + objective.name
waypoint.description = "INGRESS on " + objective.name
waypoint.name = "INGRESS"
self.waypoints.append(waypoint)
self.ingress_point = waypoint
def egress(self, position: Point, target: MissionTarget) -> None:
waypoint = FlightWaypoint(
FlightWaypointType.EGRESS,
position.x,
position.y,
self.doctrine.ingress_altitude
)
waypoint.pretty_name = "EGRESS from " + target.name
waypoint.description = "EGRESS from " + target.name
waypoint.name = "EGRESS"
self.waypoints.append(waypoint)
def dead_point(self, target: Union[TheaterGroundObject, Unit], name: str,
location: MissionTarget) -> None:
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
location)
# TODO: Seems fishy.
self.ingress_point.targetGroup = location
def sead_point(self, target: Union[TheaterGroundObject, Unit], name: str,
location: MissionTarget) -> None:
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
location)
# TODO: Seems fishy.
self.ingress_point.targetGroup = location
def strike_point(self, target: Union[TheaterGroundObject, Unit], name: str,
location: MissionTarget) -> None:
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
location)
def _target_point(self, target: Union[TheaterGroundObject, Unit], name: str,
description: str, location: MissionTarget) -> None:
if self.ingress_point is None:
raise RuntimeError(
"An ingress point must be added before target points."
)
waypoint = FlightWaypoint(
FlightWaypointType.TARGET_POINT,
target.position.x,
target.position.y,
0
)
waypoint.description = description
waypoint.pretty_name = description
waypoint.name = name
waypoint.only_for_player = True
self.waypoints.append(waypoint)
# TODO: This seems wrong, but it's what was there before.
self.ingress_point.targets.append(location)
def sead_area(self, target: MissionTarget) -> None:
self._target_area(f"SEAD on {target.name}", target)
# TODO: Seems fishy.
self.ingress_point.targetGroup = target
def dead_area(self, target: MissionTarget) -> None:
self._target_area(f"DEAD on {target.name}", target)
# TODO: Seems fishy.
self.ingress_point.targetGroup = target
def _target_area(self, name: str, location: MissionTarget) -> None:
if self.ingress_point is None:
raise RuntimeError(
"An ingress point must be added before target points."
)
waypoint = FlightWaypoint(
FlightWaypointType.TARGET_GROUP_LOC,
location.position.x,
location.position.y,
0
)
waypoint.description = name
waypoint.pretty_name = name
waypoint.name = name
waypoint.only_for_player = True
self.waypoints.append(waypoint)
# TODO: This seems wrong, but it's what was there before.
self.ingress_point.targets.append(location)
def cas(self, position: Point, altitude: int) -> None:
waypoint = FlightWaypoint(
FlightWaypointType.CAS,
position.x,
position.y,
altitude
)
waypoint.alt_type = "RADIO"
waypoint.description = "Provide CAS"
waypoint.name = "CAS"
waypoint.pretty_name = "CAS"
self.waypoints.append(waypoint)
def race_track_start(self, position: Point, altitude: int) -> None:
"""Creates a racetrack start waypoint.
Args:
position: Position of the waypoint.
altitude: Altitude of the racetrack in meters.
"""
waypoint = FlightWaypoint(
FlightWaypointType.PATROL_TRACK,
position.x,
position.y,
altitude
)
waypoint.name = "RACETRACK START"
waypoint.description = "Orbit between this point and the next point"
waypoint.pretty_name = "Race-track start"
self.waypoints.append(waypoint)
# TODO: Does this actually do anything?
# orbit0.targets.append(location)
# Note: Targets of PATROL TRACK waypoints are the points to be defended.
# orbit0.targets.append(flight.from_cp)
# orbit0.targets.append(center)
def race_track_end(self, position: Point, altitude: int) -> None:
"""Creates a racetrack end waypoint.
Args:
position: Position of the waypoint.
altitude: Altitude of the racetrack in meters.
"""
waypoint = FlightWaypoint(
FlightWaypointType.PATROL,
position.x,
position.y,
altitude
)
waypoint.name = "RACETRACK END"
waypoint.description = "Orbit between this point and the previous point"
waypoint.pretty_name = "Race-track end"
self.waypoints.append(waypoint)
def race_track(self, start: Point, end: Point, altitude: int) -> None:
"""Creates two waypoint for a racetrack orbit.
Args:
start: The beginning racetrack waypoint.
end: The ending racetrack waypoint.
altitude: The racetrack altitude.
"""
self.race_track_start(start, altitude)
self.race_track_end(end, altitude)
def rtb(self, arrival: ControlPoint, is_helo: bool = False) -> None:
"""Creates descent ant landing waypoints for the given control point.
Args:
arrival: Arrival airfield or carrier.
"""
self.descent(arrival, is_helo)
self.land(arrival)