Dan Albert 452848fd2a Make TOT waypoints non-optional for flight plans.
Flights without a meaningful TOT make the code around startup time (and
other scheduling behaviors) unnecessarily complicated because they have
to handle unpredictable flight plans. We can simplify this by requiring
that all flight plans have a waypoint associated with their TOT. For
custom flight plans, we can just fall back to the takeoff waypoint. For
RTB flight plans (which are only synthetic flight plans injected for
aborted flights), we can use the abort point.

This also means that all flight plans now have, at the very least, a
departure waypoint. Deleting this waypoint is invalid even for custom
flights, so that's no a problem.
2022-09-02 21:13:23 -07:00

94 lines
2.9 KiB
Python

from __future__ import annotations
from collections.abc import Iterator
from dataclasses import dataclass
from datetime import timedelta
from typing import TYPE_CHECKING, Type
from game.utils import feet
from .ibuilder import IBuilder
from .standard import StandardFlightPlan, StandardLayout
from .waypointbuilder import WaypointBuilder
from ..flightstate import InFlight
if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
class RtbLayout(StandardLayout):
abort_location: FlightWaypoint
nav_to_destination: list[FlightWaypoint]
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield self.abort_location
yield from self.nav_to_destination
yield self.arrival
if self.divert is not None:
yield self.divert
yield self.bullseye
class RtbFlightPlan(StandardFlightPlan[RtbLayout]):
@staticmethod
def builder_type() -> Type[Builder]:
return Builder
@property
def abort_index(self) -> int:
return 1
@property
def tot_waypoint(self) -> FlightWaypoint:
return self.layout.abort_location
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
return None
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
return None
@property
def mission_departure_time(self) -> timedelta:
return timedelta()
class Builder(IBuilder[RtbFlightPlan, RtbLayout]):
def layout(self) -> RtbLayout:
if not isinstance(self.flight.state, InFlight):
raise RuntimeError(f"Cannot abort {self} because it is not in flight")
current_position = self.flight.state.estimate_position()
current_altitude, altitude_reference = self.flight.state.estimate_altitude()
altitude_is_agl = self.flight.unit_type.dcs_unit_type.helicopter
altitude = (
feet(1500)
if altitude_is_agl
else self.flight.unit_type.preferred_patrol_altitude
)
builder = WaypointBuilder(self.flight, self.flight.coalition)
abort_point = builder.nav(
current_position, current_altitude, altitude_reference == "RADIO"
)
abort_point.name = "ABORT AND RTB"
abort_point.pretty_name = "Abort and RTB"
abort_point.description = "Abort mission and return to base"
return RtbLayout(
departure=builder.takeoff(self.flight.departure),
abort_location=abort_point,
nav_to_destination=builder.nav_path(
current_position,
self.flight.arrival.position,
altitude,
altitude_is_agl,
),
arrival=builder.land(self.flight.arrival),
divert=builder.divert(self.flight.divert),
bullseye=builder.bullseye(),
)
def build(self) -> RtbFlightPlan:
return RtbFlightPlan(self.flight, self.layout())