diff --git a/changelog.md b/changelog.md index a0a34ccc..ddba6c54 100644 --- a/changelog.md +++ b/changelog.md @@ -15,6 +15,7 @@ Saves from 6.0.0 are compatible with 6.1.0 * **[Engine]** Support for DCS 2.8.1.34437. * **[Factions]** Defaulted bluefor modern to use Georgian and Ukrainian liveries for Russian aircraft. * **[Factions]** Added Peru. +* **[Flight Planning]** AEW&C and Refueling flights are now plannable on LHA carriers. * **[Flight Planning]** Refueling flights planned on aircraft carriers will act as a recovery tanker for the carrier. * **[Loadouts]** Adjusted F-15E loadouts. * **[Mission Generation]** The previous turn will now be saved as last_turn.liberation when submitting mission results. This is often essential for debugging bug reports. **Include this file in the bug report whenever it is available.** diff --git a/game/ato/flightplans/flightplanbuildertypes.py b/game/ato/flightplans/flightplanbuildertypes.py index 0f4a0de7..d994bb73 100644 --- a/game/ato/flightplans/flightplanbuildertypes.py +++ b/game/ato/flightplans/flightplanbuildertypes.py @@ -3,8 +3,7 @@ from __future__ import annotations from typing import Any, TYPE_CHECKING, Type from game.ato import FlightType -from game.ato.flightplans.shiprecoverytanker import RecoveryTankerFlightPlan -from game.theater.controlpoint import Carrier +from game.theater.controlpoint import NavalControlPoint from .aewc import AewcFlightPlan from .airassault import AirAssaultFlightPlan from .airlift import AirliftFlightPlan @@ -21,6 +20,7 @@ from .ocarunway import OcaRunwayFlightPlan from .packagerefueling import PackageRefuelingFlightPlan from .planningerror import PlanningError from .sead import SeadFlightPlan +from .shiprecoverytanker import RecoveryTankerFlightPlan from .strike import StrikeFlightPlan from .sweep import SweepFlightPlan from .tarcap import TarCapFlightPlan @@ -37,7 +37,7 @@ class FlightPlanBuilderTypes: if flight.flight_type is FlightType.REFUELING: target = flight.package.target if target.is_friendly(flight.squadron.player) and isinstance( - target, Carrier + target, NavalControlPoint ): return RecoveryTankerFlightPlan.builder_type() if target.is_friendly(flight.squadron.player) or isinstance( diff --git a/game/ato/flightplans/shiprecoverytanker.py b/game/ato/flightplans/shiprecoverytanker.py index 8deca407..cfa20e46 100644 --- a/game/ato/flightplans/shiprecoverytanker.py +++ b/game/ato/flightplans/shiprecoverytanker.py @@ -8,7 +8,6 @@ from game.ato.flightplans.ibuilder import IBuilder from game.ato.flightplans.standard import StandardLayout from game.ato.flightplans.waypointbuilder import WaypointBuilder from game.ato.flightwaypoint import FlightWaypoint -from game.utils import feet @dataclass(frozen=True) @@ -39,7 +38,7 @@ class RecoveryTankerFlightPlan(StandardFlightPlan[RecoveryTankerLayout]): @property def mission_departure_time(self) -> timedelta: - return timedelta(hours=2) + return self.patrol_end_time @property def patrol_start_time(self) -> timedelta: @@ -47,7 +46,7 @@ class RecoveryTankerFlightPlan(StandardFlightPlan[RecoveryTankerLayout]): @property def patrol_end_time(self) -> timedelta: - return self.tot + self.mission_departure_time + return self.tot + timedelta(hours=2) def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: if waypoint == self.tot_waypoint: @@ -56,28 +55,33 @@ class RecoveryTankerFlightPlan(StandardFlightPlan[RecoveryTankerLayout]): def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: if waypoint == self.tot_waypoint: - return self.tot + self.mission_departure_time + return self.mission_departure_time return None class Builder(IBuilder[RecoveryTankerFlightPlan, RecoveryTankerLayout]): def layout(self) -> RecoveryTankerLayout: - # TODO: Propagate the ship position. - ship = self.package.target.position - builder = WaypointBuilder(self.flight, self.coalition) - recovery = builder.recovery_tanker(ship) + # TODO: Propagate the ship position to the Tanker's TOT, + # so that we minimize the tanker's need to catch up to the carrier. + recovery_ship = self.package.target.position + recovery_tanker = builder.recovery_tanker(recovery_ship) + # We don't have per aircraft cruise altitudes, so just reuse patrol altitude? tanker_type = self.flight.unit_type - altitude = tanker_type.preferred_patrol_altitude + nav_cruise_altitude = tanker_type.preferred_patrol_altitude return RecoveryTankerLayout( departure=builder.takeoff(self.flight.departure), - nav_to=builder.nav_path(self.flight.departure.position, ship, altitude), - nav_from=builder.nav_path(ship, self.flight.arrival.position, altitude), - recovery_ship=recovery, + nav_to=builder.nav_path( + self.flight.departure.position, recovery_ship, nav_cruise_altitude + ), + nav_from=builder.nav_path( + recovery_ship, self.flight.arrival.position, nav_cruise_altitude + ), + recovery_ship=recovery_tanker, arrival=builder.land(self.flight.arrival), divert=builder.divert(self.flight.divert), bullseye=builder.bullseye(), diff --git a/game/missiongenerator/aircraft/waypoints/recoverytanker.py b/game/missiongenerator/aircraft/waypoints/recoverytanker.py index 46354d11..8f89267d 100644 --- a/game/missiongenerator/aircraft/waypoints/recoverytanker.py +++ b/game/missiongenerator/aircraft/waypoints/recoverytanker.py @@ -2,24 +2,27 @@ from dcs.point import MovingPoint from dcs.task import ActivateBeaconCommand, RecoveryTanker from game.ato import FlightType -from game.missiongenerator.missiondata import TankerInfo from game.utils import feet, knots from .pydcswaypointbuilder import PydcsWaypointBuilder class RecoveryTankerBuilder(PydcsWaypointBuilder): def add_tasks(self, waypoint: MovingPoint) -> None: - if self.flight.flight_type == FlightType.REFUELING: - group_id = self._get_carrier_group_id() - speed = knots(250).meters_per_second - altitude = feet(6000).meters - # Last waypoint has index of 1. - last_waypoint = 2 - recovery_tanker = RecoveryTanker(group_id, speed, altitude, last_waypoint) - waypoint.add_task(recovery_tanker) + assert self.flight.flight_type == FlightType.REFUELING + group_id = self._get_carrier_group_id() + speed = knots(250).meters_per_second + altitude = feet(6000).meters - self.configure_tanker_tacan(waypoint) + # Last waypoint has index of 1. + # Give the tanker a end condition of the last carrier waypoint. + # If the carrier ever gets more than one waypoint this approach needs to change. + last_waypoint = 2 + recovery_tanker = RecoveryTanker(group_id, speed, altitude, last_waypoint) + + waypoint.add_task(recovery_tanker) + + self.configure_tanker_tacan(waypoint) def _get_carrier_group_id(self) -> int: name = self.package.target.name diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index e7d5b516..d004b294 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -1177,6 +1177,8 @@ class NavalControlPoint(ControlPoint, ABC): if self.is_friendly(for_player): yield from [ + FlightType.AEWC, + FlightType.REFUELING, # TODO: FlightType.INTERCEPTION # TODO: Buddy tanking for the A-4? # TODO: Rescue chopper? @@ -1272,8 +1274,7 @@ class Carrier(NavalControlPoint): yield from super().mission_types(for_player) if self.is_friendly(for_player): yield from [ - FlightType.AEWC, - FlightType.REFUELING, + # Nothing yet. ] def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None: