Add types for distance and speed.

Not converting all at once so I can prove the concept. After that we'll
want to cover all the cases where an int distance or speed is a part of
the save game (I've done one of them here with `Flight.alt`) so further
cleanups don't break save compat.

https://github.com/Khopa/dcs_liberation/issues/558
This commit is contained in:
Dan Albert
2020-11-20 13:57:53 -08:00
parent 44bc2d769b
commit 113947b9f0
11 changed files with 269 additions and 157 deletions

View File

@@ -85,7 +85,7 @@ from game.theater.controlpoint import (
)
from game.theater.theatergroundobject import TheaterGroundObject
from game.unitmap import UnitMap
from game.utils import knots_to_kph, nm_to_meter
from game.utils import Distance, Speed, knots_to_kph, kph, meters, nm_to_meter
from gen.airsupportgen import AirSupport
from gen.ato import AirTaskingOrder, Package
from gen.callsigns import create_group_callsign_from_unit
@@ -110,12 +110,12 @@ from .naming import namegen
if TYPE_CHECKING:
from game import Game
WARM_START_HELI_AIRSPEED = 120
WARM_START_HELI_ALT = 500
WARM_START_ALTITUDE = 3000
WARM_START_AIRSPEED = 550
WARM_START_HELI_AIRSPEED = kph(120)
WARM_START_HELI_ALT = meters(500)
WARM_START_ALTITUDE = meters(3000)
WARM_START_AIRSPEED = kph(550)
RTB_ALTITUDE = 800
RTB_ALTITUDE = meters(800)
RTB_DISTANCE = 5000
HELI_ALT = 500
@@ -867,7 +867,9 @@ class AircraftConflictGenerator:
start_type=self._start_type(start_type),
group_size=count)
def _add_radio_waypoint(self, group: FlyingGroup, position, altitude: int, airspeed: int = 600):
def _add_radio_waypoint(self, group: FlyingGroup, position,
altitude: Distance,
airspeed: int = 600) -> MovingPoint:
point = group.add_waypoint(position, altitude, airspeed)
point.alt_type = "RADIO"
return point
@@ -884,7 +886,8 @@ class AircraftConflictGenerator:
tod_location = position.point_from_heading(heading, RTB_DISTANCE)
self._add_radio_waypoint(group, tod_location, last_waypoint.alt)
destination_waypoint = self._add_radio_waypoint(group, position, RTB_ALTITUDE)
destination_waypoint = self._add_radio_waypoint(group, position,
RTB_ALTITUDE)
if isinstance(at, Airport):
group.land_at(at)
return destination_waypoint
@@ -1380,7 +1383,8 @@ class PydcsWaypointBuilder:
def build(self) -> MovingPoint:
waypoint = self.group.add_waypoint(
Point(self.waypoint.x, self.waypoint.y), self.waypoint.alt,
Point(self.waypoint.x, self.waypoint.y),
self.waypoint.alt.meters,
name=self.mission.string(self.waypoint.name))
if self.waypoint.flyover:

View File

