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.
This commit is contained in:
Dan Albert 2022-09-02 20:54:41 -07:00 committed by Raffson
parent ad9686947b
commit 07632e2705
No known key found for this signature in database
GPG Key ID: B0402B2C9B764D99
11 changed files with 33 additions and 24 deletions

View File

@ -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]:

View File

@ -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:

View File

@ -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:

View File

@ -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())

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -12,7 +12,6 @@ if TYPE_CHECKING:
@dataclass(frozen=True)
class StandardLayout(Layout, ABC):
departure: FlightWaypoint
arrival: FlightWaypoint
divert: FlightWaypoint | None
bullseye: FlightWaypoint

View File

@ -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

View File

@ -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: