Dan Albert 87441b8939 Formalize waypoint actions.
Create a WaypointAction class that defines the actions taken at a
waypoint. These will often map one-to-one with DCS waypoint actions but
can also be higher level and generate multiple actions. Once everything
has migrated all waypoint-type-specific behaviors of
PydcsWaypointBuilder will be gone, and it'll be easier to keep the sim
behaviors in sync with the mission generator behaviors.

For now only hold has been migrated. This is actually probably the most
complicated action we have (starting with this may have been a mistake,
but it did find all the rough edges quickly) since it affects waypoint
timings and flight position during simulation. That part isn't handled
as neatly as I'd like because the FlightState still has to special case
LOITER points to avoid simulating the wrong waypoint position. At some
point we should probably start tracking real positions in FlightState,
and when we do that will be solved.
2023-08-13 12:43:59 -07:00

123 lines
4.9 KiB
Python

from __future__ import annotations
from datetime import datetime
from typing import Any, Iterable, Union
from dcs import Mission
from dcs.planes import AJS37, F_14B, JF_17
from dcs.point import MovingPoint, PointAction
from dcs.unitgroup import FlyingGroup
from game.ato import Flight, FlightWaypoint
from game.ato.flightwaypointtype import FlightWaypointType
from game.ato.traveltime import GroundSpeed
from game.flightplan.waypointactions.taskcontext import TaskContext
from game.missiongenerator.missiondata import MissionData
from game.theater import MissionTarget, TheaterUnit
from game.unitmap import UnitMap
TARGET_WAYPOINTS = (
FlightWaypointType.TARGET_GROUP_LOC,
FlightWaypointType.TARGET_POINT,
FlightWaypointType.TARGET_SHIP,
)
class PydcsWaypointBuilder:
def __init__(
self,
waypoint: FlightWaypoint,
group: FlyingGroup[Any],
flight: Flight,
mission: Mission,
now: datetime,
mission_data: MissionData,
unit_map: UnitMap,
generated_waypoint_idx: int,
) -> None:
self.waypoint = waypoint
self.group = group
self.package = flight.package
self.flight = flight
self.mission = mission
self.now = now
self.mission_data = mission_data
self.unit_map = unit_map
self.generated_waypoint_idx = generated_waypoint_idx
def dcs_name_for_waypoint(self) -> str:
return self.waypoint.name
def build(self) -> MovingPoint:
waypoint = self.group.add_waypoint(
self.waypoint.position,
self.waypoint.alt.meters,
# The speed we pass will be overridden for most waypoints because we'll set
# a TOT and leave the speed up to the AI, but for the few types of waypoints
# that don't have TOTs (e.g. nav points), we set a reasonable cruise speed
# to pydcs doesn't assign the default of 600kph ground speed (which is very
# slow at most altitudes).
#
# Calling GroundSpeed.for_flight isn't really a correct fix here. We ought
# to be using FlightPlan.speed_between_waypoints, but the way the waypoint
# builder is called makes it difficult to track the previous waypoint. This
# is probably good enough for a stop gap, and most of the flight planning
# code is hopefully being rewritten soon anyway.
#
# https://github.com/dcs-liberation/dcs_liberation/issues/3113
speed=GroundSpeed.for_flight(self.flight, self.waypoint.alt).kph,
name=self.dcs_name_for_waypoint(),
)
if self.waypoint.flyover:
waypoint.action = PointAction.FlyOverPoint
# It seems we need to leave waypoint.type exactly as it is even
# though it's set to "Turning Point". If I set this to "Fly Over
# Point" and then save the mission in the ME DCS resets it.
if self.flight.client_count > 0:
# Set Altitute to 0 AGL for player flights so that they can slave target pods or weapons to the waypoint
waypoint.alt = 0
waypoint.alt_type = "RADIO"
waypoint.alt_type = self.waypoint.alt_type
tot = self.flight.flight_plan.tot_for_waypoint(self.waypoint)
if tot is not None:
self.set_waypoint_tot(waypoint, tot)
self.add_tasks(waypoint)
return waypoint
def add_tasks(self, waypoint: MovingPoint) -> None:
ctx = TaskContext(self.now)
for action in self.waypoint.actions:
for task in action.iter_tasks(ctx):
waypoint.add_task(task)
def set_waypoint_tot(self, waypoint: MovingPoint, tot: datetime) -> None:
self.waypoint.tot = tot
if not self._viggen_client_tot():
waypoint.ETA = int((tot - self.now).total_seconds())
waypoint.ETA_locked = True
waypoint.speed_locked = False
def _viggen_client_tot(self) -> bool:
"""Viggen player aircraft consider any waypoint with a TOT set to be a target ("M") waypoint.
If the flight is a player controlled Viggen flight, no TOT should be set on any waypoint except actual target waypoints.
"""
if (
self.flight.client_count > 0
and self.flight.unit_type.dcs_unit_type == AJS37
) and (self.waypoint.waypoint_type not in TARGET_WAYPOINTS):
return True
else:
return False
def register_special_waypoints(
self, targets: Iterable[Union[MissionTarget, TheaterUnit]]
) -> None:
"""Create special target waypoints for various aircraft"""
for i, t in enumerate(targets):
if self.group.units[0].unit_type == JF_17 and i < 4:
self.group.add_nav_target_point(t.position, "PP" + str(i + 1))
if self.group.units[0].unit_type == F_14B and i == 0:
self.group.add_nav_target_point(t.position, "ST")