diff --git a/changelog.md b/changelog.md index 08dab6c1..bed65fd8 100644 --- a/changelog.md +++ b/changelog.md @@ -16,6 +16,7 @@ Saves from 2.5 are not compatible with 3.0. * **[Flight Planner]** Planned airspeed increased to 0.85 mach for supersonic airframes and 85% of max speed for subsonic. * **[Flight Planner]** Taxi time estimation for airfields increased from 5 minutes to 8 minutes. * **[Flight Planner]** Reduce expected error margin for flight plans from 10% to 5%. +* **[Flight Planner]** SEAD flights are scheduled one minute ahead of the package's TOT so that they can suppress the site ahead of the strike. * **[Payloads]** AI flights for most air to ground mission types (CAS excluded) will have their guns emptied to prevent strafing fully armed and operational battle stations. Gun-reliant airframes like A-10s and warbirds will keep their bullets. * **[Kneeboard]** ATC table overflow alleviated by wrapping long airfield names and splitting ATC frequency and channel into separate rows. * **[UI]** Added new web based map UI. This is mostly functional but many of the old display options are a WIP. Revert to the old map with --old-map. diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index c2779e3f..11cc4716 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -10,14 +10,13 @@ from __future__ import annotations import logging import math import random -from dataclasses import dataclass +from dataclasses import dataclass, field from datetime import timedelta from functools import cached_property from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple -from dcs.planes import E_3A, E_2C, A_50, KJ_2000 - from dcs.mapping import Point +from dcs.planes import E_3A, E_2C, A_50, KJ_2000 from dcs.unit import Unit from shapely.geometry import Point as ShapelyPoint @@ -125,6 +124,10 @@ class FlightPlan: """ raise NotImplementedError + @property + def tot(self) -> timedelta: + return self.package.time_over_target + self.tot_offset + @cached_property def bingo_fuel(self) -> int: """Bingo fuel value for the FlightPlan""" @@ -217,10 +220,9 @@ class FlightPlan: if tot_waypoint is None: return None - time = self.tot_for_waypoint(tot_waypoint) + time = self.tot if time is None: return None - time += self.tot_offset return time - self._travel_time_to_waypoint(tot_waypoint) def startup_time(self) -> Optional[timedelta]: @@ -520,7 +522,7 @@ class TarCapFlightPlan(PatrollingFlightPlan): start = self.package.escort_start_time if start is not None: return start + self.tot_offset - return super().patrol_start_time + self.tot_offset + return self.tot @property def patrol_end_time(self) -> timedelta: @@ -544,6 +546,7 @@ class StrikeFlightPlan(FormationFlightPlan): land: FlightWaypoint divert: Optional[FlightWaypoint] bullseye: FlightWaypoint + lead_time: timedelta = field(default_factory=timedelta) def iter_waypoints(self) -> Iterator[FlightWaypoint]: yield self.takeoff @@ -582,6 +585,13 @@ class StrikeFlightPlan(FormationFlightPlan): def tot_waypoint(self) -> FlightWaypoint: return self.targets[0] + @property + def tot_offset(self) -> timedelta: + try: + return -self.lead_time + except AttributeError: + return timedelta() + @property def target_area_waypoint(self) -> FlightWaypoint: return FlightWaypoint( @@ -626,7 +636,7 @@ class StrikeFlightPlan(FormationFlightPlan): @property def ingress_time(self) -> timedelta: - tot = self.package.time_over_target + tot = self.tot travel_time = self.travel_time_between_waypoints( self.ingress, self.target_area_waypoint ) @@ -634,7 +644,7 @@ class StrikeFlightPlan(FormationFlightPlan): @property def egress_time(self) -> timedelta: - tot = self.package.time_over_target + tot = self.tot travel_time = self.travel_time_between_waypoints( self.target_area_waypoint, self.egress ) @@ -646,7 +656,7 @@ class StrikeFlightPlan(FormationFlightPlan): elif waypoint == self.egress: return self.egress_time elif waypoint in self.targets: - return self.package.time_over_target + return self.tot return super().tot_for_waypoint(waypoint) @@ -691,7 +701,7 @@ class SweepFlightPlan(LoiterFlightPlan): @property def sweep_end_time(self) -> timedelta: - return self.package.time_over_target + self.tot_offset + return self.tot def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]: if waypoint == self.sweep_start: @@ -1513,7 +1523,11 @@ class FlightPlanBuilder: targets.append(StrikeTarget(location.name, target)) return self.strike_flightplan( - flight, location, FlightWaypointType.INGRESS_SEAD, targets + flight, + location, + FlightWaypointType.INGRESS_SEAD, + targets, + lead_time=timedelta(minutes=1), ) def generate_escort(self, flight: Flight) -> StrikeFlightPlan: @@ -1691,6 +1705,7 @@ class FlightPlanBuilder: location: MissionTarget, ingress_type: FlightWaypointType, targets: Optional[List[StrikeTarget]] = None, + lead_time: timedelta = timedelta(), ) -> StrikeFlightPlan: assert self.package.waypoints is not None builder = WaypointBuilder(flight, self.game, self.is_player, targets) @@ -1730,6 +1745,7 @@ class FlightPlanBuilder: land=builder.land(flight.arrival), divert=builder.divert(flight.divert), bullseye=builder.bullseye(), + lead_time=lead_time, ) def _retreating_rendezvous_point(self, attack_transition: Point) -> Point: