mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
295 lines
10 KiB
Python
295 lines
10 KiB
Python
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 join(self, position: Point) -> None:
|
|
waypoint = FlightWaypoint(
|
|
FlightWaypointType.JOIN,
|
|
position.x,
|
|
position.y,
|
|
self.doctrine.ingress_altitude
|
|
)
|
|
waypoint.pretty_name = "Join"
|
|
waypoint.description = "Rendezvous with package"
|
|
waypoint.name = "JOIN"
|
|
self.waypoints.append(waypoint)
|
|
|
|
def split(self, position: Point) -> None:
|
|
waypoint = FlightWaypoint(
|
|
FlightWaypointType.SPLIT,
|
|
position.x,
|
|
position.y,
|
|
self.doctrine.ingress_altitude
|
|
)
|
|
waypoint.pretty_name = "Split"
|
|
waypoint.description = "Depart from package"
|
|
waypoint.name = "SPLIT"
|
|
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)
|