mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Improve unit system support in kneeboards.
* Factor out unit systems. * Add support for more unit systems (nautical and imperial). * Fuel units support. * Data for many more aircraft.
This commit is contained in:
@@ -33,7 +33,11 @@ from game.radio.channels import (
|
||||
from game.utils import (
|
||||
Distance,
|
||||
SPEED_OF_SOUND_AT_SEA_LEVEL,
|
||||
ImperialUnits,
|
||||
MetricUnits,
|
||||
NauticalUnits,
|
||||
Speed,
|
||||
UnitSystem,
|
||||
feet,
|
||||
knots,
|
||||
kph,
|
||||
@@ -154,8 +158,8 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
||||
# main weapon. It'll RTB when it doesn't have gun ammo left.
|
||||
gunfighter: bool
|
||||
|
||||
# If true, kneeboards will be generated in metric units
|
||||
metric_kneeboard: bool
|
||||
# UnitSystem to use for the kneeboard, defaults to Nautical (kt/nm/ft)
|
||||
kneeboard_units: UnitSystem
|
||||
|
||||
# If true, kneeboards will display zulu times
|
||||
utc_kneeboard: bool
|
||||
@@ -376,6 +380,13 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
||||
except KeyError:
|
||||
introduction = "No data."
|
||||
|
||||
units_data = data.get("kneeboard_units", "nautical").lower()
|
||||
units: UnitSystem = NauticalUnits()
|
||||
if units_data == "imperial":
|
||||
units = ImperialUnits()
|
||||
if units_data == "metric":
|
||||
units = MetricUnits()
|
||||
|
||||
for variant in data.get("variants", [aircraft.id]):
|
||||
yield AircraftType(
|
||||
dcs_unit_type=aircraft,
|
||||
@@ -401,6 +412,6 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
||||
intra_flight_radio=radio_config.intra_flight,
|
||||
channel_allocator=radio_config.channel_allocator,
|
||||
channel_namer=radio_config.channel_namer,
|
||||
metric_kneeboard=data.get("metric_kneeboard", False),
|
||||
kneeboard_units=units,
|
||||
utc_kneeboard=data.get("utc_kneeboard", False),
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ from game.dcs.aircrafttype import AircraftType
|
||||
from game.radio.radios import RadioFrequency
|
||||
from game.theater import ConflictTheater, LatLon, TheaterGroundObject
|
||||
from game.theater.bullseye import Bullseye
|
||||
from game.utils import Distance, meters
|
||||
from game.utils import Distance, UnitSystem, meters, mps, pounds
|
||||
from game.weather import Weather
|
||||
from gen.runways import RunwayData
|
||||
from .aircraft.flightdata import FlightData
|
||||
@@ -202,12 +202,12 @@ class FlightPlanBuilder:
|
||||
|
||||
WAYPOINT_DESC_MAX_LEN = 25
|
||||
|
||||
def __init__(self, start_time: datetime.datetime, is_metric: bool) -> None:
|
||||
def __init__(self, start_time: datetime.datetime, units: UnitSystem) -> None:
|
||||
self.start_time = start_time
|
||||
self.rows: List[List[str]] = []
|
||||
self.target_points: List[NumberedWaypoint] = []
|
||||
self.last_waypoint: Optional[FlightWaypoint] = None
|
||||
self.is_metric = is_metric
|
||||
self.units = units
|
||||
|
||||
def add_waypoint(self, waypoint_num: int, waypoint: FlightWaypoint) -> None:
|
||||
if waypoint.waypoint_type == FlightWaypointType.TARGET_POINT:
|
||||
@@ -269,32 +269,24 @@ class FlightPlanBuilder:
|
||||
return f"{local_time.strftime('%H:%M:%S')}{'Z' if local_time.tzinfo is not None else ''}"
|
||||
|
||||
def _format_alt(self, alt: Distance) -> str:
|
||||
if self.is_metric:
|
||||
return f"{alt.meters:.0f} m"
|
||||
else:
|
||||
return f"{alt.feet:.0f} ft"
|
||||
return f"{self.units.distance_short(alt):.0f}"
|
||||
|
||||
def _waypoint_distance(self, waypoint: FlightWaypoint) -> str:
|
||||
if self.last_waypoint is None:
|
||||
return "-"
|
||||
|
||||
if self.is_metric:
|
||||
distance = meters(
|
||||
self.last_waypoint.position.distance_to_point(waypoint.position)
|
||||
)
|
||||
return f"{(distance.meters / 1000):.1f} km"
|
||||
else:
|
||||
distance = meters(
|
||||
self.last_waypoint.position.distance_to_point(waypoint.position)
|
||||
)
|
||||
return f"{distance.nautical_miles:.1f} nm"
|
||||
distance = meters(
|
||||
self.last_waypoint.position.distance_to_point(waypoint.position)
|
||||
)
|
||||
|
||||
return f"{self.units.distance_long(distance):.1f}"
|
||||
|
||||
def _waypoint_bearing(self, waypoint: FlightWaypoint) -> str:
|
||||
if self.last_waypoint is None:
|
||||
return "-"
|
||||
bearing = self.last_waypoint.position.heading_between_point(waypoint.position)
|
||||
|
||||
return f"{(bearing):.0f} T"
|
||||
return f"{(bearing):.0f}"
|
||||
|
||||
def _ground_speed(self, waypoint: FlightWaypoint) -> str:
|
||||
if self.last_waypoint is None:
|
||||
@@ -310,20 +302,19 @@ class FlightPlanBuilder:
|
||||
else:
|
||||
return "-"
|
||||
|
||||
distance = meters(
|
||||
speed = mps(
|
||||
self.last_waypoint.position.distance_to_point(waypoint.position)
|
||||
/ (waypoint.tot - last_time).total_seconds()
|
||||
)
|
||||
duration = (waypoint.tot - last_time).total_seconds() / 3600
|
||||
if self.is_metric:
|
||||
return f"{int((distance.meters / 1000) / duration)} km/h"
|
||||
else:
|
||||
return f"{int(distance.nautical_miles / duration)} kt"
|
||||
|
||||
@staticmethod
|
||||
def _format_min_fuel(min_fuel: Optional[float]) -> str:
|
||||
return f"{self.units.speed(speed):.0f}"
|
||||
|
||||
def _format_min_fuel(self, min_fuel: Optional[float]) -> str:
|
||||
if min_fuel is None:
|
||||
return ""
|
||||
return str(math.ceil(min_fuel / 100) * 100)
|
||||
|
||||
mass = pounds(min_fuel)
|
||||
return f"{math.ceil(self.units.mass(mass) / 100) * 100:.0f}"
|
||||
|
||||
def build(self) -> List[List[str]]:
|
||||
return self.rows
|
||||
@@ -373,13 +364,29 @@ class BriefingPage(KneeboardPage):
|
||||
)
|
||||
|
||||
writer.heading("Flight Plan")
|
||||
flight_plan_builder = FlightPlanBuilder(
|
||||
self.start_time, self.flight.aircraft_type.metric_kneeboard
|
||||
)
|
||||
|
||||
units = self.flight.aircraft_type.kneeboard_units
|
||||
|
||||
flight_plan_builder = FlightPlanBuilder(self.start_time, units)
|
||||
for num, waypoint in enumerate(self.flight.waypoints):
|
||||
flight_plan_builder.add_waypoint(num, waypoint)
|
||||
|
||||
uom_row = [
|
||||
[
|
||||
"",
|
||||
"",
|
||||
units.distance_short_uom,
|
||||
units.distance_long_uom,
|
||||
"T",
|
||||
units.speed_uom,
|
||||
"",
|
||||
"",
|
||||
units.mass_uom,
|
||||
]
|
||||
]
|
||||
|
||||
writer.table(
|
||||
flight_plan_builder.build(),
|
||||
flight_plan_builder.build() + uom_row,
|
||||
headers=[
|
||||
"#",
|
||||
"Action",
|
||||
@@ -404,15 +411,18 @@ class BriefingPage(KneeboardPage):
|
||||
)
|
||||
writer.text(f"QNH: {qnh_in_hg} inHg / {qnh_mm_hg} mmHg / {qnh_hpa} hPa")
|
||||
|
||||
writer.table(
|
||||
[
|
||||
fl = self.flight
|
||||
|
||||
if fl.bingo_fuel and fl.joker_fuel:
|
||||
writer.table(
|
||||
[
|
||||
"{}lbs".format(self.flight.bingo_fuel),
|
||||
"{}lbs".format(self.flight.joker_fuel),
|
||||
]
|
||||
],
|
||||
["Bingo", "Joker"],
|
||||
)
|
||||
[
|
||||
f"{units.mass(pounds(fl.bingo_fuel)):.0f} {units.mass_uom}",
|
||||
f"{units.mass(pounds(fl.joker_fuel)):.0f} {units.mass_uom}",
|
||||
]
|
||||
],
|
||||
["Bingo", "Joker"],
|
||||
)
|
||||
|
||||
if any(self.flight.laser_codes):
|
||||
codes: list[list[str]] = []
|
||||
|
||||
167
game/utils.py
167
game/utils.py
@@ -1,4 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import itertools
|
||||
import math
|
||||
@@ -14,16 +15,149 @@ METERS_TO_FEET = 3.28084
|
||||
FEET_TO_METERS = 1 / METERS_TO_FEET
|
||||
NM_TO_METERS = 1852
|
||||
METERS_TO_NM = 1 / NM_TO_METERS
|
||||
MILES_TO_METERS = 1609.34
|
||||
METERS_TO_MILES = 1 / MILES_TO_METERS
|
||||
|
||||
KNOTS_TO_KPH = 1.852
|
||||
KPH_TO_KNOTS = 1 / KNOTS_TO_KPH
|
||||
MS_TO_KPH = 3.6
|
||||
KPH_TO_MS = 1 / MS_TO_KPH
|
||||
KPH_TO_MPH = 0.621371
|
||||
MPH_TO_KPH = 1 / KPH_TO_MPH
|
||||
|
||||
INHG_TO_HPA = 33.86389
|
||||
INHG_TO_MMHG = 25.400002776728
|
||||
|
||||
LBS_TO_KG = 0.453592
|
||||
KG_TO_LBS = 1 / LBS_TO_KG
|
||||
|
||||
|
||||
class UnitSystem(ABC):
|
||||
@abstractmethod
|
||||
def distance_short(self, dist: Distance) -> float:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def distance_long(self, dist: Distance) -> float:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def distance_short_uom(self) -> str:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def distance_long_uom(self) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def speed(self, speed: Speed) -> float:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def speed_uom(self) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def mass(self, mass: Mass) -> float:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def mass_uom(self) -> str:
|
||||
pass
|
||||
|
||||
|
||||
class NauticalUnits(UnitSystem):
|
||||
def distance_short(self, dist: Distance) -> float:
|
||||
return dist.feet
|
||||
|
||||
def distance_long(self, dist: Distance) -> float:
|
||||
return dist.nautical_miles
|
||||
|
||||
@property
|
||||
def distance_short_uom(self) -> str:
|
||||
return "ft"
|
||||
|
||||
@property
|
||||
def distance_long_uom(self) -> str:
|
||||
return "nm"
|
||||
|
||||
def speed(self, speed: Speed) -> float:
|
||||
return speed.knots
|
||||
|
||||
@property
|
||||
def speed_uom(self) -> str:
|
||||
return "kt"
|
||||
|
||||
def mass(self, mass: Mass) -> float:
|
||||
return mass.pounds
|
||||
|
||||
@property
|
||||
def mass_uom(self) -> str:
|
||||
return "lb"
|
||||
|
||||
|
||||
class MetricUnits(UnitSystem):
|
||||
def distance_short(self, dist: Distance) -> float:
|
||||
return dist.meters
|
||||
|
||||
def distance_long(self, dist: Distance) -> float:
|
||||
return dist.kilometers
|
||||
|
||||
@property
|
||||
def distance_short_uom(self) -> str:
|
||||
return "m"
|
||||
|
||||
@property
|
||||
def distance_long_uom(self) -> str:
|
||||
return "km"
|
||||
|
||||
def speed(self, speed: Speed) -> float:
|
||||
return speed.kph
|
||||
|
||||
@property
|
||||
def speed_uom(self) -> str:
|
||||
return "kph"
|
||||
|
||||
def mass(self, mass: Mass) -> float:
|
||||
return mass.kgs
|
||||
|
||||
@property
|
||||
def mass_uom(self) -> str:
|
||||
return "kg"
|
||||
|
||||
|
||||
class ImperialUnits(UnitSystem):
|
||||
def distance_short(self, dist: Distance) -> float:
|
||||
return dist.feet
|
||||
|
||||
def distance_long(self, dist: Distance) -> float:
|
||||
return dist.miles
|
||||
|
||||
@property
|
||||
def distance_short_uom(self) -> str:
|
||||
return "ft"
|
||||
|
||||
@property
|
||||
def distance_long_uom(self) -> str:
|
||||
return "m"
|
||||
|
||||
def speed(self, speed: Speed) -> float:
|
||||
return speed.mph
|
||||
|
||||
@property
|
||||
def speed_uom(self) -> str:
|
||||
return "mph"
|
||||
|
||||
def mass(self, mass: Mass) -> float:
|
||||
return mass.pounds
|
||||
|
||||
@property
|
||||
def mass_uom(self) -> str:
|
||||
return "lb"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -42,6 +176,14 @@ class Distance:
|
||||
def nautical_miles(self) -> float:
|
||||
return self.distance_in_meters * METERS_TO_NM
|
||||
|
||||
@property
|
||||
def kilometers(self) -> float:
|
||||
return self.distance_in_meters / 1000
|
||||
|
||||
@property
|
||||
def miles(self) -> float:
|
||||
return self.distance_in_meters * METERS_TO_MILES
|
||||
|
||||
@classmethod
|
||||
def from_feet(cls, value: float) -> Distance:
|
||||
return cls(value * FEET_TO_METERS)
|
||||
@@ -119,6 +261,10 @@ class Speed:
|
||||
def meters_per_second(self) -> float:
|
||||
return self.speed_in_kph * KPH_TO_MS
|
||||
|
||||
@property
|
||||
def mph(self) -> float:
|
||||
return self.speed_in_kph * KPH_TO_MPH
|
||||
|
||||
def mach(self, altitude: Distance = meters(0)) -> float:
|
||||
c_sound = mach(1, altitude)
|
||||
return self.speed_in_kph / c_sound.kph
|
||||
@@ -272,6 +418,27 @@ def inches_hg(value: float) -> Pressure:
|
||||
return Pressure(value)
|
||||
|
||||
|
||||
@dataclass(frozen=True, order=True)
|
||||
class Mass:
|
||||
mass_in_kg: float
|
||||
|
||||
@property
|
||||
def pounds(self) -> float:
|
||||
return self.mass_in_kg * KG_TO_LBS
|
||||
|
||||
@property
|
||||
def kgs(self) -> float:
|
||||
return self.mass_in_kg
|
||||
|
||||
|
||||
def pounds(value: float) -> Mass:
|
||||
return Mass(value * LBS_TO_KG)
|
||||
|
||||
|
||||
def kgs(value: float) -> Mass:
|
||||
return Mass(value)
|
||||
|
||||
|
||||
PairwiseT = TypeVar("PairwiseT")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user