mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Reduce fuel for fast-forwarded player flights.
This only has an effect for aircraft for which we have fuel consumption data, but that's fine. Fixes https://github.com/dcs-liberation/dcs_liberation/issues/1681
This commit is contained in:
parent
b2cbf4b6f4
commit
1a3b8d1dd6
@ -1,12 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Optional, List, TYPE_CHECKING
|
from typing import Any, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
from dcs.planes import C_101CC, C_101EB, Su_33
|
||||||
|
|
||||||
from gen.flights.loadouts import Loadout
|
from gen.flights.loadouts import Loadout
|
||||||
|
|
||||||
from .flightroster import FlightRoster
|
from .flightroster import FlightRoster
|
||||||
from .flightstate import Uninitialized, FlightState
|
from .flightstate import FlightState, Uninitialized
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.dcs.aircrafttype import AircraftType
|
from game.dcs.aircrafttype import AircraftType
|
||||||
@ -122,6 +123,18 @@ class Flight:
|
|||||||
self.roster.clear()
|
self.roster.clear()
|
||||||
self.squadron.claim_inventory(-self.count)
|
self.squadron.claim_inventory(-self.count)
|
||||||
|
|
||||||
|
def max_takeoff_fuel(self) -> Optional[float]:
|
||||||
|
# Special case so Su 33 and C101 can take off
|
||||||
|
unit_type = self.unit_type.dcs_unit_type
|
||||||
|
if unit_type == Su_33:
|
||||||
|
if self.flight_type.is_air_to_air:
|
||||||
|
return Su_33.fuel_max / 2.2
|
||||||
|
else:
|
||||||
|
return Su_33.fuel_max * 0.8
|
||||||
|
elif unit_type in {C_101EB, C_101CC}:
|
||||||
|
return unit_type.fuel_max * 0.5
|
||||||
|
return None
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
if self.custom_name:
|
if self.custom_name:
|
||||||
return f"{self.custom_name} {self.count} x {self.unit_type}"
|
return f"{self.custom_name} {self.count} x {self.unit_type}"
|
||||||
|
|||||||
@ -37,3 +37,9 @@ class FlightState(ABC):
|
|||||||
|
|
||||||
def a2a_commit_region(self) -> Optional[ThreatPoly]:
|
def a2a_commit_region(self) -> Optional[ThreatPoly]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def estimate_fuel(self) -> float:
|
||||||
|
"""Returns the estimated remaining fuel **in kilograms**."""
|
||||||
|
if (max_takeoff_fuel := self.flight.max_takeoff_fuel()) is not None:
|
||||||
|
return max_takeoff_fuel
|
||||||
|
return self.flight.unit_type.dcs_unit_type.fuel_max
|
||||||
|
|||||||
@ -11,7 +11,7 @@ from game.ato.flightstate.flightstate import FlightState
|
|||||||
from game.ato.flightwaypoint import FlightWaypoint
|
from game.ato.flightwaypoint import FlightWaypoint
|
||||||
from game.ato.flightwaypointtype import FlightWaypointType
|
from game.ato.flightwaypointtype import FlightWaypointType
|
||||||
from game.ato.starttype import StartType
|
from game.ato.starttype import StartType
|
||||||
from game.utils import Distance, Speed, meters
|
from game.utils import Distance, LBS_TO_KG, Speed, meters, pairwise
|
||||||
from gen.flights.flightplan import LoiterFlightPlan
|
from gen.flights.flightplan import LoiterFlightPlan
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -83,6 +83,30 @@ class InFlight(FlightState):
|
|||||||
self.current_waypoint, self.next_waypoint
|
self.current_waypoint, self.next_waypoint
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def estimate_fuel_at_current_waypoint(self) -> float:
|
||||||
|
initial_fuel = super().estimate_fuel()
|
||||||
|
if self.flight.unit_type.fuel_consumption is None:
|
||||||
|
return initial_fuel
|
||||||
|
initial_fuel -= self.flight.unit_type.fuel_consumption.taxi * LBS_TO_KG
|
||||||
|
waypoints = self.flight.flight_plan.waypoints[: self.waypoint_index + 1]
|
||||||
|
for a, b in pairwise(waypoints[:-1]):
|
||||||
|
consumption = self.flight.flight_plan.fuel_consumption_between_points(a, b)
|
||||||
|
assert consumption is not None
|
||||||
|
initial_fuel -= consumption * LBS_TO_KG
|
||||||
|
return initial_fuel
|
||||||
|
|
||||||
|
def estimate_fuel(self) -> float:
|
||||||
|
initial_fuel = self.estimate_fuel_at_current_waypoint()
|
||||||
|
ppm = self.flight.flight_plan.fuel_rate_to_between_points(
|
||||||
|
self.current_waypoint, self.next_waypoint
|
||||||
|
)
|
||||||
|
if ppm is None:
|
||||||
|
return initial_fuel
|
||||||
|
position = self.estimate_position()
|
||||||
|
distance = meters(self.current_waypoint.position.distance_to_point(position))
|
||||||
|
consumption = distance.nautical_miles * ppm * LBS_TO_KG
|
||||||
|
return initial_fuel - consumption
|
||||||
|
|
||||||
def next_waypoint_state(self) -> FlightState:
|
def next_waypoint_state(self) -> FlightState:
|
||||||
from game.ato.flightstate.loiter import Loiter
|
from game.ato.flightstate.loiter import Loiter
|
||||||
from game.ato.flightstate.racetrack import RaceTrack
|
from game.ato.flightstate.racetrack import RaceTrack
|
||||||
|
|||||||
@ -29,6 +29,10 @@ class Loiter(InFlight):
|
|||||||
def estimate_speed(self) -> Speed:
|
def estimate_speed(self) -> Speed:
|
||||||
return self.flight.unit_type.preferred_patrol_speed(self.estimate_altitude()[0])
|
return self.flight.unit_type.preferred_patrol_speed(self.estimate_altitude()[0])
|
||||||
|
|
||||||
|
def estimate_fuel(self) -> float:
|
||||||
|
# TODO: Estimate loiter consumption per minute?
|
||||||
|
return self.estimate_fuel_at_current_waypoint()
|
||||||
|
|
||||||
def next_waypoint_state(self) -> FlightState:
|
def next_waypoint_state(self) -> FlightState:
|
||||||
# Do not automatically advance to the next waypoint. Just proceed from the
|
# Do not automatically advance to the next waypoint. Just proceed from the
|
||||||
# current one with the normal flying state.
|
# current one with the normal flying state.
|
||||||
|
|||||||
@ -40,6 +40,10 @@ class RaceTrack(InFlight):
|
|||||||
def estimate_speed(self) -> Speed:
|
def estimate_speed(self) -> Speed:
|
||||||
return self.flight.unit_type.preferred_patrol_speed(self.estimate_altitude()[0])
|
return self.flight.unit_type.preferred_patrol_speed(self.estimate_altitude()[0])
|
||||||
|
|
||||||
|
def estimate_fuel(self) -> float:
|
||||||
|
# TODO: Estimate loiter consumption per minute?
|
||||||
|
return self.estimate_fuel_at_current_waypoint()
|
||||||
|
|
||||||
def travel_time_between_waypoints(self) -> timedelta:
|
def travel_time_between_waypoints(self) -> timedelta:
|
||||||
return self.patrol_duration
|
return self.patrol_duration
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from typing import TYPE_CHECKING
|
|||||||
from .flightstate import FlightState
|
from .flightstate import FlightState
|
||||||
from .inflight import InFlight
|
from .inflight import InFlight
|
||||||
from ..starttype import StartType
|
from ..starttype import StartType
|
||||||
|
from ...utils import LBS_TO_KG
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
@ -33,6 +34,12 @@ class Takeoff(FlightState):
|
|||||||
def spawn_type(self) -> StartType:
|
def spawn_type(self) -> StartType:
|
||||||
return StartType.RUNWAY
|
return StartType.RUNWAY
|
||||||
|
|
||||||
|
def estimate_fuel(self) -> float:
|
||||||
|
initial_fuel = super().estimate_fuel()
|
||||||
|
if self.flight.unit_type.fuel_consumption is None:
|
||||||
|
return initial_fuel
|
||||||
|
return initial_fuel - self.flight.unit_type.fuel_consumption.taxi * LBS_TO_KG
|
||||||
|
|
||||||
def should_halt_sim(self, enemy_aircraft_coverage: AircraftEngagementZones) -> bool:
|
def should_halt_sim(self, enemy_aircraft_coverage: AircraftEngagementZones) -> bool:
|
||||||
if (
|
if (
|
||||||
self.flight.client_count > 0
|
self.flight.client_count > 0
|
||||||
|
|||||||
@ -6,8 +6,7 @@ from typing import Any, Optional, TYPE_CHECKING
|
|||||||
|
|
||||||
from dcs import Mission
|
from dcs import Mission
|
||||||
from dcs.flyingunit import FlyingUnit
|
from dcs.flyingunit import FlyingUnit
|
||||||
from dcs.planes import C_101CC, C_101EB, F_14B, Su_33
|
from dcs.planes import F_14B
|
||||||
from dcs.task import CAP
|
|
||||||
from dcs.unit import Skill
|
from dcs.unit import Skill
|
||||||
from dcs.unitgroup import FlyingGroup
|
from dcs.unitgroup import FlyingGroup
|
||||||
|
|
||||||
@ -208,14 +207,21 @@ class FlightGroupConfigurator:
|
|||||||
pylon.equip(self.group, weapon)
|
pylon.equip(self.group, weapon)
|
||||||
|
|
||||||
def setup_fuel(self) -> None:
|
def setup_fuel(self) -> None:
|
||||||
# Special case so Su 33 and C101 can take off
|
fuel = self.flight.state.estimate_fuel()
|
||||||
unit_type = self.flight.unit_type.dcs_unit_type
|
if fuel < 0:
|
||||||
if unit_type == Su_33:
|
logging.warning(
|
||||||
for unit in self.group.units:
|
f"Flight {self.flight} is estimated to have no fuel at mission start. "
|
||||||
if self.group.task == CAP:
|
"This estimate does not account for external fuel tanks. Setting "
|
||||||
unit.fuel = Su_33.fuel_max / 2.2
|
"starting fuel to 100kg."
|
||||||
else:
|
)
|
||||||
unit.fuel = Su_33.fuel_max * 0.8
|
fuel = 100
|
||||||
elif unit_type in {C_101EB, C_101CC}:
|
for unit, pilot in zip(self.group.units, self.flight.roster.pilots):
|
||||||
for unit in self.group.units:
|
if pilot is not None and pilot.player:
|
||||||
unit.fuel = unit_type.fuel_max * 0.5
|
unit.fuel = fuel
|
||||||
|
elif (max_takeoff_fuel := self.flight.max_takeoff_fuel()) is not None:
|
||||||
|
unit.fuel = max_takeoff_fuel
|
||||||
|
else:
|
||||||
|
# pydcs arbitrarily reduces the fuel of in-flight spawns by 10%. We do
|
||||||
|
# our own tracking, so undo that.
|
||||||
|
# https://github.com/pydcs/dcs/commit/303a81a8e0c778599fe136dd22cb2ae8123639a6
|
||||||
|
unit.fuel = self.flight.unit_type.dcs_unit_type.fuel_max
|
||||||
|
|||||||
@ -18,7 +18,7 @@ from game.ato.starttype import StartType
|
|||||||
from game.missiongenerator.airsupport import AirSupport
|
from game.missiongenerator.airsupport import AirSupport
|
||||||
from game.settings import Settings
|
from game.settings import Settings
|
||||||
from game.theater import ControlPointType
|
from game.theater import ControlPointType
|
||||||
from game.utils import meters, pairwise
|
from game.utils import pairwise
|
||||||
from .baiingress import BaiIngressBuilder
|
from .baiingress import BaiIngressBuilder
|
||||||
from .cargostop import CargoStopBuilder
|
from .cargostop import CargoStopBuilder
|
||||||
from .casingress import CasIngressBuilder
|
from .casingress import CasIngressBuilder
|
||||||
@ -145,19 +145,6 @@ class WaypointGenerator:
|
|||||||
if self.flight.unit_type.fuel_consumption is None:
|
if self.flight.unit_type.fuel_consumption is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
combat_speed_types = {
|
|
||||||
FlightWaypointType.INGRESS_BAI,
|
|
||||||
FlightWaypointType.INGRESS_CAS,
|
|
||||||
FlightWaypointType.INGRESS_DEAD,
|
|
||||||
FlightWaypointType.INGRESS_ESCORT,
|
|
||||||
FlightWaypointType.INGRESS_OCA_AIRCRAFT,
|
|
||||||
FlightWaypointType.INGRESS_OCA_RUNWAY,
|
|
||||||
FlightWaypointType.INGRESS_SEAD,
|
|
||||||
FlightWaypointType.INGRESS_STRIKE,
|
|
||||||
FlightWaypointType.INGRESS_SWEEP,
|
|
||||||
FlightWaypointType.SPLIT,
|
|
||||||
} | set(TARGET_WAYPOINTS)
|
|
||||||
|
|
||||||
consumption = self.flight.unit_type.fuel_consumption
|
consumption = self.flight.unit_type.fuel_consumption
|
||||||
min_fuel: float = consumption.min_safe
|
min_fuel: float = consumption.min_safe
|
||||||
|
|
||||||
@ -174,14 +161,10 @@ class WaypointGenerator:
|
|||||||
return
|
return
|
||||||
|
|
||||||
for b, a in pairwise(main_flight_plan):
|
for b, a in pairwise(main_flight_plan):
|
||||||
distance = meters(a.position.distance_to_point(b.position))
|
for_leg = self.flight.flight_plan.fuel_consumption_between_points(a, b)
|
||||||
if a.waypoint_type is FlightWaypointType.TAKEOFF:
|
if for_leg is None:
|
||||||
ppm = consumption.climb
|
continue
|
||||||
elif b.waypoint_type in combat_speed_types:
|
min_fuel += for_leg
|
||||||
ppm = consumption.combat
|
|
||||||
else:
|
|
||||||
ppm = consumption.cruise
|
|
||||||
min_fuel += distance.nautical_miles * ppm
|
|
||||||
a.min_fuel = min_fuel
|
a.min_fuel = min_fuel
|
||||||
|
|
||||||
def set_takeoff_time(self, waypoint: FlightWaypoint) -> timedelta:
|
def set_takeoff_time(self, waypoint: FlightWaypoint) -> timedelta:
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import math
|
|||||||
import random
|
import random
|
||||||
from collections import Iterable
|
from collections import Iterable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Union, Any, TypeVar
|
from typing import TypeVar, Union
|
||||||
|
|
||||||
METERS_TO_FEET = 3.28084
|
METERS_TO_FEET = 3.28084
|
||||||
FEET_TO_METERS = 1 / METERS_TO_FEET
|
FEET_TO_METERS = 1 / METERS_TO_FEET
|
||||||
@ -20,6 +20,8 @@ KPH_TO_MS = 1 / MS_TO_KPH
|
|||||||
INHG_TO_HPA = 33.86389
|
INHG_TO_HPA = 33.86389
|
||||||
INHG_TO_MMHG = 25.400002776728
|
INHG_TO_MMHG = 25.400002776728
|
||||||
|
|
||||||
|
LBS_TO_KG = 0.453592
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, order=True)
|
@dataclass(frozen=True, order=True)
|
||||||
class Distance:
|
class Distance:
|
||||||
|
|||||||
@ -19,30 +19,30 @@ from dcs.mapping import Point
|
|||||||
from dcs.unit import Unit
|
from dcs.unit import Unit
|
||||||
from shapely.geometry import Point as ShapelyPoint
|
from shapely.geometry import Point as ShapelyPoint
|
||||||
|
|
||||||
|
from game.ato.flighttype import FlightType
|
||||||
|
from game.ato.flightwaypoint import FlightWaypoint
|
||||||
|
from game.ato.flightwaypointtype import FlightWaypointType
|
||||||
from game.ato.starttype import StartType
|
from game.ato.starttype import StartType
|
||||||
from game.data.doctrine import Doctrine
|
from game.data.doctrine import Doctrine
|
||||||
from game.dcs.aircrafttype import FuelConsumption
|
from game.dcs.aircrafttype import FuelConsumption
|
||||||
from game.flightplan import IpZoneGeometry, JoinZoneGeometry, HoldZoneGeometry
|
from game.flightplan import HoldZoneGeometry, IpZoneGeometry, JoinZoneGeometry
|
||||||
from game.theater import (
|
from game.theater import (
|
||||||
Airfield,
|
Airfield,
|
||||||
|
ConflictTheater,
|
||||||
ControlPoint,
|
ControlPoint,
|
||||||
FrontLine,
|
FrontLine,
|
||||||
MissionTarget,
|
MissionTarget,
|
||||||
|
NavalControlPoint,
|
||||||
SamGroundObject,
|
SamGroundObject,
|
||||||
TheaterGroundObject,
|
TheaterGroundObject,
|
||||||
NavalControlPoint,
|
|
||||||
ConflictTheater,
|
|
||||||
)
|
)
|
||||||
from game.theater.theatergroundobject import (
|
from game.theater.theatergroundobject import (
|
||||||
|
BuildingGroundObject,
|
||||||
EwrGroundObject,
|
EwrGroundObject,
|
||||||
NavalGroundObject,
|
NavalGroundObject,
|
||||||
BuildingGroundObject,
|
|
||||||
)
|
)
|
||||||
from game.utils import Distance, Heading, Speed, feet, meters, nautical_miles, knots
|
from game.utils import Distance, Heading, Speed, feet, knots, meters, nautical_miles
|
||||||
from .closestairfields import ObjectiveDistanceCache
|
from .closestairfields import ObjectiveDistanceCache
|
||||||
from game.ato.flighttype import FlightType
|
|
||||||
from game.ato.flightwaypointtype import FlightWaypointType
|
|
||||||
from game.ato.flightwaypoint import FlightWaypoint
|
|
||||||
from .traveltime import GroundSpeed, TravelTime
|
from .traveltime import GroundSpeed, TravelTime
|
||||||
from .waypointbuilder import StrikeTarget, WaypointBuilder
|
from .waypointbuilder import StrikeTarget, WaypointBuilder
|
||||||
|
|
||||||
@ -126,6 +126,30 @@ class FlightPlan:
|
|||||||
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
|
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
|
||||||
return self.best_speed_between_waypoints(a, b)
|
return self.best_speed_between_waypoints(a, b)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def fuel_consumption_between_points(
|
||||||
|
self, a: FlightWaypoint, b: FlightWaypoint
|
||||||
|
) -> Optional[float]:
|
||||||
|
ppm = self.fuel_rate_to_between_points(a, b)
|
||||||
|
if ppm is None:
|
||||||
|
return None
|
||||||
|
distance = meters(a.position.distance_to_point(b.position))
|
||||||
|
return distance.nautical_miles * ppm
|
||||||
|
|
||||||
|
def fuel_rate_to_between_points(
|
||||||
|
self, a: FlightWaypoint, b: FlightWaypoint
|
||||||
|
) -> Optional[float]:
|
||||||
|
if self.flight.unit_type.fuel_consumption is None:
|
||||||
|
return None
|
||||||
|
if a.waypoint_type is FlightWaypointType.TAKEOFF:
|
||||||
|
return self.flight.unit_type.fuel_consumption.climb
|
||||||
|
if b in self.combat_speed_waypoints:
|
||||||
|
return self.flight.unit_type.fuel_consumption.combat
|
||||||
|
return self.flight.unit_type.fuel_consumption.cruise
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_waypoint(self) -> Optional[FlightWaypoint]:
|
def tot_waypoint(self) -> Optional[FlightWaypoint]:
|
||||||
"""The waypoint that is associated with the package TOT, or None.
|
"""The waypoint that is associated with the package TOT, or None.
|
||||||
@ -343,9 +367,13 @@ class FormationFlightPlan(LoiterFlightPlan):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def package_speed_waypoints(self) -> Set[FlightWaypoint]:
|
def package_speed_waypoints(self) -> set[FlightWaypoint]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
|
||||||
|
return self.package_speed_waypoints
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_waypoint(self) -> Optional[FlightWaypoint]:
|
def tot_waypoint(self) -> Optional[FlightWaypoint]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -595,7 +623,7 @@ class StrikeFlightPlan(FormationFlightPlan):
|
|||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def package_speed_waypoints(self) -> Set[FlightWaypoint]:
|
def package_speed_waypoints(self) -> set[FlightWaypoint]:
|
||||||
return {
|
return {
|
||||||
self.ingress,
|
self.ingress,
|
||||||
self.split,
|
self.split,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user