mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Retreat air units from captured bases.
If there are not airbases withing ferry range with available parking for the aircraft then the aircraft will be captured and sold. Otherwise the aircraft will retreat to the closest available airbase. Fixes https://github.com/Khopa/dcs_liberation/issues/693
This commit is contained in:
parent
461635c001
commit
b5278550e7
@ -14,7 +14,7 @@ Saves from 2.3 are not compatible with 2.4.
|
||||
* **[Mission Generator]** Multiple groups are created for complex SAM sites (SAMs with additional point defense or SHORADS), improving Skynet behavior.
|
||||
* **[Skynet]** Point defenses are now configured to remain on to protect the site they accompany.
|
||||
* **[Balance]** Opfor now gains income using the same rules as the player, significantly increasing their income relative to the player for most campaigns.
|
||||
* **[Balance]** Ground units now retreat from captured bases when they are connected to a friendly base. Units with no retreat path will be captured and sold.
|
||||
* **[Balance]** Units now retreat from captured bases when able. Units with no retreat path will be captured and sold.
|
||||
* **[Economy]** FOBs generate only $10M per turn (previously $20M like airbases).
|
||||
* **[Economy]** Carriers and off-map spawns generate no income (previously $20M like airbases).
|
||||
* **[UI]** Multi-SAM objectives now show threat and detection rings per group.
|
||||
|
||||
@ -22,6 +22,7 @@ from dcs.terrain.terrain import Airport, ParkingSlot
|
||||
from dcs.unittype import FlyingType
|
||||
|
||||
from game import db
|
||||
from gen.flights.closestairfields import ObjectiveDistanceCache
|
||||
from gen.ground_forces.ai_ground_planner_db import TYPE_SHORAD
|
||||
from gen.ground_forces.combat_stance import CombatStance
|
||||
from gen.runways import RunwayAssigner, RunwayData
|
||||
@ -35,6 +36,8 @@ from .theatergroundobject import (
|
||||
TheaterGroundObject,
|
||||
VehicleGroupGroundObject,
|
||||
)
|
||||
from ..db import PRICES
|
||||
from ..utils import nautical_miles
|
||||
from ..weather import Conditions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -416,10 +419,62 @@ class ControlPoint(MissionTarget, ABC):
|
||||
destination.control_point.base.commision_units({unit_type: 1})
|
||||
destination = heapq.heappushpop(destinations, destination)
|
||||
|
||||
def capture_aircraft(self, game: Game, airframe: Type[FlyingType],
|
||||
count: int) -> None:
|
||||
try:
|
||||
value = PRICES[airframe] * count
|
||||
except KeyError:
|
||||
logging.exception(f"Unknown price for {airframe.id}")
|
||||
return
|
||||
|
||||
game.adjust_budget(value, player=not self.captured)
|
||||
game.message(
|
||||
f"No valid retreat destination in range of {self.name} for "
|
||||
f"{airframe.id}. {count} aircraft have been captured and sold for "
|
||||
f"${value}M.")
|
||||
|
||||
def aircraft_retreat_destination(
|
||||
self, game: Game,
|
||||
airframe: Type[FlyingType]) -> Optional[ControlPoint]:
|
||||
closest = ObjectiveDistanceCache.get_closest_airfields(self)
|
||||
# TODO: Should be airframe dependent.
|
||||
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:]
|
||||
for airbase in airfields:
|
||||
if not airbase.can_operate(airframe):
|
||||
continue
|
||||
if airbase.captured != self.captured:
|
||||
continue
|
||||
if airbase.unclaimed_parking(game) > 0:
|
||||
return airbase
|
||||
return None
|
||||
|
||||
def _retreat_air_units(self, game: Game, airframe: Type[FlyingType],
|
||||
count: int) -> None:
|
||||
while count:
|
||||
logging.debug(f"Retreating {count} {airframe.id} from {self.name}")
|
||||
destination = self.aircraft_retreat_destination(game, airframe)
|
||||
if destination is None:
|
||||
self.capture_aircraft(game, airframe, count)
|
||||
return
|
||||
parking = destination.unclaimed_parking(game)
|
||||
transfer_amount = min([parking, count])
|
||||
destination.base.commision_units({airframe: transfer_amount})
|
||||
count -= transfer_amount
|
||||
|
||||
def retreat_air_units(self, game: Game) -> None:
|
||||
# TODO: Capture in order of price to retain maximum value?
|
||||
while self.base.aircraft:
|
||||
airframe, count = self.base.aircraft.popitem()
|
||||
self._retreat_air_units(game, airframe, count)
|
||||
|
||||
# TODO: Should be Airbase specific.
|
||||
def capture(self, game: Game, for_player: bool) -> None:
|
||||
self.pending_unit_deliveries.refund_all(game)
|
||||
self.retreat_ground_units(game)
|
||||
self.retreat_air_units(game)
|
||||
|
||||
if for_player:
|
||||
self.captured = True
|
||||
@ -428,8 +483,6 @@ class ControlPoint(MissionTarget, ABC):
|
||||
|
||||
self.base.set_strength_to_minimum()
|
||||
|
||||
self.base.aircraft = {}
|
||||
|
||||
self.clear_base_defenses()
|
||||
from .start_generator import BaseDefenseGenerator
|
||||
BaseDefenseGenerator(game, self).generate()
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
"""Objective adjacency lists."""
|
||||
from typing import Dict, Iterator, List, Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, Iterator, List, Optional, TYPE_CHECKING
|
||||
|
||||
from game.theater import ConflictTheater, ControlPoint, MissionTarget
|
||||
from game.utils import Distance
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.theater import ConflictTheater, ControlPoint, MissionTarget
|
||||
|
||||
|
||||
class ClosestAirfields:
|
||||
"""Precalculates which control points are closes to the given target."""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user