mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Account for planned missions for breakthrough.
Consider BAI missions planned this turn when determining if a control point is still garrisioned for preventing breakthrough. This isn't very accurate yet since the HTN isn't checking for aircraft fulfillment yet, so it might *plan* a mission to kill the garrison, but there's no way to know if it will be fulfilled.
This commit is contained in:
parent
c180eb466d
commit
4534758c21
@ -3,59 +3,50 @@ from __future__ import annotations
|
||||
from collections import Iterator
|
||||
from dataclasses import dataclass
|
||||
|
||||
from game.theater import ConflictTheater, ControlPoint
|
||||
from game.theater import ControlPoint
|
||||
from game.theater.theatergroundobject import VehicleGroupGroundObject
|
||||
from game.utils import meters, nautical_miles
|
||||
from game.utils import meters
|
||||
|
||||
|
||||
@dataclass
|
||||
class Garrisons:
|
||||
blocking_capture: list[VehicleGroupGroundObject]
|
||||
defending_front_line: list[VehicleGroupGroundObject]
|
||||
reserves: list[VehicleGroupGroundObject]
|
||||
|
||||
@property
|
||||
def in_priority_order(self) -> Iterator[VehicleGroupGroundObject]:
|
||||
yield from self.blocking_capture
|
||||
yield from self.defending_front_line
|
||||
yield from self.reserves
|
||||
|
||||
def eliminate(self, garrison: VehicleGroupGroundObject) -> None:
|
||||
if garrison in self.blocking_capture:
|
||||
self.blocking_capture.remove(garrison)
|
||||
if garrison in self.defending_front_line:
|
||||
self.defending_front_line.remove(garrison)
|
||||
if garrison in self.reserves:
|
||||
self.reserves.remove(garrison)
|
||||
|
||||
def __contains__(self, item: VehicleGroupGroundObject) -> bool:
|
||||
return item in self.in_priority_order
|
||||
|
||||
@classmethod
|
||||
def from_theater(cls, theater: ConflictTheater, player_owned: bool) -> Garrisons:
|
||||
def for_control_point(cls, control_point: ControlPoint) -> Garrisons:
|
||||
"""Categorize garrison groups based on target priority.
|
||||
|
||||
Any garrisons blocking base capture are the highest priority, followed by other
|
||||
garrisons at front-line bases, and finally any garrisons in reserve at other
|
||||
bases.
|
||||
Any garrisons blocking base capture are the highest priority.
|
||||
"""
|
||||
blocking = []
|
||||
defending = []
|
||||
reserves = []
|
||||
for cp in theater.control_points_for(player_owned):
|
||||
garrisons = [
|
||||
tgo
|
||||
for tgo in cp.ground_objects
|
||||
if isinstance(tgo, VehicleGroupGroundObject) and not tgo.is_dead
|
||||
]
|
||||
if not cp.has_active_frontline:
|
||||
reserves.extend(garrisons)
|
||||
continue
|
||||
garrisons = [
|
||||
tgo
|
||||
for tgo in control_point.ground_objects
|
||||
if isinstance(tgo, VehicleGroupGroundObject) and not tgo.is_dead
|
||||
]
|
||||
for garrison in garrisons:
|
||||
if (
|
||||
meters(garrison.distance_to(control_point))
|
||||
< ControlPoint.CAPTURE_DISTANCE
|
||||
):
|
||||
blocking.append(garrison)
|
||||
else:
|
||||
defending.append(garrison)
|
||||
|
||||
for garrison in garrisons:
|
||||
if meters(garrison.distance_to(cp)) < ControlPoint.CAPTURE_DISTANCE:
|
||||
blocking.append(garrison)
|
||||
else:
|
||||
defending.append(garrison)
|
||||
|
||||
return Garrisons(blocking, defending, reserves)
|
||||
return Garrisons(blocking, defending)
|
||||
|
||||
@ -223,17 +223,18 @@ class ObjectiveFinder:
|
||||
if not c.is_friendly(self.is_player)
|
||||
)
|
||||
|
||||
def all_possible_targets(self) -> Iterator[MissionTarget]:
|
||||
"""Iterates over all possible mission targets in the theater.
|
||||
|
||||
Valid mission targets are control points (airfields and carriers), front
|
||||
lines, and ground objects (SAM sites, factories, resource extraction
|
||||
sites, etc).
|
||||
"""
|
||||
for cp in self.game.theater.controlpoints:
|
||||
yield cp
|
||||
yield from cp.ground_objects
|
||||
yield from self.front_lines()
|
||||
def prioritized_unisolated_points(self) -> list[ControlPoint]:
|
||||
prioritized = []
|
||||
capturable_later = []
|
||||
for cp in self.game.theater.control_points_for(not self.is_player):
|
||||
if cp.is_isolated:
|
||||
continue
|
||||
if cp.has_active_frontline:
|
||||
prioritized.append(cp)
|
||||
else:
|
||||
capturable_later.append(cp)
|
||||
prioritized.extend(self._targets_by_range(capturable_later))
|
||||
return prioritized
|
||||
|
||||
@staticmethod
|
||||
def closest_airfields_to(location: MissionTarget) -> ClosestAirfields:
|
||||
|
||||
@ -7,5 +7,6 @@ from game.htn import CompoundTask, Method
|
||||
|
||||
class AttackGarrisons(CompoundTask[TheaterState]):
|
||||
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
|
||||
for garrison in state.enemy_garrisons.in_priority_order:
|
||||
yield [PlanBai(garrison)]
|
||||
for garrisons in state.enemy_garrisons.values():
|
||||
for garrison in garrisons.in_priority_order:
|
||||
yield [PlanBai(garrison)]
|
||||
|
||||
@ -14,12 +14,12 @@ class PlanBai(PackagePlanningTask[VehicleGroupGroundObject]):
|
||||
def preconditions_met(self, state: TheaterState) -> bool:
|
||||
if not super().preconditions_met(state):
|
||||
return False
|
||||
if self.target not in state.enemy_garrisons:
|
||||
if not state.has_garrison(self.target):
|
||||
return False
|
||||
return self.target_area_preconditions_met(state)
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.enemy_garrisons.eliminate(self.target)
|
||||
state.eliminate_garrison(self.target)
|
||||
|
||||
def propose_flights(self, doctrine: Doctrine) -> None:
|
||||
self.propose_flight(FlightType.BAI, 2, doctrine.mission_ranges.offensive)
|
||||
|
||||
@ -2,9 +2,6 @@ from __future__ import annotations
|
||||
|
||||
from game.commander.tasks.frontlinestancetask import FrontLineStanceTask
|
||||
from game.commander.theaterstate import TheaterState
|
||||
from game.theater import ControlPoint
|
||||
from game.theater.theatergroundobject import VehicleGroupGroundObject
|
||||
from game.utils import meters
|
||||
from gen.ground_forces.combat_stance import CombatStance
|
||||
|
||||
|
||||
@ -17,20 +14,14 @@ class BreakthroughAttack(FrontLineStanceTask):
|
||||
def have_sufficient_front_line_advantage(self) -> bool:
|
||||
return self.ground_force_balance >= 2.0
|
||||
|
||||
@property
|
||||
def opposing_garrisons_eliminated(self) -> bool:
|
||||
# TODO: Should operate on TheaterState to account for BAIs planned this turn.
|
||||
for tgo in self.enemy_cp.ground_objects:
|
||||
if not isinstance(tgo, VehicleGroupGroundObject):
|
||||
continue
|
||||
if meters(tgo.distance_to(self.enemy_cp)) < ControlPoint.CAPTURE_DISTANCE:
|
||||
return False
|
||||
return True
|
||||
def opposing_garrisons_eliminated(self, state: TheaterState) -> bool:
|
||||
garrisons = state.enemy_garrisons[self.enemy_cp]
|
||||
return not bool(garrisons.blocking_capture)
|
||||
|
||||
def preconditions_met(self, state: TheaterState) -> bool:
|
||||
if not super().preconditions_met(state):
|
||||
return False
|
||||
return self.opposing_garrisons_eliminated
|
||||
return self.opposing_garrisons_eliminated(state)
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
super().apply_effects(state)
|
||||
|
||||
@ -15,6 +15,7 @@ from game.theater.theatergroundobject import (
|
||||
TheaterGroundObject,
|
||||
NavalGroundObject,
|
||||
IadsGroundObject,
|
||||
VehicleGroupGroundObject,
|
||||
)
|
||||
from game.threatzones import ThreatZones
|
||||
from game.transfers import Convoy, CargoShip
|
||||
@ -41,7 +42,7 @@ class TheaterState(WorldState["TheaterState"]):
|
||||
enemy_convoys: list[Convoy]
|
||||
enemy_shipping: list[CargoShip]
|
||||
enemy_ships: list[NavalGroundObject]
|
||||
enemy_garrisons: Garrisons
|
||||
enemy_garrisons: dict[ControlPoint, Garrisons]
|
||||
oca_targets: list[ControlPoint]
|
||||
strike_targets: list[TheaterGroundObject[Any]]
|
||||
enemy_barcaps: list[ControlPoint]
|
||||
@ -72,6 +73,12 @@ class TheaterState(WorldState["TheaterState"]):
|
||||
self.enemy_ships.remove(target)
|
||||
self._rebuild_threat_zones()
|
||||
|
||||
def has_garrison(self, target: VehicleGroupGroundObject) -> bool:
|
||||
return target in self.enemy_garrisons[target.control_point]
|
||||
|
||||
def eliminate_garrison(self, target: VehicleGroupGroundObject) -> None:
|
||||
self.enemy_garrisons[target.control_point].eliminate(target)
|
||||
|
||||
def clone(self) -> TheaterState:
|
||||
# Do not use copy.deepcopy. Copying every TGO, control point, etc is absurdly
|
||||
# expensive.
|
||||
@ -89,7 +96,9 @@ class TheaterState(WorldState["TheaterState"]):
|
||||
enemy_convoys=list(self.enemy_convoys),
|
||||
enemy_shipping=list(self.enemy_shipping),
|
||||
enemy_ships=list(self.enemy_ships),
|
||||
enemy_garrisons=dataclasses.replace(self.enemy_garrisons),
|
||||
enemy_garrisons={
|
||||
cp: dataclasses.replace(g) for cp, g in self.enemy_garrisons.items()
|
||||
},
|
||||
oca_targets=list(self.oca_targets),
|
||||
strike_targets=list(self.strike_targets),
|
||||
enemy_barcaps=list(self.enemy_barcaps),
|
||||
@ -110,6 +119,7 @@ class TheaterState(WorldState["TheaterState"]):
|
||||
finder = ObjectiveFinder(game, player)
|
||||
auto_stance = game.settings.automate_front_line_stance
|
||||
auto_ato = game.settings.auto_ato_behavior is not AutoAtoBehavior.Disabled
|
||||
ordered_capturable_points = finder.prioritized_unisolated_points()
|
||||
return TheaterState(
|
||||
player=player,
|
||||
stance_automation_enabled=auto_stance,
|
||||
@ -126,7 +136,9 @@ class TheaterState(WorldState["TheaterState"]):
|
||||
enemy_convoys=list(finder.convoys()),
|
||||
enemy_shipping=list(finder.cargo_ships()),
|
||||
enemy_ships=list(finder.enemy_ships()),
|
||||
enemy_garrisons=Garrisons.from_theater(game.theater, not player),
|
||||
enemy_garrisons={
|
||||
cp: Garrisons.for_control_point(cp) for cp in ordered_capturable_points
|
||||
},
|
||||
oca_targets=list(finder.oca_targets(min_aircraft=20)),
|
||||
strike_targets=list(finder.strike_targets()),
|
||||
enemy_barcaps=list(game.theater.control_points_for(not player)),
|
||||
|
||||
@ -342,9 +342,13 @@ class ControlPoint(MissionTarget, ABC):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def is_global(self) -> bool:
|
||||
def is_isolated(self) -> bool:
|
||||
return not self.connected_points
|
||||
|
||||
@property
|
||||
def is_global(self) -> bool:
|
||||
return self.is_isolated
|
||||
|
||||
def transitive_connected_friendly_points(
|
||||
self, seen: Optional[Set[ControlPoint]] = None
|
||||
) -> List[ControlPoint]:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user