@@ -10,6 +10,7 @@ from dcs.unittype import FlyingType
from game import db
from game.theater.controlpoint import ControlPoint, MissionTarget
from game.utils import Distance, meters
if TYPE_CHECKING:
from gen.ato import Package
@@ -67,7 +68,7 @@ class FlightWaypointType(Enum):
class FlightWaypoint:
def __init__(self, waypoint_type: FlightWaypointType, x: float, y: float,
alt: int = 0) -> None:
alt: Distance = meters(0)) -> None:
"""Creates a flight waypoint.
Args:

View File

@@ -28,7 +28,7 @@ from game.theater import (
TheaterGroundObject,
)
from game.theater.theatergroundobject import EwrGroundObject
from game.utils import nm_to_meter, meter_to_nm
from game.utils import Distance, meters, nm_to_meter, meter_to_nm
from .closestairfields import ObjectiveDistanceCache
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
from .traveltime import GroundSpeed, TravelTime
@@ -537,7 +537,8 @@ class StrikeFlightPlan(FormationFlightPlan):
def target_area_waypoint(self) -> FlightWaypoint:
return FlightWaypoint(FlightWaypointType.TARGET_GROUP_LOC,
self.package.target.position.x,
self.package.target.position.y, 0)
self.package.target.position.y,
meters(0))
@property
def travel_time_to_target(self) -> timedelta:
@@ -863,10 +864,10 @@ class FlightPlanBuilder:
raise InvalidObjectiveLocation(flight.flight_type, location)
start, end = self.racetrack_for_objective(location)
patrol_alt = random.randint(
patrol_alt = meters(random.randint(
self.doctrine.min_patrol_altitude,
self.doctrine.max_patrol_altitude
)
))
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
start, end = builder.race_track(start, end, patrol_alt)
@@ -983,8 +984,8 @@ class FlightPlanBuilder:
"""
location = self.package.target
patrol_alt = random.randint(self.doctrine.min_patrol_altitude,
self.doctrine.max_patrol_altitude)
patrol_alt = meters(random.randint(self.doctrine.min_patrol_altitude,
self.doctrine.max_patrol_altitude))
# Create points
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)

View File

@@ -8,7 +8,13 @@ from typing import Optional, TYPE_CHECKING
from dcs.mapping import Point
from dcs.unittype import FlyingType
from game.utils import meter_to_nm
from game.utils import (
Distance,
SPEED_OF_SOUND_AT_SEA_LEVEL,
Speed,
kph, mach, meter_to_nm,
meters,
)
from gen.flights.flight import Flight
if TYPE_CHECKING:
@@ -18,7 +24,7 @@ if TYPE_CHECKING:
class GroundSpeed:
@classmethod
def for_flight(cls, flight: Flight, altitude: int) -> int:
def for_flight(cls, flight: Flight, altitude: Distance) -> int:
if not issubclass(flight.unit_type, FlyingType):
raise TypeError("Flight has non-flying unit")
@@ -27,56 +33,20 @@ class GroundSpeed:
# on fuel, but mission speed will be fast enough to keep the flight
# safer.
c_sound_sea_level = 661.5
# DCS's max speed is in kph at 0 MSL. Convert to knots.
max_speed = flight.unit_type.max_speed * 0.539957
if max_speed > c_sound_sea_level:
# DCS's max speed is in kph at 0 MSL.
max_speed = kph(flight.unit_type.max_speed)
if max_speed > SPEED_OF_SOUND_AT_SEA_LEVEL:
# Aircraft is supersonic. Limit to mach 0.8 to conserve fuel and
# account for heavily loaded jets.
return int(cls.from_mach(0.8, altitude))
return int(mach(0.8, altitude).knots)
# For subsonic aircraft, assume the aircraft can reasonably perform at
# 80% of its maximum, and that it can maintain the same mach at altitude
# as it can at sea level. This probably isn't great assumption, but
# might. be sufficient given the wiggle room. We can come up with
# another heuristic if needed.
mach = max_speed * 0.8 / c_sound_sea_level
return int(cls.from_mach(mach, altitude)) # knots
@staticmethod
def from_mach(mach: float, altitude_m: int) -> float:
"""Returns the ground speed in knots for the given mach and altitude.
Args:
mach: The mach number to convert to ground speed.
altitude_m: The altitude in meters.
Returns:
The ground speed corresponding to the given altitude and mach number
in knots.
"""
# https://www.grc.nasa.gov/WWW/K-12/airplane/atmos.html
altitude_ft = altitude_m * 3.28084
if altitude_ft <= 36152:
temperature_f = 59 - 0.00356 * altitude_ft
else:
# There's another formula for altitudes over 82k feet, but we better
# not be planning waypoints that high...
temperature_f = -70
temperature_k = (temperature_f + 459.67) * (5 / 9)
# https://www.engineeringtoolbox.com/specific-heat-ratio-d_602.html
# Dependent on temperature, but varies very little (+/-0.001)
# between -40F and 180F.
heat_capacity_ratio = 1.4
# https://www.grc.nasa.gov/WWW/K-12/airplane/sound.html
gas_constant = 286 # m^2/s^2/K
c_sound = math.sqrt(heat_capacity_ratio * gas_constant * temperature_k)
# c_sound is in m/s, convert to knots.
return (c_sound * 1.944) * mach
cruise_mach = max_speed.mach() * 0.8
return int(mach(cruise_mach, altitude).knots)
class TravelTime:

