Dan Albert 07632e2705
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-03 19:13:21 +02:00

100 lines
3.1 KiB
Python

from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import Iterator
from dataclasses import dataclass
from datetime import timedelta
from typing import Any, TYPE_CHECKING, TypeGuard, TypeVar
from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout
from game.typeguard import self_type_guard
from game.utils import Distance, Speed
if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
from .flightplan import FlightPlan
@dataclass(frozen=True)
class PatrollingLayout(StandardLayout):
nav_to: list[FlightWaypoint]
patrol_start: FlightWaypoint
patrol_end: FlightWaypoint
nav_from: list[FlightWaypoint]
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield from self.nav_to
yield self.patrol_start
yield self.patrol_end
yield from self.nav_from
yield self.arrival
if self.divert is not None:
yield self.divert
yield self.bullseye
LayoutT = TypeVar("LayoutT", bound=PatrollingLayout)
class PatrollingFlightPlan(StandardFlightPlan[LayoutT], ABC):
@property
@abstractmethod
def patrol_duration(self) -> timedelta:
"""Maximum time to remain on station."""
@property
@abstractmethod
def patrol_speed(self) -> Speed:
"""Racetrack speed TAS."""
@property
@abstractmethod
def engagement_distance(self) -> Distance:
"""The maximum engagement distance.
The engagement range of any Search Then Engage task, or the radius of a Search
Then Engage in Zone task. Any enemies of the appropriate type for this mission
within this range of the flight's current position (or the center of the zone)
will be engaged by the flight.
"""
@property
def patrol_start_time(self) -> timedelta:
return self.package.time_over_target
@property
def patrol_end_time(self) -> timedelta:
# TODO: This is currently wrong for CAS.
# CAS missions end when they're winchester or bingo. We need to
# configure push tasks for the escorts rather than relying on timing.
return self.patrol_start_time + self.patrol_duration
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
if waypoint == self.layout.patrol_start:
return self.patrol_start_time
return None
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
if waypoint == self.layout.patrol_end:
return self.patrol_end_time
return None
@property
def package_speed_waypoints(self) -> set[FlightWaypoint]:
return {self.layout.patrol_start, self.layout.patrol_end}
@property
def tot_waypoint(self) -> FlightWaypoint:
return self.layout.patrol_start
@property
def mission_departure_time(self) -> timedelta:
return self.patrol_end_time
@self_type_guard
def is_patrol(
self, flight_plan: FlightPlan[Any]
) -> TypeGuard[PatrollingFlightPlan[Any]]:
return True