mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Reimplement aircraft retreats for captured bases.
This commit is contained in:
parent
5fae178081
commit
469b1e5efe
@ -263,14 +263,32 @@ class Squadron:
|
|||||||
def can_fulfill_flight(self, count: int) -> bool:
|
def can_fulfill_flight(self, count: int) -> bool:
|
||||||
return self.can_provide_pilots(count) and self.untasked_aircraft >= count
|
return self.can_provide_pilots(count) and self.untasked_aircraft >= count
|
||||||
|
|
||||||
def refund_orders(self) -> None:
|
def refund_orders(self, count: Optional[int] = None) -> None:
|
||||||
self.coalition.adjust_budget(self.aircraft.price * self.pending_deliveries)
|
if count is None:
|
||||||
self.pending_deliveries = 0
|
count = self.pending_deliveries
|
||||||
|
self.coalition.adjust_budget(self.aircraft.price * count)
|
||||||
|
self.pending_deliveries -= count
|
||||||
|
|
||||||
def deliver_orders(self) -> None:
|
def deliver_orders(self) -> None:
|
||||||
|
self.cancel_overflow_orders()
|
||||||
self.owned_aircraft += self.pending_deliveries
|
self.owned_aircraft += self.pending_deliveries
|
||||||
self.pending_deliveries = 0
|
self.pending_deliveries = 0
|
||||||
|
|
||||||
|
def relocate_to(self, destination: ControlPoint) -> None:
|
||||||
|
self.location = destination
|
||||||
|
|
||||||
|
def cancel_overflow_orders(self) -> None:
|
||||||
|
if self.pending_deliveries <= 0:
|
||||||
|
return
|
||||||
|
overflow = -self.location.unclaimed_parking()
|
||||||
|
if overflow > 0:
|
||||||
|
sell_count = min(overflow, self.pending_deliveries)
|
||||||
|
logging.debug(
|
||||||
|
f"{self.location} is overfull by {overflow} aircraft. Cancelling "
|
||||||
|
f"orders for {sell_count} aircraft to make room."
|
||||||
|
)
|
||||||
|
self.refund_orders(sell_count)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_fulfillable_aircraft(self) -> int:
|
def max_fulfillable_aircraft(self) -> int:
|
||||||
return max(self.number_of_available_pilots, self.untasked_aircraft)
|
return max(self.number_of_available_pilots, self.untasked_aircraft)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
import heapq
|
import heapq
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
@ -581,31 +582,77 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def aircraft_retreat_destination(
|
def aircraft_retreat_destination(
|
||||||
self, airframe: AircraftType
|
self, squadron: Squadron
|
||||||
) -> Optional[ControlPoint]:
|
) -> Optional[ControlPoint]:
|
||||||
closest = ObjectiveDistanceCache.get_closest_airfields(self)
|
closest = ObjectiveDistanceCache.get_closest_airfields(self)
|
||||||
# TODO: Should be airframe dependent.
|
max_retreat_distance = squadron.aircraft.max_mission_range
|
||||||
max_retreat_distance = nautical_miles(200)
|
|
||||||
# Skip the first airbase because that's the airbase we're retreating
|
# Skip the first airbase because that's the airbase we're retreating
|
||||||
# from.
|
# from.
|
||||||
airfields = list(closest.operational_airfields_within(max_retreat_distance))[1:]
|
airfields = list(closest.operational_airfields_within(max_retreat_distance))[1:]
|
||||||
|
not_preferred: Optional[ControlPoint] = None
|
||||||
|
overfull: list[ControlPoint] = []
|
||||||
for airbase in airfields:
|
for airbase in airfields:
|
||||||
if not airbase.can_operate(airframe):
|
|
||||||
continue
|
|
||||||
if airbase.captured != self.captured:
|
if airbase.captured != self.captured:
|
||||||
continue
|
continue
|
||||||
if airbase.unclaimed_parking() > 0:
|
|
||||||
return airbase
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
if airbase.unclaimed_parking() < squadron.owned_aircraft:
|
||||||
def _retreat_squadron(squadron: Squadron) -> None:
|
if airbase.can_operate(squadron.aircraft):
|
||||||
logging.error("Air unit retreat not currently implemented")
|
overfull.append(airbase)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if squadron.operates_from(airbase):
|
||||||
|
# Has room, is a preferred base type for this squadron, and is the
|
||||||
|
# closest choice. No need to keep looking.
|
||||||
|
return airbase
|
||||||
|
|
||||||
|
if not_preferred is None and airbase.can_operate(squadron.aircraft):
|
||||||
|
# Has room and is capable of operating from this base, but it isn't
|
||||||
|
# preferred. Remember this option and use it if we can't find a
|
||||||
|
# preferred base type with room.
|
||||||
|
not_preferred = airbase
|
||||||
|
if not_preferred is not None:
|
||||||
|
# It's not our best choice but the other choices don't have room for the
|
||||||
|
# squadron and would lead to aircraft being captured.
|
||||||
|
return not_preferred
|
||||||
|
|
||||||
|
# No base was available with enough room. Find whichever base has the most room
|
||||||
|
# available so we lose as little as possible. The overfull list is already
|
||||||
|
# sorted by distance, and filtered for appropriate destinations.
|
||||||
|
base_for_fewest_losses: Optional[ControlPoint] = None
|
||||||
|
loss_count = math.inf
|
||||||
|
for airbase in overfull:
|
||||||
|
overflow = -(
|
||||||
|
airbase.unclaimed_parking()
|
||||||
|
- squadron.owned_aircraft
|
||||||
|
- squadron.pending_deliveries
|
||||||
|
)
|
||||||
|
if overflow < loss_count:
|
||||||
|
loss_count = overflow
|
||||||
|
base_for_fewest_losses = airbase
|
||||||
|
return base_for_fewest_losses
|
||||||
|
|
||||||
|
def _retreat_squadron(self, game: Game, squadron: Squadron) -> None:
|
||||||
|
destination = self.aircraft_retreat_destination(squadron)
|
||||||
|
if destination is None:
|
||||||
|
squadron.refund_orders()
|
||||||
|
self.capture_aircraft(game, squadron.aircraft, squadron.owned_aircraft)
|
||||||
|
return
|
||||||
|
logging.debug(f"{squadron} retreating to {destination} from {self}")
|
||||||
|
squadron.relocate_to(destination)
|
||||||
|
squadron.cancel_overflow_orders()
|
||||||
|
overflow = -destination.unclaimed_parking()
|
||||||
|
if overflow > 0:
|
||||||
|
logging.debug(
|
||||||
|
f"Not enough room for {squadron} at {destination}. Capturing "
|
||||||
|
f"{overflow} aircraft."
|
||||||
|
)
|
||||||
|
self.capture_aircraft(game, squadron.aircraft, overflow)
|
||||||
|
squadron.owned_aircraft -= overflow
|
||||||
|
|
||||||
def retreat_air_units(self, game: Game) -> None:
|
def retreat_air_units(self, game: Game) -> None:
|
||||||
# TODO: Capture in order of price to retain maximum value?
|
# TODO: Capture in order of price to retain maximum value?
|
||||||
for squadron in self.squadrons:
|
for squadron in self.squadrons:
|
||||||
self._retreat_squadron(squadron)
|
self._retreat_squadron(game, squadron)
|
||||||
|
|
||||||
def depopulate_uncapturable_tgos(self) -> None:
|
def depopulate_uncapturable_tgos(self) -> None:
|
||||||
for tgo in self.connected_objectives:
|
for tgo in self.connected_objectives:
|
||||||
@ -616,8 +663,6 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
def capture(self, game: Game, for_player: bool) -> None:
|
def capture(self, game: Game, for_player: bool) -> None:
|
||||||
new_coalition = game.coalition_for(for_player)
|
new_coalition = game.coalition_for(for_player)
|
||||||
self.ground_unit_orders.refund_all(self.coalition)
|
self.ground_unit_orders.refund_all(self.coalition)
|
||||||
for squadron in self.squadrons:
|
|
||||||
squadron.refund_orders()
|
|
||||||
self.retreat_ground_units(game)
|
self.retreat_ground_units(game)
|
||||||
self.retreat_air_units(game)
|
self.retreat_air_units(game)
|
||||||
self.depopulate_uncapturable_tgos()
|
self.depopulate_uncapturable_tgos()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user