from __future__ import annotations from dataclasses import dataclass from datetime import datetime, timedelta from typing import Iterator, TYPE_CHECKING, Type from dcs import Point from game.utils import Heading from .ibuilder import IBuilder from .loiter import LoiterFlightPlan, LoiterLayout from .waypointbuilder import WaypointBuilder from ...flightplan import HoldZoneGeometry if TYPE_CHECKING: from ..flightwaypoint import FlightWaypoint @dataclass class SweepLayout(LoiterLayout): nav_to: list[FlightWaypoint] sweep_start: FlightWaypoint sweep_end: FlightWaypoint nav_from: list[FlightWaypoint] def iter_waypoints(self) -> Iterator[FlightWaypoint]: yield self.departure if self.hold: yield self.hold yield from self.nav_to yield self.sweep_start yield self.sweep_end yield from self.nav_from yield self.arrival if self.divert is not None: yield self.divert yield self.bullseye class SweepFlightPlan(LoiterFlightPlan): @staticmethod def builder_type() -> Type[Builder]: return Builder @property def combat_speed_waypoints(self) -> set[FlightWaypoint]: return {self.layout.sweep_end} @property def tot_waypoint(self) -> FlightWaypoint: return self.layout.sweep_end def default_tot_offset(self) -> timedelta: return -timedelta(minutes=5) @property def sweep_start_time(self) -> datetime: travel_time = self.total_time_between_waypoints( self.layout.sweep_start, self.layout.sweep_end ) return self.sweep_end_time - travel_time @property def sweep_end_time(self) -> datetime: return self.tot def tot_for_waypoint(self, waypoint: FlightWaypoint) -> datetime | None: if waypoint == self.layout.sweep_start: return self.sweep_start_time if waypoint == self.layout.sweep_end: return self.sweep_end_time return None def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> datetime | None: if waypoint == self.layout.hold: return self.push_time return None @property def push_time(self) -> datetime: return self.sweep_end_time - self.travel_time_between_waypoints( self.layout.hold, self.layout.sweep_end, ) @property def mission_begin_on_station_time(self) -> datetime | None: return None @property def mission_departure_time(self) -> datetime: return self.sweep_end_time class Builder(IBuilder[SweepFlightPlan, SweepLayout]): def layout(self) -> SweepLayout: assert self.package.waypoints is not None target = self.package.target.position heading = Heading.from_degrees( self.package.waypoints.join.heading_between_point(target) ) start_pos = target.point_from_heading( heading.degrees, -self.doctrine.sweep_distance.meters ) builder = WaypointBuilder(self.flight) start, end = builder.sweep(start_pos, target, self.doctrine.ingress_altitude) hold = builder.hold(self._hold_point()) return SweepLayout( departure=builder.takeoff(self.flight.departure), hold=hold, nav_to=builder.nav_path( hold.position, start.position, self.doctrine.ingress_altitude ), nav_from=builder.nav_path( end.position, self.flight.arrival.position, self.doctrine.ingress_altitude, ), sweep_start=start, sweep_end=end, arrival=builder.land(self.flight.arrival), divert=builder.divert(self.flight.divert), bullseye=builder.bullseye(), ) def _hold_point(self) -> Point: assert self.package.waypoints is not None origin = self.flight.departure.position target = self.package.target.position join = self.package.waypoints.join ip = self.package.waypoints.ingress return HoldZoneGeometry( target, origin, ip, join, self.coalition, self.theater ).find_best_hold_point() def build(self, dump_debug_info: bool = False) -> SweepFlightPlan: return SweepFlightPlan(self.flight, self.layout())