From 30801dff9f9561058e021a0e2e467f79ad10690c Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Tue, 3 Aug 2021 12:22:55 +0200 Subject: [PATCH] Use more sensible patrol speeds for CAP, and fix is_helo (#1492) * Use more sensible patrol speeds for CAP, and fix is_helo --- game/dcs/aircrafttype.py | 46 ++++++++++++++++++++++++++++++++-- gen/aircraft.py | 16 ++++-------- gen/flights/flight.py | 4 +-- gen/flights/flightplan.py | 39 ++++++++++++++++++++++++---- gen/flights/waypointbuilder.py | 3 ++- 5 files changed, 87 insertions(+), 21 deletions(-) diff --git a/game/dcs/aircrafttype.py b/game/dcs/aircrafttype.py index 39e3b06d..1ce958ca 100644 --- a/game/dcs/aircrafttype.py +++ b/game/dcs/aircrafttype.py @@ -29,7 +29,15 @@ from game.radio.channels import ( ViggenRadioChannelAllocator, NoOpChannelAllocator, ) -from game.utils import Distance, Speed, feet, kph, knots, nautical_miles +from game.utils import ( + Distance, + SPEED_OF_SOUND_AT_SEA_LEVEL, + Speed, + feet, + kph, + knots, + nautical_miles, +) if TYPE_CHECKING: from gen.aircraft import FlightData @@ -182,7 +190,7 @@ class AircraftType(UnitType[Type[FlyingType]]): @property def preferred_patrol_altitude(self) -> Distance: - if self.patrol_altitude: + if self.patrol_altitude is not None: return self.patrol_altitude else: # Estimate based on max speed. @@ -208,6 +216,40 @@ class AircraftType(UnitType[Type[FlyingType]]): min(altitude_for_highest_speed, rounded_altitude), ) + def preferred_patrol_speed(self, altitude: Distance) -> Speed: + """Preferred true airspeed when patrolling""" + if self.patrol_speed is not None: + return self.patrol_speed + else: + # Estimate based on max speed. + max_speed = self.max_speed + if max_speed > SPEED_OF_SOUND_AT_SEA_LEVEL * 1.6: + # Fast airplanes, should manage pretty high patrol speed + return ( + Speed.from_mach(0.85, altitude) + if altitude.feet > 20000 + else Speed.from_mach(0.7, altitude) + ) + elif max_speed > SPEED_OF_SOUND_AT_SEA_LEVEL * 1.2: + # Medium-fast like F/A-18C + return ( + Speed.from_mach(0.8, altitude) + if altitude.feet > 20000 + else Speed.from_mach(0.65, altitude) + ) + elif max_speed > SPEED_OF_SOUND_AT_SEA_LEVEL * 0.7: + # Semi-fast like airliners or similar + return ( + Speed.from_mach(0.5, altitude) + if altitude.feet > 20000 + else Speed.from_mach(0.4, altitude) + ) + else: + # Slow like warbirds or helicopters + # Use whichever is slowest - mach 0.35 or 70% of max speed + logging.debug(f"{self.name} max_speed * 0.7 is {max_speed * 0.7}") + return min(Speed.from_mach(0.35, altitude), max_speed * 0.7) + def alloc_flight_radio(self, radio_registry: RadioRegistry) -> RadioFrequency: from gen.radios import ChannelInUseError, kHz diff --git a/gen/aircraft.py b/gen/aircraft.py index 42a5ccac..d42e52f2 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -1775,17 +1775,11 @@ class RaceTrackBuilder(PydcsWaypointBuilder): ) ) - # TODO: Set orbit speeds for all race tracks and remove this special case. - if isinstance(flight_plan, RefuelingFlightPlan): - orbit = OrbitAction( - altitude=waypoint.alt, - pattern=OrbitAction.OrbitPattern.RaceTrack, - speed=int(flight_plan.patrol_speed.kph), - ) - else: - orbit = OrbitAction( - altitude=waypoint.alt, pattern=OrbitAction.OrbitPattern.RaceTrack - ) + orbit = OrbitAction( + altitude=waypoint.alt, + pattern=OrbitAction.OrbitPattern.RaceTrack, + speed=int(flight_plan.patrol_speed.kph), + ) racetrack = ControlledTask(orbit) self.set_waypoint_tot(waypoint, flight_plan.patrol_start_time) diff --git a/gen/flights/flight.py b/gen/flights/flight.py index 5c3241f7..9bb0c9bc 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -141,8 +141,8 @@ class FlightWaypoint: waypoint_type: The waypoint type. x: X coordinate of the waypoint. y: Y coordinate of the waypoint. - alt: Altitude of the waypoint. By default this is AGL, but it can be - changed to MSL by setting alt_type to "RADIO". + alt: Altitude of the waypoint. By default this is MSL, but it can be + changed to AGL by setting alt_type to "RADIO" """ self.waypoint_type = waypoint_type self.x = x diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index 463be24d..408eb5fd 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -411,6 +411,9 @@ class PatrollingFlightPlan(FlightPlan): #: Maximum time to remain on station. patrol_duration: timedelta + #: Racetrack speed TAS. + patrol_speed: Speed + #: 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 @@ -779,9 +782,6 @@ class RefuelingFlightPlan(PatrollingFlightPlan): divert: Optional[FlightWaypoint] bullseye: FlightWaypoint - #: Racetrack speed. - patrol_speed: Speed - def iter_waypoints(self) -> Iterator[FlightWaypoint]: yield self.takeoff yield from self.nav_to @@ -1124,6 +1124,11 @@ class FlightPlanBuilder: min(self.doctrine.max_patrol_altitude, randomized_alt), ) + patrol_speed = flight.unit_type.preferred_patrol_speed(patrol_alt) + logging.debug( + f"BARCAP patrol speed for {flight.unit_type.name} at {patrol_alt.feet}ft: {patrol_speed.knots} KTAS" + ) + builder = WaypointBuilder(flight, self.coalition) start, end = builder.race_track(start_pos, end_pos, patrol_alt) @@ -1131,6 +1136,7 @@ class FlightPlanBuilder: package=self.package, flight=flight, patrol_duration=self.doctrine.cap_duration, + patrol_speed=patrol_speed, engagement_distance=self.doctrine.cap_engagement_range, takeoff=builder.takeoff(flight.departure), nav_to=builder.nav_path( @@ -1362,6 +1368,10 @@ class FlightPlanBuilder: self.doctrine.min_patrol_altitude, min(self.doctrine.max_patrol_altitude, randomized_alt), ) + patrol_speed = flight.unit_type.preferred_patrol_speed(patrol_alt) + logging.debug( + f"TARCAP patrol speed for {flight.unit_type.name} at {patrol_alt.feet}ft: {patrol_speed.knots} KTAS" + ) # Create points builder = WaypointBuilder(flight, self.coalition) @@ -1383,6 +1393,7 @@ class FlightPlanBuilder: # requests an escort the CAP flight will remain on station for the # duration of the escorted mission, or until it is winchester/bingo. patrol_duration=self.doctrine.cap_duration, + patrol_speed=patrol_speed, engagement_distance=self.doctrine.cap_engagement_range, takeoff=builder.takeoff(flight.departure), nav_to=builder.nav_path(flight.departure.position, orbit0p, patrol_alt), @@ -1546,16 +1557,33 @@ class FlightPlanBuilder: builder = WaypointBuilder(flight, self.coalition) + # 2021-08-02: patrol_speed will currently have no effect because + # CAS doesn't use OrbitAction. But all PatrollingFlightPlan are expected + # to have patrol_speed + is_helo = flight.unit_type.dcs_unit_type.helicopter + ingress_egress_altitude = ( + self.doctrine.ingress_altitude if not is_helo else meters(50) + ) + patrol_speed = flight.unit_type.preferred_patrol_speed(ingress_egress_altitude) + use_agl_ingress_egress = is_helo + return CasFlightPlan( package=self.package, flight=flight, patrol_duration=self.doctrine.cas_duration, + patrol_speed=patrol_speed, takeoff=builder.takeoff(flight.departure), nav_to=builder.nav_path( - flight.departure.position, ingress, self.doctrine.ingress_altitude + flight.departure.position, + ingress, + ingress_egress_altitude, + use_agl_ingress_egress, ), nav_from=builder.nav_path( - egress, flight.arrival.position, self.doctrine.ingress_altitude + egress, + flight.arrival.position, + ingress_egress_altitude, + use_agl_ingress_egress, ), patrol_start=builder.ingress( FlightWaypointType.INGRESS_CAS, ingress, location @@ -1608,6 +1636,7 @@ class FlightPlanBuilder: else: altitude = feet(21000) + # TODO: Could use flight.unit_type.preferred_patrol_speed(altitude) instead. if tanker_type.patrol_speed is not None: speed = tanker_type.patrol_speed else: diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index 05ca1d93..37e23bb4 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -1,4 +1,5 @@ from __future__ import annotations +import logging import random from dataclasses import dataclass @@ -55,7 +56,7 @@ class WaypointBuilder: @property def is_helo(self) -> bool: - return getattr(self.flight.unit_type, "helicopter", False) + return self.flight.unit_type.dcs_unit_type.helicopter def takeoff(self, departure: ControlPoint) -> FlightWaypoint: """Create takeoff waypoint for the given arrival airfield or carrier.