diff --git a/game/ato/flightplans/flightplan.py b/game/ato/flightplans/flightplan.py index e0419353..5e631c3f 100644 --- a/game/ato/flightplans/flightplan.py +++ b/game/ato/flightplans/flightplan.py @@ -231,8 +231,23 @@ class FlightPlan(ABC, Generic[LayoutT]): ) for previous_waypoint, waypoint in self.edges(until=destination): - total += self.travel_time_between_waypoints(previous_waypoint, waypoint) - return total + total += self.total_time_between_waypoints(previous_waypoint, waypoint) + # Trim microseconds. Our simulation tick rate is 1 second, so anything that + # takes 100.1 or 100.9 seconds will take 100 seconds. DCS doesn't handle + # sub-second resolution for tasks anyway, nor are they interesting from a + # mission planning perspective, so there's little value to keeping them in the + # model. + return timedelta(seconds=math.floor(total.total_seconds())) + + def total_time_between_waypoints( + self, a: FlightWaypoint, b: FlightWaypoint + ) -> timedelta: + """Returns the total time spent between a and b. + + The total time between waypoints differs from the travel time in that it may + include additional time for actions such as loitering. + """ + return self.travel_time_between_waypoints(a, b) def travel_time_between_waypoints( self, a: FlightWaypoint, b: FlightWaypoint diff --git a/game/ato/flightplans/formationattack.py b/game/ato/flightplans/formationattack.py index 6d4c871d..1be5669d 100644 --- a/game/ato/flightplans/formationattack.py +++ b/game/ato/flightplans/formationattack.py @@ -14,7 +14,6 @@ from game.utils import Speed, meters, nautical_miles, feet from .flightplan import FlightPlan from .formation import FormationFlightPlan, FormationLayout from .ibuilder import IBuilder -from .planningerror import PlanningError from .waypointbuilder import StrikeTarget, WaypointBuilder from .. import FlightType from ..flightwaypoint import FlightWaypoint @@ -56,42 +55,19 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC): "RADIO", ) - @property - def travel_time_to_target(self) -> timedelta: - """The estimated time between the first waypoint and the target.""" - destination = self.tot_waypoint - total = timedelta() - for previous_waypoint, waypoint in self.edges(): - if waypoint == self.tot_waypoint: - # For anything strike-like the TOT waypoint is the *flight's* - # mission target, but to synchronize with the rest of the - # package we need to use the travel time to the same position as - # the others. - total += self.travel_time_between_waypoints( - previous_waypoint, self.target_area_waypoint - ) - break - total += self.travel_time_between_waypoints(previous_waypoint, waypoint) - else: - raise PlanningError( - f"Did not find destination waypoint {destination} in " - f"waypoints for {self.flight}" - ) - return total - @property def join_time(self) -> timedelta: - travel_time = self.travel_time_between_waypoints( + travel_time = self.total_time_between_waypoints( self.layout.join, self.layout.ingress ) return self.ingress_time - travel_time @property def split_time(self) -> timedelta: - travel_time_ingress = self.travel_time_between_waypoints( + travel_time_ingress = self.total_time_between_waypoints( self.layout.ingress, self.target_area_waypoint ) - travel_time_egress = self.travel_time_between_waypoints( + travel_time_egress = self.total_time_between_waypoints( self.target_area_waypoint, self.layout.split ) minutes_at_target = 0.75 * len(self.layout.targets) @@ -106,7 +82,7 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC): @property def ingress_time(self) -> timedelta: tot = self.tot - travel_time = self.travel_time_between_waypoints( + travel_time = self.total_time_between_waypoints( self.layout.ingress, self.target_area_waypoint ) return tot - travel_time diff --git a/game/ato/flightplans/loiter.py b/game/ato/flightplans/loiter.py index ee306907..803fa729 100644 --- a/game/ato/flightplans/loiter.py +++ b/game/ato/flightplans/loiter.py @@ -33,10 +33,10 @@ class LoiterFlightPlan(StandardFlightPlan[Any], ABC): return self.push_time + self.tot_offset return None - def travel_time_between_waypoints( + def total_time_between_waypoints( self, a: FlightWaypoint, b: FlightWaypoint ) -> timedelta: - travel_time = super().travel_time_between_waypoints(a, b) + travel_time = super().total_time_between_waypoints(a, b) if a != self.layout.hold: return travel_time return travel_time + self.hold_duration diff --git a/game/ato/flightplans/packagerefueling.py b/game/ato/flightplans/packagerefueling.py index 68e7269e..dea4afbe 100644 --- a/game/ato/flightplans/packagerefueling.py +++ b/game/ato/flightplans/packagerefueling.py @@ -59,10 +59,10 @@ class PackageRefuelingFlightPlan(RefuelingFlightPlan): "REFUEL", FlightWaypointType.REFUEL, refuel, altitude ) - delay_target_to_split: timedelta = self.travel_time_between_waypoints( + delay_target_to_split: timedelta = self.total_time_between_waypoints( self.target_area_waypoint(), split_waypoint ) - delay_split_to_refuel: timedelta = self.travel_time_between_waypoints( + delay_split_to_refuel: timedelta = self.total_time_between_waypoints( split_waypoint, refuel_waypoint ) diff --git a/game/ato/flightplans/sweep.py b/game/ato/flightplans/sweep.py index 68e7e0b7..e332dbc0 100644 --- a/game/ato/flightplans/sweep.py +++ b/game/ato/flightplans/sweep.py @@ -56,7 +56,7 @@ class SweepFlightPlan(LoiterFlightPlan): @property def sweep_start_time(self) -> timedelta: - travel_time = self.travel_time_between_waypoints( + travel_time = self.total_time_between_waypoints( self.layout.sweep_start, self.layout.sweep_end ) return self.sweep_end_time - travel_time diff --git a/game/ato/flightstate/inflight.py b/game/ato/flightstate/inflight.py index c9af70c5..53e4f852 100644 --- a/game/ato/flightstate/inflight.py +++ b/game/ato/flightstate/inflight.py @@ -51,17 +51,9 @@ class InFlight(FlightState, ABC): return index <= self.waypoint_index def travel_time_between_waypoints(self) -> timedelta: - travel_time = self.flight.flight_plan.travel_time_between_waypoints( + return self.flight.flight_plan.travel_time_between_waypoints( self.current_waypoint, self.next_waypoint ) - if self.current_waypoint.waypoint_type is FlightWaypointType.LOITER: - # Loiter time is already built into travel_time_between_waypoints. If we're - # at a loiter point but still a regular InFlight (Loiter overrides this - # method) that means we're traveling from the loiter point but no longer - # loitering. - assert self.flight.flight_plan.is_loiter(self.flight.flight_plan) - travel_time -= self.flight.flight_plan.hold_duration - return travel_time @abstractmethod def estimate_position(self) -> Point: