Fix several cases of wrongly using broken runways.

The usual symptom here was the game breaking when a carrier is
destroyed. The carrier would no longer be operational but missions would
be assigned there that could not generate flight plans.
This commit is contained in:
Dan Albert 2021-05-31 14:11:13 -07:00
parent f6909d2f98
commit 355e6e1d15
7 changed files with 38 additions and 17 deletions

View File

@ -46,6 +46,7 @@ Saves from 2.5 are not compatible with 3.0.
* **[Campaign]** Fixed bug where offshore strike locations were being used to spawn ship objectives.
* **[Flight Planner]** AI strike flight plans now include the correct target actions for building groups.
* **[Flight Planner]** AI BAI/DEAD/SEAD flights now have tasks to attack all groups at the target location, not just the primary group (for multi-group SAM sites).
* **[Flight Planner]** Fixed some contexts where damaged runways would be used. Destroying a carrier will no longer break the game.
# 2.5.1

View File

@ -258,11 +258,9 @@ class ProcurementAi:
) -> Iterator[ControlPoint]:
distance_cache = ObjectiveDistanceCache.get_closest_airfields(request.near)
threatened = []
for cp in distance_cache.airfields_within(request.range):
for cp in distance_cache.operational_airfields_within(request.range):
if not cp.is_friendly(self.is_player):
continue
if not cp.runway_is_operational():
continue
if cp.unclaimed_parking(self.game) < request.number:
continue
if self.threat_zones.threatened(cp.position):

View File

@ -517,7 +517,7 @@ class ControlPoint(MissionTarget, ABC):
max_retreat_distance = nautical_miles(200)
# Skip the first airbase because that's the airbase we're retreating
# from.
airfields = list(closest.airfields_within(max_retreat_distance))[1:]
airfields = list(closest.operational_airfields_within(max_retreat_distance))[1:]
for airbase in airfields:
if not airbase.can_operate(airframe):
continue

View File

@ -124,7 +124,7 @@ class ThreatZones:
cls, location: ControlPoint, max_distance: Distance
) -> Optional[ControlPoint]:
airfields = ObjectiveDistanceCache.get_closest_airfields(location)
for airfield in airfields.airfields_within(max_distance):
for airfield in airfields.all_airfields_within(max_distance):
if airfield.captured != location.captured:
return airfield
return None

View File

@ -162,7 +162,7 @@ class AircraftAllocator:
self, flight: ProposedFlight, task: FlightType
) -> Optional[Tuple[ControlPoint, Squadron]]:
types = aircraft_for_task(task)
airfields_in_range = self.closest_airfields.airfields_within(
airfields_in_range = self.closest_airfields.operational_airfields_within(
flight.max_distance
)
@ -258,7 +258,9 @@ class PackageBuilder:
self, aircraft: Type[FlyingType], arrival: ControlPoint
) -> Optional[ControlPoint]:
divert_limit = nautical_miles(150)
for airfield in self.closest_airfields.airfields_within(divert_limit):
for airfield in self.closest_airfields.operational_airfields_within(
divert_limit
):
if airfield.captured != self.is_player:
continue
if airfield == arrival:
@ -467,9 +469,11 @@ class ObjectiveFinder:
# Off-map spawn locations don't need protection.
continue
airfields_in_proximity = self.closest_airfields_to(cp)
airfields_in_threat_range = airfields_in_proximity.airfields_within(
airfields_in_threat_range = (
airfields_in_proximity.operational_airfields_within(
self.AIRFIELD_THREAT_RANGE
)
)
for airfield in airfields_in_threat_range:
if not airfield.is_friendly(self.is_player):
yield cp

View File

@ -27,17 +27,35 @@ class ClosestAirfields:
def operational_airfields(self) -> Iterator[ControlPoint]:
return (c for c in self.closest_airfields if c.runway_is_operational())
def airfields_within(self, distance: Distance) -> Iterator[ControlPoint]:
def _airfields_within(
self, distance: Distance, operational: bool
) -> Iterator[ControlPoint]:
airfields = (
self.operational_airfields if operational else self.closest_airfields
)
for cp in airfields:
if cp.distance_to(self.target) < distance.meters:
yield cp
else:
break
def operational_airfields_within(
self, distance: Distance
) -> Iterator[ControlPoint]:
"""Iterates over all airfields within the given range of the target.
Note that this iterates over *all* airfields, not just friendly
airfields.
"""
for cp in self.closest_airfields:
if cp.distance_to(self.target) < distance.meters:
yield cp
else:
break
return self._airfields_within(distance, operational=True)
def all_airfields_within(self, distance: Distance) -> Iterator[ControlPoint]:
"""Iterates over all airfields within the given range of the target.
Note that this iterates over *all* airfields, not just friendly
airfields.
"""
return self._airfields_within(distance, operational=False)
class ObjectiveDistanceCache:

View File

@ -1807,7 +1807,7 @@ class FlightPlanBuilder:
# We'll always have a package, but if this is being planned via the UI
# it could be the first flight in the package.
if not self.package.flights:
raise RuntimeError(
raise PlanningError(
"Cannot determine source airfield for package with no flights"
)
@ -1819,4 +1819,4 @@ class FlightPlanBuilder:
for flight in self.package.flights:
if flight.departure == airfield:
return airfield
raise RuntimeError("Could not find any airfield assigned to this package")
raise PlanningError("Could not find any airfield assigned to this package")