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:
Dan Albert 2020-12-19 21:18:18 -08:00
parent 113947b9f0
commit 2ac818dcdd
18 changed files with 224 additions and 253 deletions

View File

@ -1,7 +1,7 @@
from dataclasses import dataclass
from datetime import timedelta
from game.utils import Distance, feet, nm_to_meter, feet_to_meter
from game.utils import Distance, feet, nautical_miles
@dataclass(frozen=True)
@ -12,31 +12,28 @@ class Doctrine:
strike: bool
antiship: bool
strike_max_range: int
sead_max_range: int
rendezvous_altitude: Distance
hold_distance: int
push_distance: int
join_distance: int
split_distance: int
ingress_egress_distance: int
hold_distance: Distance
push_distance: Distance
join_distance: Distance
split_distance: Distance
ingress_egress_distance: Distance
ingress_altitude: Distance
egress_altitude: int
egress_altitude: Distance
min_patrol_altitude: int
max_patrol_altitude: int
pattern_altitude: int
min_patrol_altitude: Distance
max_patrol_altitude: Distance
pattern_altitude: Distance
cap_duration: timedelta
cap_min_track_length: int
cap_max_track_length: int
cap_min_distance_from_cp: int
cap_max_distance_from_cp: int
cap_min_track_length: Distance
cap_max_track_length: Distance
cap_min_distance_from_cp: Distance
cap_max_distance_from_cp: Distance
cas_duration: timedelta
sweep_distance: int
sweep_distance: Distance
MODERN_DOCTRINE = Doctrine(
@ -45,26 +42,24 @@ MODERN_DOCTRINE = Doctrine(
sead=True,
strike=True,
antiship=True,
strike_max_range=1500000,
sead_max_range=1500000,
rendezvous_altitude=feet(25000),
hold_distance=nm_to_meter(15),
push_distance=nm_to_meter(20),
join_distance=nm_to_meter(20),
split_distance=nm_to_meter(20),
ingress_egress_distance=nm_to_meter(45),
hold_distance=nautical_miles(15),
push_distance=nautical_miles(20),
join_distance=nautical_miles(20),
split_distance=nautical_miles(20),
ingress_egress_distance=nautical_miles(45),
ingress_altitude=feet(20000),
egress_altitude=feet_to_meter(20000),
min_patrol_altitude=feet_to_meter(15000),
max_patrol_altitude=feet_to_meter(33000),
pattern_altitude=feet_to_meter(5000),
egress_altitude=feet(20000),
min_patrol_altitude=feet(15000),
max_patrol_altitude=feet(33000),
pattern_altitude=feet(5000),
cap_duration=timedelta(minutes=30),
cap_min_track_length=nm_to_meter(15),
cap_max_track_length=nm_to_meter(40),
cap_min_distance_from_cp=nm_to_meter(10),
cap_max_distance_from_cp=nm_to_meter(40),
cap_min_track_length=nautical_miles(15),
cap_max_track_length=nautical_miles(40),
cap_min_distance_from_cp=nautical_miles(10),
cap_max_distance_from_cp=nautical_miles(40),
cas_duration=timedelta(minutes=30),
sweep_distance=nm_to_meter(60),
sweep_distance=nautical_miles(60),
)
COLDWAR_DOCTRINE = Doctrine(
@ -73,26 +68,24 @@ COLDWAR_DOCTRINE = Doctrine(
sead=True,
strike=True,
antiship=True,
strike_max_range=1500000,
sead_max_range=1500000,
rendezvous_altitude=feet(22000),
hold_distance=nm_to_meter(10),
push_distance=nm_to_meter(10),
join_distance=nm_to_meter(10),
split_distance=nm_to_meter(10),
ingress_egress_distance=nm_to_meter(30),
hold_distance=nautical_miles(10),
push_distance=nautical_miles(10),
join_distance=nautical_miles(10),
split_distance=nautical_miles(10),
ingress_egress_distance=nautical_miles(30),
ingress_altitude=feet(18000),
egress_altitude=feet_to_meter(18000),
min_patrol_altitude=feet_to_meter(10000),
max_patrol_altitude=feet_to_meter(24000),
pattern_altitude=feet_to_meter(5000),
egress_altitude=feet(18000),
min_patrol_altitude=feet(10000),
max_patrol_altitude=feet(24000),
pattern_altitude=feet(5000),
cap_duration=timedelta(minutes=30),
cap_min_track_length=nm_to_meter(12),
cap_max_track_length=nm_to_meter(24),
cap_min_distance_from_cp=nm_to_meter(8),
cap_max_distance_from_cp=nm_to_meter(25),
cap_min_track_length=nautical_miles(12),
cap_max_track_length=nautical_miles(24),
cap_min_distance_from_cp=nautical_miles(8),
cap_max_distance_from_cp=nautical_miles(25),
cas_duration=timedelta(minutes=30),
sweep_distance=nm_to_meter(40),
sweep_distance=nautical_miles(40),
)
WWII_DOCTRINE = Doctrine(
@ -101,24 +94,22 @@ WWII_DOCTRINE = Doctrine(
sead=False,
strike=True,
antiship=True,
strike_max_range=1500000,
sead_max_range=1500000,
hold_distance=nm_to_meter(5),
push_distance=nm_to_meter(5),
join_distance=nm_to_meter(5),
split_distance=nm_to_meter(5),
hold_distance=nautical_miles(5),
push_distance=nautical_miles(5),
join_distance=nautical_miles(5),
split_distance=nautical_miles(5),
rendezvous_altitude=feet(10000),
ingress_egress_distance=nm_to_meter(7),
ingress_egress_distance=nautical_miles(7),
ingress_altitude=feet(8000),
egress_altitude=feet_to_meter(8000),
min_patrol_altitude=feet_to_meter(4000),
max_patrol_altitude=feet_to_meter(15000),
pattern_altitude=feet_to_meter(5000),
egress_altitude=feet(8000),
min_patrol_altitude=feet(4000),
max_patrol_altitude=feet(15000),
pattern_altitude=feet(5000),
cap_duration=timedelta(minutes=30),
cap_min_track_length=nm_to_meter(8),
cap_max_track_length=nm_to_meter(18),
cap_min_distance_from_cp=nm_to_meter(0),
cap_max_distance_from_cp=nm_to_meter(5),
cap_min_track_length=nautical_miles(8),
cap_max_track_length=nautical_miles(18),
cap_min_distance_from_cp=nautical_miles(0),
cap_max_distance_from_cp=nautical_miles(5),
cas_duration=timedelta(minutes=30),
sweep_distance=nm_to_meter(10),
sweep_distance=nautical_miles(10),
)

View File

@ -6,11 +6,12 @@ import random
from typing import Iterator, List, Optional, TYPE_CHECKING, Type
from dcs.task import CAP, CAS
from dcs.unittype import FlyingType, UnitType, VehicleType
from dcs.unittype import FlyingType, VehicleType
from game import db
from game.factions.faction import Faction
from game.theater import ControlPoint, MissionTarget
from game.utils import Distance
from gen.flights.ai_flight_planner_db import (
capable_aircraft_for_task,
preferred_aircraft_for_task,
@ -25,7 +26,7 @@ if TYPE_CHECKING:
@dataclass(frozen=True)
class AircraftProcurementRequest:
near: MissionTarget
range: int
range: Distance
task_capability: FlightType
number: int

View File

@ -55,7 +55,7 @@ from .controlpoint import (
Fob,
)
from .landmap import Landmap, load_landmap, poly_contains
from ..utils import nm_to_meter
from ..utils import Distance, meters, nautical_miles
Numeric = Union[int, float]
@ -115,7 +115,7 @@ class MizCampaignLoader:
AirDefence.SAM_SA_3_S_125_LN_5P73.id,
}
BASE_DEFENSE_RADIUS = nm_to_meter(2)
BASE_DEFENSE_RADIUS = nautical_miles(2)
def __init__(self, miz: Path, theater: ConflictTheater) -> None:
self.theater = theater
@ -317,9 +317,9 @@ class MizCampaignLoader:
self.control_points[origin.id])
return front_lines
def objective_info(self, group: Group) -> Tuple[ControlPoint, int]:
def objective_info(self, group: Group) -> Tuple[ControlPoint, Distance]:
closest = self.theater.closest_control_point(group.position)
distance = closest.position.distance_to_point(group.position)
distance = meters(closest.position.distance_to_point(group.position))
return closest, distance
def add_preset_locations(self) -> None:

View File

@ -29,38 +29,6 @@ def opposite_heading(h):
return heading_sum(h, 180)
def meter_to_feet(value: float) -> int:
return int(meters(value).feet)
def feet_to_meter(value: float) -> int:
return int(feet(value).meters)
def meter_to_nm(value: float) -> int:
return int(meters(value).nautical_miles)
def nm_to_meter(value: float) -> int:
return int(nautical_miles(value).meters)
def knots_to_kph(value: float) -> int:
return int(knots(value).kph)
def kph_to_mps(value: float) -> int:
return int(kph(value).meters_per_second)
def mps_to_kph(value: float) -> int:
return int(mps(value).kph)
def mps_to_knots(value: float) -> int:
return int(mps(value).knots)
@dataclass(frozen=True, order=True)
class Distance:
distance_in_meters: float
@ -89,14 +57,20 @@ class Distance:
def from_nautical_miles(cls, value: float) -> Distance:
return cls(value * NM_TO_METERS)
def __add__(self, other: Distance) -> Distance:
return meters(self.meters + other.meters)
def __sub__(self, other: Distance) -> Distance:
return meters(self.meters - other.meters)
def __mul__(self, other: Union[float, int]) -> Distance:
return Distance(self.meters * other)
return meters(self.meters * other)
def __truediv__(self, other: Union[float, int]) -> Distance:
return Distance(self.meters / other)
return meters(self.meters / other)
def __floordiv__(self, other: Union[float, int]) -> Distance:
return Distance(self.meters // other)
return meters(self.meters // other)
def feet(value: float) -> Distance:
@ -165,14 +139,20 @@ class Speed:
c_sound = math.sqrt(heat_capacity_ratio * gas_constant * temperature_k)
return mps(c_sound) * value
def __add__(self, other: Speed) -> Speed:
return kph(self.kph + other.kph)
def __sub__(self, other: Speed) -> Speed:
return kph(self.kph - other.kph)
def __mul__(self, other: Union[float, int]) -> Speed:
return Speed(self.kph * other)
return kph(self.kph * other)
def __truediv__(self, other: Union[float, int]) -> Speed:
return Speed(self.kph / other)
return kph(self.kph / other)
def __floordiv__(self, other: Union[float, int]) -> Speed:
return Speed(self.kph // other)
return kph(self.kph // other)
def knots(value: float) -> Speed:

View File

@ -10,6 +10,7 @@ from typing import Optional, TYPE_CHECKING
from dcs.weather import Weather as PydcsWeather, Wind
from game.settings import Settings
from game.utils import Distance, meters
if TYPE_CHECKING:
from game.theater import ConflictTheater
@ -39,7 +40,7 @@ class Clouds:
@dataclass(frozen=True)
class Fog:
visibility: int
visibility: Distance
thickness: int
@ -56,7 +57,7 @@ class Weather:
if random.randrange(5) != 0:
return None
return Fog(
visibility=random.randint(2500, 5000),
visibility=meters(random.randint(2500, 5000)),
thickness=random.randint(100, 500)
)

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 Distance, Speed, knots_to_kph, kph, meters, nm_to_meter
from game.utils import Distance, meters, nautical_miles
from gen.airsupportgen import AirSupport
from gen.ato import AirTaskingOrder, Package
from gen.callsigns import create_group_callsign_from_unit
@ -110,10 +110,8 @@ from .naming import namegen
if TYPE_CHECKING:
from game import Game
WARM_START_HELI_AIRSPEED = kph(120)
WARM_START_HELI_ALT = meters(500)
WARM_START_ALTITUDE = meters(3000)
WARM_START_AIRSPEED = kph(550)
RTB_ALTITUDE = meters(800)
RTB_DISTANCE = 5000
@ -832,11 +830,13 @@ class AircraftConflictGenerator:
else:
alt = WARM_START_ALTITUDE
speed = knots_to_kph(GroundSpeed.for_flight(flight, alt))
speed = GroundSpeed.for_flight(flight, alt)
pos = Point(at.x + random.randint(100, 1000), at.y + random.randint(100, 1000))
logging.info("airgen: {} for {} at {} at {}".format(flight.unit_type, side.id, alt, speed))
logging.info(
"airgen: {} for {} at {} at {}".format(flight.unit_type, side.id,
alt, int(speed.kph)))
group = self.m.flight_group(
country=side,
name=name,
@ -844,7 +844,7 @@ class AircraftConflictGenerator:
airport=None,
position=pos,
altitude=alt,
speed=speed,
speed=speed.kph,
maintask=None,
group_size=flight.count)
@ -1515,7 +1515,7 @@ class CasIngressBuilder(PydcsWaypointBuilder):
logging.error(
"No CAS waypoint found. Falling back to search and engage")
waypoint.add_task(EngageTargets(
max_distance=nm_to_meter(10),
max_distance=int(nautical_miles(10).meters),
targets=[
Targets.All.GroundUnits.GroundVehicles,
Targets.All.GroundUnits.AirDefence.AAA,
@ -1564,7 +1564,7 @@ class OcaAircraftIngressBuilder(PydcsWaypointBuilder):
position=target.position,
# Al Dhafra is 4 nm across at most. Add a little wiggle room in case
# the airport position from DCS is not centered.
radius=nm_to_meter(3),
radius=int(nautical_miles(3).meters),
targets=[Targets.All.Air]
)
task.params["attackQtyLimit"] = False
@ -1604,7 +1604,7 @@ class SeadIngressBuilder(PydcsWaypointBuilder):
if tgroup is not None:
waypoint.add_task(EngageTargetsInZone(
position=tgroup.position,
radius=nm_to_meter(30),
radius=int(nautical_miles(30).meters),
targets=[
Targets.All.GroundUnits.AirDefence,
])
@ -1686,7 +1686,7 @@ class SweepIngressBuilder(PydcsWaypointBuilder):
return waypoint
waypoint.tasks.append(EngageTargets(
max_distance=nm_to_meter(50),
max_distance=int(nautical_miles(50).meters),
targets=[Targets.All.Air.Planes.Fighters]))
return waypoint
@ -1729,7 +1729,7 @@ class JoinPointBuilder(PydcsWaypointBuilder):
# https://forums.eagle.ru/forum/english/digital-combat-simulator/dcs-world-2-5/bugs-and-problems-ai/ai-ad/250183-task-follow-and-escort-temporarily-aborted
waypoint.add_task(ControlledTask(EngageTargets(
# TODO: From doctrine.
max_distance=nm_to_meter(30),
max_distance=int(nautical_miles(30).meters),
targets=[Targets.All.Air.Planes.Fighters]
)))
@ -1769,8 +1769,9 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
# later.
cap_types = {FlightType.BARCAP, FlightType.TARCAP}
if self.flight.flight_type in cap_types:
waypoint.tasks.append(EngageTargets(max_distance=nm_to_meter(50),
targets=[Targets.All.Air]))
waypoint.tasks.append(
EngageTargets(max_distance=int(nautical_miles(50).meters),
targets=[Targets.All.Air]))
racetrack = ControlledTask(OrbitAction(
altitude=waypoint.alt,

View File

@ -17,6 +17,7 @@ from typing import Dict, List, Optional
from dcs.mapping import Point
from game.theater.missiontarget import MissionTarget
from game.utils import Speed
from .flights.flight import Flight, FlightType
from .flights.flightplan import FormationFlightPlan
@ -59,7 +60,7 @@ class Package:
waypoints: Optional[PackageWaypoints] = field(default=None)
@property
def formation_speed(self) -> Optional[int]:
def formation_speed(self) -> Optional[Speed]:
"""The speed of the package when in formation.
If none of the flights in the package will join a formation, this

View File

@ -21,7 +21,7 @@ class EnvironmentGenerator:
def set_fog(self, fog: Optional[Fog]) -> None:
if fog is None:
return
self.mission.weather.fog_visibility = fog.visibility
self.mission.weather.fog_visibility = fog.visibility.meters
self.mission.weather.fog_thickness = fog.thickness
def set_wind(self, wind: WindConditions) -> None:

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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:

View File

@ -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.

View File

@ -34,7 +34,7 @@ from game.theater.theatergroundobject import (
LhaGroundObject, ShipGroundObject,
)
from game.unitmap import UnitMap
from game.utils import knots_to_kph, kph_to_mps, mps_to_kph
from game.utils import knots, mps
from .radios import RadioFrequency, RadioRegistry
from .runways import RunwayData
from .tacan import TacanBand, TacanChannel, TacanRegistry
@ -247,13 +247,13 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
wind = self.game.conditions.weather.wind.at_0m
brc = wind.direction + 180
# Aim for 25kts over the deck.
carrier_speed = knots_to_kph(25) - mps_to_kph(wind.speed)
carrier_speed = knots(25) - mps(wind.speed)
for attempt in range(5):
point = group.points[0].position.point_from_heading(
brc, 100000 - attempt * 20000)
if self.game.theater.is_in_sea(point):
group.points[0].speed = kph_to_mps(carrier_speed)
group.add_waypoint(point, carrier_speed)
group.points[0].speed = carrier_speed.meters_per_second
group.add_waypoint(point, carrier_speed.kph)
return brc
return None

View File

@ -33,8 +33,7 @@ from dcs.mission import Mission
from dcs.unittype import FlyingType
from tabulate import tabulate
from game.utils import meter_to_nm
from . import units
from game.utils import meters
from .aircraft import AIRCRAFT_DATA, FlightData
from .airsupportgen import AwacsInfo, TankerInfo
from .briefinggen import CommInfo, JtacInfo, MissionInfoGenerator
@ -170,10 +169,10 @@ class FlightPlanBuilder:
if self.last_waypoint is None:
return "-"
distance = meter_to_nm(self.last_waypoint.position.distance_to_point(
distance = meters(self.last_waypoint.position.distance_to_point(
waypoint.position
))
return f"{distance} NM"
return f"{distance.nautical_miles:.1f} NM"
def _ground_speed(self, waypoint: FlightWaypoint) -> str:
if self.last_waypoint is None:
@ -189,19 +188,11 @@ class FlightPlanBuilder:
else:
return "-"
distance = meter_to_nm(self.last_waypoint.position.distance_to_point(
distance = meters(self.last_waypoint.position.distance_to_point(
waypoint.position
))
duration = (waypoint.tot - last_time).total_seconds() / 3600
try:
return f"{int(distance / duration)} kt"
except ZeroDivisionError:
# TODO: Improve resolution of unit conversions.
# When waypoints are very close to each other they can end up with
# identical TOTs because our unit conversion functions truncate to
# int. When waypoints have the same TOT the duration will be zero.
# https://github.com/Khopa/dcs_liberation/issues/557
return "-"
return f"{int(distance.nautical_miles / duration)} kt"
def build(self) -> List[List[str]]:
return self.rows

View File

@ -1,12 +1,18 @@
from PySide2.QtCore import Qt
from PySide2.QtWidgets import QLabel, QHBoxLayout, QGroupBox, QVBoxLayout, QFrame, QGridLayout
from PySide2.QtGui import QPixmap
from game.weather import Conditions, TimeOfDay, Weather
from game.utils import meter_to_nm, mps_to_knots
from PySide2.QtWidgets import (
QFrame,
QGridLayout,
QGroupBox,
QHBoxLayout,
QLabel,
QVBoxLayout,
)
from dcs.weather import Weather as PydcsWeather
import qt_ui.uiconstants as CONST
from game.utils import mps
from game.weather import Conditions, TimeOfDay
class QTimeTurnWidget(QGroupBox):
"""
@ -163,20 +169,20 @@ class QWeatherWidget(QGroupBox):
def updateWinds(self):
"""Updates the UI with the current conditions wind info.
"""
windGlSpeed = mps_to_knots(self.conditions.weather.wind.at_0m.speed or 0)
windGlSpeed = mps(self.conditions.weather.wind.at_0m.speed or 0)
windGlDir = str(self.conditions.weather.wind.at_0m.direction or 0).rjust(3, '0')
self.windGLSpeedLabel.setText('{}kts'.format(windGlSpeed))
self.windGLDirLabel.setText('{}º'.format(windGlDir))
self.windGLSpeedLabel.setText(f'{int(windGlSpeed.knots)}kts')
self.windGLDirLabel.setText(f'{windGlDir}º')
windFL08Speed = mps_to_knots(self.conditions.weather.wind.at_2000m.speed or 0)
windFL08Speed = mps(self.conditions.weather.wind.at_2000m.speed or 0)
windFL08Dir = str(self.conditions.weather.wind.at_2000m.direction or 0).rjust(3, '0')
self.windFL08SpeedLabel.setText('{}kts'.format(windFL08Speed))
self.windFL08DirLabel.setText('{}º'.format(windFL08Dir))
self.windFL08SpeedLabel.setText(f'{int(windFL08Speed.knots)}kts')
self.windFL08DirLabel.setText(f'{windFL08Dir}º')
windFL26Speed = mps_to_knots(self.conditions.weather.wind.at_8000m.speed or 0)
windFL26Speed = mps(self.conditions.weather.wind.at_8000m.speed or 0)
windFL26Dir = str(self.conditions.weather.wind.at_8000m.direction or 0).rjust(3, '0')
self.windFL26SpeedLabel.setText('{}kts'.format(windFL26Speed))
self.windFL26DirLabel.setText('{}º'.format(windFL26Dir))
self.windFL26SpeedLabel.setText(f'{int(windFL26Speed.knots)}kts')
self.windFL26DirLabel.setText(f'{windFL26Dir}º')
def updateForecast(self):
"""Updates the Forecast Text and icon with the current conditions wind info.
@ -223,11 +229,10 @@ class QWeatherWidget(QGroupBox):
if not fog:
self.forecastFog.setText('No fog')
else:
visvibilityNm = round(meter_to_nm(fog.visibility), 1)
self.forecastFog.setText('Fog vis: {}nm'.format(visvibilityNm))
visibility = round(fog.visibility.nautical_miles, 1)
self.forecastFog.setText(f'Fog vis: {visibility}nm')
icon = [time, ('cloudy' if cloudDensity > 1 else None), 'fog']
icon_key = "Weather_{}".format('-'.join(filter(None.__ne__, icon)))
icon = CONST.ICONS.get(icon_key) or CONST.ICONS['Weather_night-partly-cloudy']
self.weather_icon.setPixmap(icon)

View File

@ -5,8 +5,8 @@ import logging
import math
from typing import Iterable, Iterator, List, Optional, Tuple
from PySide2 import QtWidgets, QtCore
from PySide2.QtCore import QPointF, Qt, QLineF, QRectF
from PySide2 import QtCore, QtWidgets
from PySide2.QtCore import QLineF, QPointF, QRectF, Qt
from PySide2.QtGui import (
QBrush,
QColor,
@ -14,13 +14,15 @@ from PySide2.QtGui import (
QPen,
QPixmap,
QPolygonF,
QWheelEvent, )
QWheelEvent,
)
from PySide2.QtWidgets import (
QFrame,
QGraphicsItem,
QGraphicsOpacityEffect,
QGraphicsScene,
QGraphicsView, QGraphicsSceneMouseEvent,
QGraphicsSceneMouseEvent,
QGraphicsView,
)
from dcs import Point
from dcs.mapping import point_from_heading
@ -32,7 +34,7 @@ from game.theater.conflicttheater import FrontLine, ReferencePoint
from game.theater.theatergroundobject import (
TheaterGroundObject,
)
from game.utils import meter_to_feet, nm_to_meter, meter_to_nm
from game.utils import Distance, meters, nautical_miles
from game.weather import TimeOfDay
from gen import Conflict
from gen.flights.flight import Flight, FlightWaypoint, FlightWaypointType
@ -45,7 +47,7 @@ from qt_ui.widgets.map.QMapControlPoint import QMapControlPoint
from qt_ui.widgets.map.QMapGroundObject import QMapGroundObject
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
MAX_SHIP_DISTANCE = 80
MAX_SHIP_DISTANCE = nautical_miles(80)
def binomial(i: int, n: int) -> float:
"""Binomial coefficient"""
@ -171,7 +173,7 @@ class QLiberationMap(QGraphicsView):
self.game = game
if self.game is not None:
logging.debug("Reloading Map Canvas")
self.nm_to_pixel_ratio = self.km_to_pixel(float(nm_to_meter(1)) / 1000.0)
self.nm_to_pixel_ratio = self.distance_to_pixels(nautical_miles(1))
self.reload_scene()
"""
@ -567,7 +569,7 @@ class QLiberationMap(QGraphicsView):
BIG_LINE = 5
SMALL_LINE = 2
dist = self.km_to_pixel(nm_to_meter(scale_distance_nm)/1000.0)
dist = self.distance_to_pixels(nautical_miles(scale_distance_nm))
self.scene().addRect(POS_X, POS_Y-PADDING, PADDING*2 + dist, BIG_LINE*2+3*PADDING, pen=CONST.COLORS["black"], brush=CONST.COLORS["black"])
l = self.scene().addLine(POS_X + PADDING, POS_Y + BIG_LINE*2, POS_X + PADDING + dist, POS_Y + BIG_LINE*2)
@ -663,12 +665,12 @@ class QLiberationMap(QGraphicsView):
Point(offset.x / scale.x, offset.y / scale.y))
return point_a.world_coordinates - scaled
def km_to_pixel(self, km):
def distance_to_pixels(self, distance: Distance) -> int:
p1 = Point(0, 0)
p2 = Point(0, 1000*km)
p2 = Point(0, distance.meters)
p1a = Point(*self._transform_point(p1))
p2a = Point(*self._transform_point(p2))
return p1a.distance_to_point(p2a)
return int(p1a.distance_to_point(p2a))
def highlight_color(self, transparent: Optional[bool] = False) -> QColor:
return QColor(255, 255, 0, 20 if transparent else 255)
@ -820,7 +822,7 @@ class QLiberationMap(QGraphicsView):
distance = self.selected_cp.control_point.position.distance_to_point(
world_destination
)
if meter_to_nm(distance) > MAX_SHIP_DISTANCE:
if meters(distance) > MAX_SHIP_DISTANCE:
return False
return self.game.theater.is_in_sea(world_destination)

View File

@ -4,7 +4,6 @@ from PySide2.QtCore import QItemSelectionModel, QPoint
from PySide2.QtGui import QStandardItem, QStandardItemModel
from PySide2.QtWidgets import QHeaderView, QTableView
from game.utils import meter_to_feet
from gen.ato import Package
from gen.flights.flight import Flight, FlightWaypoint, FlightWaypointType
from qt_ui.windows.mission.flight.waypoints.QFlightWaypointItem import \