View File

@@ -14,6 +14,7 @@ from game.theater import (
OffMapSpawn,
TheaterGroundObject,
)
from game.utils import Distance, meters
from game.weather import Conditions
from .flight import Flight, FlightWaypoint, FlightWaypointType
@@ -53,7 +54,9 @@ class WaypointBuilder:
FlightWaypointType.NAV,
position.x,
position.y,
500 if self.is_helo else self.doctrine.rendezvous_altitude
meters(
500
) if self.is_helo else self.doctrine.rendezvous_altitude
)
waypoint.name = "NAV"
waypoint.alt_type = "BARO"
@@ -64,7 +67,7 @@ class WaypointBuilder:
FlightWaypointType.TAKEOFF,
position.x,
position.y,
0
meters(0)
)
waypoint.name = "TAKEOFF"
waypoint.alt_type = "RADIO"
@@ -84,7 +87,9 @@ class WaypointBuilder:
FlightWaypointType.NAV,
position.x,
position.y,
500 if self.is_helo else self.doctrine.rendezvous_altitude
meters(
500
) if self.is_helo else self.doctrine.rendezvous_altitude
)
waypoint.name = "NAV"
waypoint.alt_type = "BARO"
@@ -95,7 +100,7 @@ class WaypointBuilder:
FlightWaypointType.LANDING_POINT,
position.x,
position.y,
0
meters(0)
)
waypoint.name = "LANDING"
waypoint.alt_type = "RADIO"
@@ -116,12 +121,12 @@ class WaypointBuilder:
position = divert.position
if isinstance(divert, OffMapSpawn):
if self.is_helo:
altitude = 500
altitude = meters(500)
else:
altitude = self.doctrine.rendezvous_altitude
altitude_type = "BARO"
else:
altitude = 0
altitude = meters(0)
altitude_type = "RADIO"
waypoint = FlightWaypoint(
@@ -142,7 +147,9 @@ class WaypointBuilder:
FlightWaypointType.LOITER,
position.x,
position.y,
500 if self.is_helo else self.doctrine.rendezvous_altitude
meters(
500
) if self.is_helo else self.doctrine.rendezvous_altitude
)
waypoint.pretty_name = "Hold"
waypoint.description = "Wait until push time"
@@ -154,7 +161,9 @@ class WaypointBuilder:
FlightWaypointType.JOIN,
position.x,
position.y,
500 if self.is_helo else self.doctrine.ingress_altitude
meters(
500
) if self.is_helo else self.doctrine.ingress_altitude
)
waypoint.pretty_name = "Join"
waypoint.description = "Rendezvous with package"
@@ -166,7 +175,9 @@ class WaypointBuilder:
FlightWaypointType.SPLIT,
position.x,
position.y,
500 if self.is_helo else self.doctrine.ingress_altitude
meters(
500
) if self.is_helo else self.doctrine.ingress_altitude
)
waypoint.pretty_name = "Split"
waypoint.description = "Depart from package"
@@ -179,7 +190,9 @@ class WaypointBuilder:
ingress_type,
position.x,
position.y,
500 if self.is_helo else self.doctrine.ingress_altitude
meters(
500
) if self.is_helo else self.doctrine.ingress_altitude
)
waypoint.pretty_name = "INGRESS on " + objective.name
waypoint.description = "INGRESS on " + objective.name
@@ -193,7 +206,9 @@ class WaypointBuilder:
FlightWaypointType.EGRESS,
position.x,
position.y,
500 if self.is_helo else self.doctrine.ingress_altitude
meters(
500
) if self.is_helo else self.doctrine.ingress_altitude
)
waypoint.pretty_name = "EGRESS from " + target.name
waypoint.description = "EGRESS from " + target.name
@@ -218,7 +233,7 @@ class WaypointBuilder:
FlightWaypointType.TARGET_POINT,
target.target.position.x,
target.target.position.y,
0
meters(0)
)
waypoint.description = description
waypoint.pretty_name = description
@@ -249,7 +264,7 @@ class WaypointBuilder:
FlightWaypointType.TARGET_GROUP_LOC,
location.position.x,
location.position.y,
0
meters(0)
)
waypoint.description = name
waypoint.pretty_name = name
@@ -274,7 +289,7 @@ class WaypointBuilder:
FlightWaypointType.CAS,
position.x,
position.y,
500 if self.is_helo else 1000
meters(500) if self.is_helo else meters(1000)
)
waypoint.alt_type = "RADIO"
waypoint.description = "Provide CAS"
@@ -283,12 +298,12 @@ class WaypointBuilder:
return waypoint
@staticmethod
def race_track_start(position: Point, altitude: int) -> FlightWaypoint:
def race_track_start(position: Point, altitude: Distance) -> FlightWaypoint:
"""Creates a racetrack start waypoint.
Args:
position: Position of the waypoint.
altitude: Altitude of the racetrack in meters.
altitude: Altitude of the racetrack.
"""
waypoint = FlightWaypoint(
FlightWaypointType.PATROL_TRACK,
@@ -302,12 +317,12 @@ class WaypointBuilder:
return waypoint
@staticmethod
def race_track_end(position: Point, altitude: int) -> FlightWaypoint:
def race_track_end(position: Point, altitude: Distance) -> FlightWaypoint:
"""Creates a racetrack end waypoint.
Args:
position: Position of the waypoint.
altitude: Altitude of the racetrack in meters.
altitude: Altitude of the racetrack.
"""
waypoint = FlightWaypoint(
FlightWaypointType.PATROL,
@@ -321,7 +336,7 @@ class WaypointBuilder:
return waypoint
def race_track(self, start: Point, end: Point,
altitude: int) -> Tuple[FlightWaypoint, FlightWaypoint]:
altitude: Distance) -> Tuple[FlightWaypoint, FlightWaypoint]:
"""Creates two waypoint for a racetrack orbit.
Args:
@@ -333,7 +348,7 @@ class WaypointBuilder:
self.race_track_end(end, altitude))
@staticmethod
def sweep_start(position: Point, altitude: int) -> FlightWaypoint:
def sweep_start(position: Point, altitude: Distance) -> FlightWaypoint:
"""Creates a sweep start waypoint.
Args:
@@ -352,7 +367,7 @@ class WaypointBuilder:
return waypoint
@staticmethod
def sweep_end(position: Point, altitude: int) -> FlightWaypoint:
def sweep_end(position: Point, altitude: Distance) -> FlightWaypoint:
"""Creates a sweep end waypoint.
Args:
@@ -371,7 +386,7 @@ class WaypointBuilder:
return waypoint
def sweep(self, start: Point, end: Point,
altitude: int) -> Tuple[FlightWaypoint, FlightWaypoint]:
altitude: Distance) -> Tuple[FlightWaypoint, FlightWaypoint]:
"""Creates two waypoint for a racetrack orbit.
Args:
@@ -404,7 +419,9 @@ class WaypointBuilder:
FlightWaypointType.TARGET_GROUP_LOC,
target.position.x,
target.position.y,
500 if self.is_helo else self.doctrine.ingress_altitude
meters(
500
) if self.is_helo else self.doctrine.ingress_altitude
)
waypoint.name = "TARGET"
waypoint.description = "Escort the package"

View File

@@ -153,7 +153,7 @@ class FlightPlanBuilder:
self.rows.append([
str(waypoint.number),
waypoint.waypoint.pretty_name,
str(int(units.meters_to_feet(waypoint.waypoint.alt))),
str(int(waypoint.waypoint.alt.feet)),
self._waypoint_distance(waypoint.waypoint),
self._ground_speed(waypoint.waypoint),
self._format_time(waypoint.waypoint.tot),