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)
This commit is contained in:
Dan Albert 2023-01-28 12:42:21 -08:00
parent 5caf07f476
commit 938d6b4bdf
4 changed files with 16 additions and 11 deletions

View File

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

View File

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

View File

@ -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=[],

View File

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