From 938d6b4bdf9c29a1bab128868e8f1c2eed318e32 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 28 Jan 2023 12:42:21 -0800 Subject: [PATCH] Fix invalid tanker planning. All three refueling missions share a common task type and differentiate their behavior based on the type and allegiance of the package target. This means that if the theater commander identifies a carrier as the best location for a theater refueling task, a recovery tanker will be planned by mistake. If this happens on a sunken carrier, mission generation will fail because a recovery tanker cannot be generated for a sunken carrier. Fix the crash and the misplanned theater tanker by preventing the commander from choosing fleet control points as refueling targets. Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2693. (cherry picked from commit eea98b01f6e7311969bae08325fd38345dfbedb1) --- changelog.md | 1 + game/commander/objectivefinder.py | 10 ++++------ game/commander/theaterstate.py | 7 ++++++- .../aircraft/waypoints/recoverytanker.py | 9 +++++---- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/changelog.md b/changelog.md index a4b848b2..d808717b 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,7 @@ ## Fixes * **[Data]** Fixed unit ID for the KS-19 AAA. KS-19 would not previously generate correctly in missions. A new game is required for this fix to take effect. +* **[Flight Planning]** Automatic flight planning will no longer accidentally plan a recovery tanker instead of a theater refueling package. This fixes a potential crash during mission generation when opfor plans a refueling task at a sunk carrier. You'll need to skip the current turn to force opfor to replan their flights to get the fix. # 6.1.0 diff --git a/game/commander/objectivefinder.py b/game/commander/objectivefinder.py index a82e8218..e229de7d 100644 --- a/game/commander/objectivefinder.py +++ b/game/commander/objectivefinder.py @@ -5,6 +5,7 @@ import operator from collections.abc import Iterable, Iterator from typing import TYPE_CHECKING, TypeVar +from game.ato.closestairfields import ClosestAirfields, ObjectiveDistanceCache from game.theater import ( Airfield, ControlPoint, @@ -15,12 +16,11 @@ from game.theater import ( ) from game.theater.theatergroundobject import ( BuildingGroundObject, + IadsBuildingGroundObject, IadsGroundObject, NavalGroundObject, - IadsBuildingGroundObject, ) from game.utils import meters, nautical_miles -from game.ato.closestairfields import ClosestAirfields, ObjectiveDistanceCache if TYPE_CHECKING: from game import Game @@ -209,22 +209,20 @@ class ObjectiveFinder: raise RuntimeError("Found no friendly control points. You probably lost.") return farthest - def closest_friendly_control_point(self) -> ControlPoint: + def preferred_theater_refueling_control_point(self) -> ControlPoint | None: """Finds the friendly control point that is closest to any threats.""" threat_zones = self.game.threat_zone_for(not self.is_player) closest = None min_distance = meters(math.inf) for cp in self.friendly_control_points(): - if isinstance(cp, OffMapSpawn): + if isinstance(cp, OffMapSpawn) or cp.is_fleet: continue distance = threat_zones.distance_to_threat(cp.position) if distance < min_distance: closest = cp min_distance = distance - if closest is None: - raise RuntimeError("Found no friendly control points. You probably lost.") return closest def enemy_control_points(self) -> Iterator[ControlPoint]: diff --git a/game/commander/theaterstate.py b/game/commander/theaterstate.py index 3bd05f73..1412439f 100644 --- a/game/commander/theaterstate.py +++ b/game/commander/theaterstate.py @@ -153,6 +153,11 @@ class TheaterState(WorldState["TheaterState"]): barcap_duration = coalition.doctrine.cap_duration.total_seconds() barcap_rounds = math.ceil(mission_duration / barcap_duration) + refueling_targets: list[MissionTarget] = [] + theater_refuling_point = finder.preferred_theater_refueling_control_point() + if theater_refuling_point is not None: + refueling_targets.append(theater_refuling_point) + return TheaterState( context=context, barcaps_needed={ @@ -162,7 +167,7 @@ class TheaterState(WorldState["TheaterState"]): front_line_stances={f: None for f in finder.front_lines()}, vulnerable_front_lines=list(finder.front_lines()), aewc_targets=[finder.farthest_friendly_control_point()], - refueling_targets=[finder.closest_friendly_control_point()], + refueling_targets=refueling_targets, enemy_air_defenses=list(finder.enemy_air_defenses()), threatening_air_defenses=[], detecting_air_defenses=[], diff --git a/game/missiongenerator/aircraft/waypoints/recoverytanker.py b/game/missiongenerator/aircraft/waypoints/recoverytanker.py index 8f89267d..1451f0d7 100644 --- a/game/missiongenerator/aircraft/waypoints/recoverytanker.py +++ b/game/missiongenerator/aircraft/waypoints/recoverytanker.py @@ -31,10 +31,11 @@ class RecoveryTankerBuilder(PydcsWaypointBuilder): for key, value in theater_objects.items(): # Check name and position in case there are multiple of same carrier. if name in key and value.theater_unit.position == carrier_position: - theater_mapping = value - break - assert theater_mapping is not None - return theater_mapping.dcs_group_id + return value.dcs_group_id + raise RuntimeError( + f"Could not find a carrier in the mission matching {name} at " + f"({carrier_position.x}, {carrier_position.y})" + ) def configure_tanker_tacan(self, waypoint: MovingPoint) -> None: