mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Convert to new unit APIs, remove old APIs.
There are probably plenty of raw ints around that never used the old conversion APIs, but we'll just need to fix those when we see them. Fixes https://github.com/Khopa/dcs_liberation/issues/558
This commit is contained in:
@@ -36,22 +36,12 @@ from game.theater.theatergroundobject import (
|
||||
EwrGroundObject,
|
||||
NavalGroundObject, VehicleGroupGroundObject,
|
||||
)
|
||||
from game.utils import nm_to_meter
|
||||
from game.utils import Distance, nautical_miles, nautical_miles
|
||||
from gen import Conflict
|
||||
from gen.ato import Package
|
||||
from gen.flights.ai_flight_planner_db import (
|
||||
ANTISHIP_CAPABLE,
|
||||
ANTISHIP_PREFERRED,
|
||||
CAP_CAPABLE,
|
||||
CAP_PREFERRED,
|
||||
CAS_CAPABLE,
|
||||
CAS_PREFERRED,
|
||||
RUNWAY_ATTACK_CAPABLE,
|
||||
RUNWAY_ATTACK_PREFERRED,
|
||||
SEAD_CAPABLE,
|
||||
SEAD_PREFERRED,
|
||||
STRIKE_CAPABLE,
|
||||
STRIKE_PREFERRED, capable_aircraft_for_task, preferred_aircraft_for_task,
|
||||
capable_aircraft_for_task,
|
||||
preferred_aircraft_for_task,
|
||||
)
|
||||
from gen.flights.closestairfields import (
|
||||
ClosestAirfields,
|
||||
@@ -85,7 +75,7 @@ class ProposedFlight:
|
||||
num_aircraft: int
|
||||
|
||||
#: The maximum distance between the objective and the departure airfield.
|
||||
max_distance: int
|
||||
max_distance: Distance
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.task} {self.num_aircraft} ship"
|
||||
@@ -212,7 +202,7 @@ class PackageBuilder:
|
||||
|
||||
def find_divert_field(self, aircraft: FlyingType,
|
||||
arrival: ControlPoint) -> Optional[ControlPoint]:
|
||||
divert_limit = nm_to_meter(150)
|
||||
divert_limit = nautical_miles(150)
|
||||
for airfield in self.closest_airfields.airfields_within(divert_limit):
|
||||
if airfield.captured != self.is_player:
|
||||
continue
|
||||
@@ -241,8 +231,8 @@ class ObjectiveFinder:
|
||||
"""Identifies potential objectives for the mission planner."""
|
||||
|
||||
# TODO: Merge into doctrine.
|
||||
AIRFIELD_THREAT_RANGE = nm_to_meter(150)
|
||||
SAM_THREAT_RANGE = nm_to_meter(100)
|
||||
AIRFIELD_THREAT_RANGE = nautical_miles(150)
|
||||
SAM_THREAT_RANGE = nautical_miles(100)
|
||||
|
||||
def __init__(self, game: Game, is_player: bool) -> None:
|
||||
self.game = game
|
||||
@@ -467,13 +457,13 @@ class CoalitionMissionPlanner:
|
||||
"""
|
||||
|
||||
# TODO: Merge into doctrine, also limit by aircraft.
|
||||
MAX_CAP_RANGE = nm_to_meter(100)
|
||||
MAX_CAS_RANGE = nm_to_meter(50)
|
||||
MAX_ANTISHIP_RANGE = nm_to_meter(150)
|
||||
MAX_BAI_RANGE = nm_to_meter(150)
|
||||
MAX_OCA_RANGE = nm_to_meter(150)
|
||||
MAX_SEAD_RANGE = nm_to_meter(150)
|
||||
MAX_STRIKE_RANGE = nm_to_meter(150)
|
||||
MAX_CAP_RANGE = nautical_miles(100)
|
||||
MAX_CAS_RANGE = nautical_miles(50)
|
||||
MAX_ANTISHIP_RANGE = nautical_miles(150)
|
||||
MAX_BAI_RANGE = nautical_miles(150)
|
||||
MAX_OCA_RANGE = nautical_miles(150)
|
||||
MAX_SEAD_RANGE = nautical_miles(150)
|
||||
MAX_STRIKE_RANGE = nautical_miles(150)
|
||||
|
||||
def __init__(self, game: Game, is_player: bool) -> None:
|
||||
self.game = game
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
from typing import Dict, Iterator, List, Optional
|
||||
|
||||
from game.theater import ConflictTheater, ControlPoint, MissionTarget
|
||||
from game.utils import Distance
|
||||
|
||||
|
||||
class ClosestAirfields:
|
||||
@@ -14,14 +15,14 @@ class ClosestAirfields:
|
||||
all_control_points, key=lambda c: self.target.distance_to(c)
|
||||
)
|
||||
|
||||
def airfields_within(self, meters: int) -> Iterator[ControlPoint]:
|
||||
def airfields_within(self, distance: Distance) -> Iterator[ControlPoint]:
|
||||
"""Iterates over all airfields within the given range of the target.
|
||||
|
||||
Note that this iterates over *all* airfields, not just friendly
|
||||
airfields.
|
||||
"""
|
||||
for cp in self.closest_airfields:
|
||||
if cp.distance_to(self.target) < meters:
|
||||
if cp.distance_to(self.target) < distance.meters:
|
||||
yield cp
|
||||
else:
|
||||
break
|
||||
|
||||
@@ -106,7 +106,7 @@ class FlightWaypoint:
|
||||
def from_pydcs(cls, point: MovingPoint,
|
||||
from_cp: ControlPoint) -> "FlightWaypoint":
|
||||
waypoint = FlightWaypoint(FlightWaypointType.NAV, point.position.x,
|
||||
point.position.y, point.alt)
|
||||
point.position.y, meters(point.alt))
|
||||
waypoint.alt_type = point.alt_type
|
||||
# Other actions exist... but none of them *should* be the first
|
||||
# waypoint for a flight.
|
||||
|
||||
@@ -28,7 +28,7 @@ from game.theater import (
|
||||
TheaterGroundObject,
|
||||
)
|
||||
from game.theater.theatergroundobject import EwrGroundObject
|
||||
from game.utils import Distance, meters, nm_to_meter, meter_to_nm
|
||||
from game.utils import Distance, Speed, meters, nautical_miles
|
||||
from .closestairfields import ObjectiveDistanceCache
|
||||
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
|
||||
from .traveltime import GroundSpeed, TravelTime
|
||||
@@ -86,7 +86,7 @@ class FlightPlan:
|
||||
return zip(self.waypoints[:last_index], self.waypoints[1:last_index])
|
||||
|
||||
def best_speed_between_waypoints(self, a: FlightWaypoint,
|
||||
b: FlightWaypoint) -> int:
|
||||
b: FlightWaypoint) -> Speed:
|
||||
"""Desired ground speed between points a and b."""
|
||||
factor = 1.0
|
||||
if b.waypoint_type == FlightWaypointType.ASCEND_POINT:
|
||||
@@ -105,11 +105,10 @@ class FlightPlan:
|
||||
# We don't have an exact heightmap, but we should probably be performing
|
||||
# *some* adjustment for NTTR since the minimum altitude of the map is
|
||||
# near 2000 ft MSL.
|
||||
return int(
|
||||
GroundSpeed.for_flight(self.flight, min(a.alt, b.alt)) * factor)
|
||||
return GroundSpeed.for_flight(self.flight, min(a.alt, b.alt)) * factor
|
||||
|
||||
def speed_between_waypoints(self, a: FlightWaypoint,
|
||||
b: FlightWaypoint) -> int:
|
||||
b: FlightWaypoint) -> Speed:
|
||||
return self.best_speed_between_waypoints(a, b)
|
||||
|
||||
@property
|
||||
@@ -126,16 +125,17 @@ class FlightPlan:
|
||||
def bingo_fuel(self) -> int:
|
||||
"""Bingo fuel value for the FlightPlan
|
||||
"""
|
||||
distance_to_arrival = meter_to_nm(self.max_distance_from(self.flight.arrival))
|
||||
distance_to_arrival = self.max_distance_from(self.flight.arrival)
|
||||
|
||||
bingo = 1000 # Minimum Emergency Fuel
|
||||
bingo += 500 # Visual Traffic
|
||||
bingo += 15 * distance_to_arrival
|
||||
bingo = 1000.0 # Minimum Emergency Fuel
|
||||
bingo += 500 # Visual Traffic
|
||||
bingo += 15 * distance_to_arrival.nautical_miles
|
||||
|
||||
# TODO: Per aircraft tweaks.
|
||||
|
||||
if self.flight.divert is not None:
|
||||
bingo += 10 * meter_to_nm(self.max_distance_from(self.flight.divert))
|
||||
max_divert_distance = self.max_distance_from(self.flight.divert)
|
||||
bingo += 10 * max_divert_distance.nautical_miles
|
||||
|
||||
return round(bingo / 100) * 100
|
||||
|
||||
@@ -145,13 +145,14 @@ class FlightPlan:
|
||||
"""
|
||||
return self.bingo_fuel + 1000
|
||||
|
||||
def max_distance_from(self, cp: ControlPoint) -> int:
|
||||
def max_distance_from(self, cp: ControlPoint) -> Distance:
|
||||
"""Returns the farthest waypoint of the flight plan from a ControlPoint.
|
||||
:arg cp The ControlPoint to measure distance from.
|
||||
"""
|
||||
if not self.waypoints:
|
||||
return 0
|
||||
return max([cp.position.distance_to_point(w.position) for w in self.waypoints])
|
||||
return meters(0)
|
||||
return max([meters(cp.position.distance_to_point(w.position)) for w in
|
||||
self.waypoints])
|
||||
|
||||
@property
|
||||
def tot_offset(self) -> timedelta:
|
||||
@@ -303,7 +304,7 @@ class FormationFlightPlan(LoiterFlightPlan):
|
||||
return self.split
|
||||
|
||||
@cached_property
|
||||
def best_flight_formation_speed(self) -> int:
|
||||
def best_flight_formation_speed(self) -> Speed:
|
||||
"""The best speed this flight is capable at all formation waypoints.
|
||||
|
||||
To ease coordination with other flights, we aim to have a single mission
|
||||
@@ -319,7 +320,7 @@ class FormationFlightPlan(LoiterFlightPlan):
|
||||
return min(speeds)
|
||||
|
||||
def speed_between_waypoints(self, a: FlightWaypoint,
|
||||
b: FlightWaypoint) -> int:
|
||||
b: FlightWaypoint) -> Speed:
|
||||
if b in self.package_speed_waypoints:
|
||||
# Should be impossible, as any package with at least one
|
||||
# FormationFlightPlan flight needs a formation speed.
|
||||
@@ -519,7 +520,7 @@ class StrikeFlightPlan(FormationFlightPlan):
|
||||
} | set(self.targets)
|
||||
|
||||
def speed_between_waypoints(self, a: FlightWaypoint,
|
||||
b: FlightWaypoint) -> int:
|
||||
b: FlightWaypoint) -> Speed:
|
||||
# FlightWaypoint is only comparable by identity, so adding
|
||||
# target_area_waypoint to package_speed_waypoints is useless.
|
||||
if b.waypoint_type == FlightWaypointType.TARGET_GROUP_LOC:
|
||||
@@ -563,7 +564,7 @@ class StrikeFlightPlan(FormationFlightPlan):
|
||||
return total
|
||||
|
||||
@property
|
||||
def mission_speed(self) -> int:
|
||||
def mission_speed(self) -> Speed:
|
||||
return GroundSpeed.for_flight(self.flight, self.ingress.alt)
|
||||
|
||||
@property
|
||||
@@ -865,8 +866,8 @@ class FlightPlanBuilder:
|
||||
|
||||
start, end = self.racetrack_for_objective(location)
|
||||
patrol_alt = meters(random.randint(
|
||||
self.doctrine.min_patrol_altitude,
|
||||
self.doctrine.max_patrol_altitude
|
||||
int(self.doctrine.min_patrol_altitude.meters),
|
||||
int(self.doctrine.max_patrol_altitude.meters)
|
||||
))
|
||||
|
||||
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||
@@ -893,7 +894,7 @@ class FlightPlanBuilder:
|
||||
|
||||
heading = self._heading_to_package_airfield(target)
|
||||
start = target.point_from_heading(heading,
|
||||
-self.doctrine.sweep_distance)
|
||||
-self.doctrine.sweep_distance.meters)
|
||||
|
||||
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||
start, end = builder.sweep(start, target,
|
||||
@@ -930,10 +931,11 @@ class FlightPlanBuilder:
|
||||
closest_airfield.position
|
||||
)
|
||||
|
||||
min_distance_from_enemy = nm_to_meter(20)
|
||||
distance_to_airfield = int(closest_airfield.position.distance_to_point(
|
||||
self.package.target.position
|
||||
))
|
||||
min_distance_from_enemy = nautical_miles(20)
|
||||
distance_to_airfield = meters(
|
||||
closest_airfield.position.distance_to_point(
|
||||
self.package.target.position
|
||||
))
|
||||
distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
|
||||
min_cap_distance = min(self.doctrine.cap_min_distance_from_cp,
|
||||
distance_to_no_fly)
|
||||
@@ -942,11 +944,12 @@ class FlightPlanBuilder:
|
||||
|
||||
end = location.position.point_from_heading(
|
||||
heading,
|
||||
random.randint(min_cap_distance, max_cap_distance)
|
||||
random.randint(int(min_cap_distance.meters),
|
||||
int(max_cap_distance.meters))
|
||||
)
|
||||
diameter = random.randint(
|
||||
self.doctrine.cap_min_track_length,
|
||||
self.doctrine.cap_max_track_length
|
||||
int(self.doctrine.cap_min_track_length.meters),
|
||||
int(self.doctrine.cap_max_track_length.meters)
|
||||
)
|
||||
start = end.point_from_heading(heading - 180, diameter)
|
||||
return start, end
|
||||
@@ -961,7 +964,8 @@ class FlightPlanBuilder:
|
||||
)
|
||||
center = ingress.point_from_heading(heading, distance / 2)
|
||||
orbit_center = center.point_from_heading(
|
||||
heading - 90, random.randint(nm_to_meter(6), nm_to_meter(15))
|
||||
heading - 90, random.randint(int(nautical_miles(6).meters),
|
||||
int(nautical_miles(15).meters))
|
||||
)
|
||||
|
||||
combat_width = distance / 2
|
||||
@@ -984,8 +988,9 @@ class FlightPlanBuilder:
|
||||
"""
|
||||
location = self.package.target
|
||||
|
||||
patrol_alt = meters(random.randint(self.doctrine.min_patrol_altitude,
|
||||
self.doctrine.max_patrol_altitude))
|
||||
patrol_alt = meters(
|
||||
random.randint(int(self.doctrine.min_patrol_altitude.meters),
|
||||
int(self.doctrine.max_patrol_altitude.meters)))
|
||||
|
||||
# Create points
|
||||
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||
@@ -1189,12 +1194,13 @@ class FlightPlanBuilder:
|
||||
# point, plan the hold point such that it retreats from the origin
|
||||
# airfield.
|
||||
return join.point_from_heading(target.heading_between_point(origin),
|
||||
self.doctrine.push_distance)
|
||||
self.doctrine.push_distance.meters)
|
||||
|
||||
heading_to_join = origin.heading_between_point(join)
|
||||
hold_point = origin.point_from_heading(heading_to_join,
|
||||
self.doctrine.push_distance)
|
||||
if hold_point.distance_to_point(join) >= self.doctrine.push_distance:
|
||||
hold_point = origin.point_from_heading(
|
||||
heading_to_join, self.doctrine.push_distance.meters)
|
||||
hold_distance = meters(hold_point.distance_to_point(join))
|
||||
if hold_distance >= self.doctrine.push_distance:
|
||||
# Hold point is between the origin airfield and the join point and
|
||||
# spaced sufficiently.
|
||||
return hold_point
|
||||
@@ -1206,10 +1212,10 @@ class FlightPlanBuilder:
|
||||
# properly.
|
||||
origin_to_join = origin.distance_to_point(join)
|
||||
cos_theta = (
|
||||
(self.doctrine.hold_distance ** 2 +
|
||||
(self.doctrine.hold_distance.meters ** 2 +
|
||||
origin_to_join ** 2 -
|
||||
self.doctrine.join_distance ** 2) /
|
||||
(2 * self.doctrine.hold_distance * origin_to_join)
|
||||
self.doctrine.join_distance.meters ** 2) /
|
||||
(2 * self.doctrine.hold_distance.meters * origin_to_join)
|
||||
)
|
||||
try:
|
||||
theta = math.acos(cos_theta)
|
||||
@@ -1218,10 +1224,10 @@ class FlightPlanBuilder:
|
||||
# hold point away from the target.
|
||||
return origin.point_from_heading(
|
||||
target.heading_between_point(origin),
|
||||
self.doctrine.hold_distance)
|
||||
self.doctrine.hold_distance.meters)
|
||||
|
||||
return origin.point_from_heading(heading_to_join - theta,
|
||||
self.doctrine.hold_distance)
|
||||
self.doctrine.hold_distance.meters)
|
||||
|
||||
# TODO: Make a model for the waypoint builder and use that in the UI.
|
||||
def generate_rtb_waypoint(self, flight: Flight,
|
||||
@@ -1273,13 +1279,13 @@ class FlightPlanBuilder:
|
||||
return attack_transition.point_from_heading(
|
||||
self.package.target.position.heading_between_point(
|
||||
self.package_airfield().position),
|
||||
self.doctrine.join_distance)
|
||||
self.doctrine.join_distance.meters)
|
||||
|
||||
def _advancing_rendezvous_point(self, attack_transition: Point) -> Point:
|
||||
"""Creates a rendezvous point that advances toward the target."""
|
||||
heading = self._heading_to_package_airfield(attack_transition)
|
||||
return attack_transition.point_from_heading(
|
||||
heading, -self.doctrine.join_distance)
|
||||
heading, -self.doctrine.join_distance.meters)
|
||||
|
||||
def _rendezvous_should_retreat(self, attack_transition: Point) -> bool:
|
||||
transition_target_distance = attack_transition.distance_to_point(
|
||||
@@ -1307,13 +1313,13 @@ class FlightPlanBuilder:
|
||||
def _ingress_point(self) -> Point:
|
||||
heading = self._target_heading_to_package_airfield()
|
||||
return self.package.target.position.point_from_heading(
|
||||
heading - 180 + 25, self.doctrine.ingress_egress_distance
|
||||
heading - 180 + 25, self.doctrine.ingress_egress_distance.meters
|
||||
)
|
||||
|
||||
def _egress_point(self) -> Point:
|
||||
heading = self._target_heading_to_package_airfield()
|
||||
return self.package.target.position.point_from_heading(
|
||||
heading - 180 - 25, self.doctrine.ingress_egress_distance
|
||||
heading - 180 - 25, self.doctrine.ingress_egress_distance.meters
|
||||
)
|
||||
|
||||
def _target_heading_to_package_airfield(self) -> int:
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import math
|
||||
from datetime import timedelta
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dcs.mapping import Point
|
||||
from dcs.unittype import FlyingType
|
||||
@@ -12,7 +12,8 @@ from game.utils import (
|
||||
Distance,
|
||||
SPEED_OF_SOUND_AT_SEA_LEVEL,
|
||||
Speed,
|
||||
kph, mach, meter_to_nm,
|
||||
kph,
|
||||
mach,
|
||||
meters,
|
||||
)
|
||||
from gen.flights.flight import Flight
|
||||
@@ -24,7 +25,7 @@ if TYPE_CHECKING:
|
||||
class GroundSpeed:
|
||||
|
||||
@classmethod
|
||||
def for_flight(cls, flight: Flight, altitude: Distance) -> int:
|
||||
def for_flight(cls, flight: Flight, altitude: Distance) -> Speed:
|
||||
if not issubclass(flight.unit_type, FlyingType):
|
||||
raise TypeError("Flight has non-flying unit")
|
||||
|
||||
@@ -38,7 +39,7 @@ class GroundSpeed:
|
||||
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(mach(0.8, altitude).knots)
|
||||
return mach(0.8, altitude)
|
||||
|
||||
# For subsonic aircraft, assume the aircraft can reasonably perform at
|
||||
# 80% of its maximum, and that it can maintain the same mach at altitude
|
||||
@@ -46,15 +47,16 @@ class GroundSpeed:
|
||||
# might. be sufficient given the wiggle room. We can come up with
|
||||
# another heuristic if needed.
|
||||
cruise_mach = max_speed.mach() * 0.8
|
||||
return int(mach(cruise_mach, altitude).knots)
|
||||
return mach(cruise_mach, altitude)
|
||||
|
||||
|
||||
class TravelTime:
|
||||
@staticmethod
|
||||
def between_points(a: Point, b: Point, speed: float) -> timedelta:
|
||||
def between_points(a: Point, b: Point, speed: Speed) -> timedelta:
|
||||
error_factor = 1.1
|
||||
distance = meter_to_nm(a.distance_to_point(b))
|
||||
return timedelta(hours=distance / speed * error_factor)
|
||||
distance = meters(a.distance_to_point(b))
|
||||
return timedelta(
|
||||
hours=distance.nautical_miles / speed.knots * error_factor)
|
||||
|
||||
|
||||
# TODO: Most if not all of this should move into FlightPlan.
|
||||
|
||||
Reference in New Issue
Block a user