From 452848fd2a00461acbba149eb954179e66477dfe Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 2 Sep 2022 20:54:41 -0700 Subject: [PATCH] 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. --- game/ato/flight.py | 7 ++++++- game/ato/flightplans/airassault.py | 2 +- game/ato/flightplans/airlift.py | 2 +- game/ato/flightplans/custom.py | 9 ++++++--- game/ato/flightplans/ferry.py | 2 +- game/ato/flightplans/flightplan.py | 19 ++++++++----------- game/ato/flightplans/patrolling.py | 2 +- game/ato/flightplans/rtb.py | 4 ++-- game/ato/flightplans/standard.py | 1 - game/ato/flightplans/sweep.py | 2 +- .../flight/waypoints/QFlightWaypointTab.py | 7 ++++++- 11 files changed, 33 insertions(+), 24 deletions(-) diff --git a/game/ato/flight.py b/game/ato/flight.py index 243b4a6d..981eb6c5 100644 --- a/game/ato/flight.py +++ b/game/ato/flight.py @@ -8,6 +8,7 @@ from dcs import Point from dcs.planes import C_101CC, C_101EB, Su_33 from .flightplans.planningerror import PlanningError +from .flightplans.waypointbuilder import WaypointBuilder from .flightroster import FlightRoster from .flightstate import FlightState, Navigating, Uninitialized from .flightstate.killed import Killed @@ -89,7 +90,11 @@ class Flight(SidcDescribable): from .flightplans.custom import CustomFlightPlan, CustomLayout self.flight_plan: FlightPlan[Any] = CustomFlightPlan( - self, CustomLayout(custom_waypoints=[]) + self, + CustomLayout( + departure=WaypointBuilder(self, self.coalition).takeoff(self.departure), + custom_waypoints=[], + ), ) def __getstate__(self) -> dict[str, Any]: diff --git a/game/ato/flightplans/airassault.py b/game/ato/flightplans/airassault.py index 8d4fa0f2..d2db440b 100644 --- a/game/ato/flightplans/airassault.py +++ b/game/ato/flightplans/airassault.py @@ -41,7 +41,7 @@ class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout]): return Builder @property - def tot_waypoint(self) -> FlightWaypoint | None: + def tot_waypoint(self) -> FlightWaypoint: return self.layout.drop_off def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: diff --git a/game/ato/flightplans/airlift.py b/game/ato/flightplans/airlift.py index 41ee1254..4ff69d05 100644 --- a/game/ato/flightplans/airlift.py +++ b/game/ato/flightplans/airlift.py @@ -47,7 +47,7 @@ class AirliftFlightPlan(StandardFlightPlan[AirliftLayout]): return Builder @property - def tot_waypoint(self) -> FlightWaypoint | None: + def tot_waypoint(self) -> FlightWaypoint: return self.layout.drop_off def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: diff --git a/game/ato/flightplans/custom.py b/game/ato/flightplans/custom.py index ec18f2eb..970ad4b8 100644 --- a/game/ato/flightplans/custom.py +++ b/game/ato/flightplans/custom.py @@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Type from .flightplan import FlightPlan, Layout from .ibuilder import IBuilder +from .waypointbuilder import WaypointBuilder from ..flightwaypointtype import FlightWaypointType if TYPE_CHECKING: @@ -18,6 +19,7 @@ class CustomLayout(Layout): custom_waypoints: list[FlightWaypoint] def iter_waypoints(self) -> Iterator[FlightWaypoint]: + yield self.departure yield from self.custom_waypoints @@ -27,7 +29,7 @@ class CustomFlightPlan(FlightPlan[CustomLayout]): return Builder @property - def tot_waypoint(self) -> FlightWaypoint | None: + def tot_waypoint(self) -> FlightWaypoint: target_types = ( FlightWaypointType.PATROL_TRACK, FlightWaypointType.TARGET_GROUP_LOC, @@ -37,7 +39,7 @@ class CustomFlightPlan(FlightPlan[CustomLayout]): for waypoint in self.waypoints: if waypoint in target_types: return waypoint - return None + return self.layout.departure def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: if waypoint == self.tot_waypoint: @@ -54,7 +56,8 @@ class CustomFlightPlan(FlightPlan[CustomLayout]): class Builder(IBuilder[CustomFlightPlan, CustomLayout]): def layout(self) -> CustomLayout: - return CustomLayout([]) + builder = WaypointBuilder(self.flight, self.coalition) + return CustomLayout(builder.takeoff(self.flight.departure), []) def build(self) -> CustomFlightPlan: return CustomFlightPlan(self.flight, self.layout()) diff --git a/game/ato/flightplans/ferry.py b/game/ato/flightplans/ferry.py index c6d6e7cc..0ddd82ac 100644 --- a/game/ato/flightplans/ferry.py +++ b/game/ato/flightplans/ferry.py @@ -34,7 +34,7 @@ class FerryFlightPlan(StandardFlightPlan[FerryLayout]): return Builder @property - def tot_waypoint(self) -> FlightWaypoint | None: + def tot_waypoint(self) -> FlightWaypoint: return self.layout.arrival def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: diff --git a/game/ato/flightplans/flightplan.py b/game/ato/flightplans/flightplan.py index 1496da10..c02a8848 100644 --- a/game/ato/flightplans/flightplan.py +++ b/game/ato/flightplans/flightplan.py @@ -10,6 +10,7 @@ from __future__ import annotations import math from abc import ABC from collections.abc import Iterator +from dataclasses import dataclass from datetime import timedelta from functools import cached_property from typing import Any, Generic, TYPE_CHECKING, TypeGuard, TypeVar @@ -40,7 +41,10 @@ INGRESS_TYPES = { } +@dataclass(frozen=True) class Layout(ABC): + departure: FlightWaypoint + @property def waypoints(self) -> list[FlightWaypoint]: """A list of all waypoints in the flight plan, in order.""" @@ -135,7 +139,7 @@ class FlightPlan(ABC, Generic[LayoutT]): return self.flight.unit_type.fuel_consumption.cruise @property - def tot_waypoint(self) -> FlightWaypoint | None: + def tot_waypoint(self) -> FlightWaypoint: """The waypoint that is associated with the package TOT, or None. Note that the only flight plans that should have no target waypoints are @@ -246,19 +250,12 @@ class FlightPlan(ABC, Generic[LayoutT]): if waypoint == end: return - def takeoff_time(self) -> timedelta | None: - tot_waypoint = self.tot_waypoint - if tot_waypoint is None: - return None - return self.tot - self._travel_time_to_waypoint(tot_waypoint) + def takeoff_time(self) -> timedelta: + return self.tot - self._travel_time_to_waypoint(self.tot_waypoint) def startup_time(self) -> timedelta | None: - takeoff_time = self.takeoff_time() - if takeoff_time is None: - return None - start_time: timedelta = ( - takeoff_time - self.estimate_startup() - self.estimate_ground_ops() + self.takeoff_time() - self.estimate_startup() - self.estimate_ground_ops() ) # In case FP math has given us some barely below zero time, round to diff --git a/game/ato/flightplans/patrolling.py b/game/ato/flightplans/patrolling.py index 43645f24..c531fdfb 100644 --- a/game/ato/flightplans/patrolling.py +++ b/game/ato/flightplans/patrolling.py @@ -85,7 +85,7 @@ class PatrollingFlightPlan(StandardFlightPlan[LayoutT], ABC): return {self.layout.patrol_start, self.layout.patrol_end} @property - def tot_waypoint(self) -> FlightWaypoint | None: + def tot_waypoint(self) -> FlightWaypoint: return self.layout.patrol_start @property diff --git a/game/ato/flightplans/rtb.py b/game/ato/flightplans/rtb.py index 7555179f..8f1cbfa8 100644 --- a/game/ato/flightplans/rtb.py +++ b/game/ato/flightplans/rtb.py @@ -40,8 +40,8 @@ class RtbFlightPlan(StandardFlightPlan[RtbLayout]): return 1 @property - def tot_waypoint(self) -> FlightWaypoint | None: - return None + def tot_waypoint(self) -> FlightWaypoint: + return self.layout.abort_location def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: return None diff --git a/game/ato/flightplans/standard.py b/game/ato/flightplans/standard.py index 6fe1fa1a..5cc45f95 100644 --- a/game/ato/flightplans/standard.py +++ b/game/ato/flightplans/standard.py @@ -12,7 +12,6 @@ if TYPE_CHECKING: @dataclass(frozen=True) class StandardLayout(Layout, ABC): - departure: FlightWaypoint arrival: FlightWaypoint divert: FlightWaypoint | None bullseye: FlightWaypoint diff --git a/game/ato/flightplans/sweep.py b/game/ato/flightplans/sweep.py index 5c4e8761..57f40c64 100644 --- a/game/ato/flightplans/sweep.py +++ b/game/ato/flightplans/sweep.py @@ -54,7 +54,7 @@ class SweepFlightPlan(LoiterFlightPlan): return {self.layout.sweep_end} @property - def tot_waypoint(self) -> FlightWaypoint | None: + def tot_waypoint(self) -> FlightWaypoint: return self.layout.sweep_end @property diff --git a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py index 8650c9ee..16329e8d 100644 --- a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py +++ b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py @@ -148,7 +148,12 @@ class QFlightWaypointTab(QFrame): if not isinstance(self.flight.flight_plan, CustomFlightPlan): self.flight.flight_plan = CustomFlightPlan( self.flight, - CustomLayout(custom_waypoints=self.flight.flight_plan.waypoints), + CustomLayout( + departure=WaypointBuilder(self.flight, self.coalition).takeoff( + self.flight.departure + ), + custom_waypoints=self.flight.flight_plan.waypoints[1:], + ), ) def confirm_recreate(self, task: FlightType) -> None: