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 collections import Iterator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from game.theater import ConflictTheater, ControlPoint
|
from game.theater import ControlPoint
|
||||||
from game.theater.theatergroundobject import VehicleGroupGroundObject
|
from game.theater.theatergroundobject import VehicleGroupGroundObject
|
||||||
from game.utils import meters, nautical_miles
|
from game.utils import meters
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Garrisons:
|
class Garrisons:
|
||||||
blocking_capture: list[VehicleGroupGroundObject]
|
blocking_capture: list[VehicleGroupGroundObject]
|
||||||
defending_front_line: list[VehicleGroupGroundObject]
|
defending_front_line: list[VehicleGroupGroundObject]
|
||||||
reserves: list[VehicleGroupGroundObject]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def in_priority_order(self) -> Iterator[VehicleGroupGroundObject]:
|
def in_priority_order(self) -> Iterator[VehicleGroupGroundObject]:
|
||||||
yield from self.blocking_capture
|
yield from self.blocking_capture
|
||||||
yield from self.defending_front_line
|
yield from self.defending_front_line
|
||||||
yield from self.reserves
|
|
||||||
|
|
||||||
def eliminate(self, garrison: VehicleGroupGroundObject) -> None:
|
def eliminate(self, garrison: VehicleGroupGroundObject) -> None:
|
||||||
if garrison in self.blocking_capture:
|
if garrison in self.blocking_capture:
|
||||||
self.blocking_capture.remove(garrison)
|
self.blocking_capture.remove(garrison)
|
||||||
if garrison in self.defending_front_line:
|
if garrison in self.defending_front_line:
|
||||||
self.defending_front_line.remove(garrison)
|
self.defending_front_line.remove(garrison)
|
||||||
if garrison in self.reserves:
|
|
||||||
self.reserves.remove(garrison)
|
|
||||||
|
|
||||||
def __contains__(self, item: VehicleGroupGroundObject) -> bool:
|
def __contains__(self, item: VehicleGroupGroundObject) -> bool:
|
||||||
return item in self.in_priority_order
|
return item in self.in_priority_order
|
||||||
|
|
||||||
@classmethod
|
@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.
|
"""Categorize garrison groups based on target priority.
|
||||||
|
|
||||||
Any garrisons blocking base capture are the highest priority, followed by other
|
Any garrisons blocking base capture are the highest priority.
|
||||||
garrisons at front-line bases, and finally any garrisons in reserve at other
|
|
||||||
bases.
|
|
||||||
"""
|
"""
|
||||||
blocking = []
|
blocking = []
|
||||||
defending = []
|
defending = []
|
||||||
reserves = []
|
garrisons = [
|
||||||
for cp in theater.control_points_for(player_owned):
|
tgo
|
||||||
garrisons = [
|
for tgo in control_point.ground_objects
|
||||||
tgo
|
if isinstance(tgo, VehicleGroupGroundObject) and not tgo.is_dead
|
||||||
for tgo in cp.ground_objects
|
]
|
||||||
if isinstance(tgo, VehicleGroupGroundObject) and not tgo.is_dead
|
for garrison in garrisons:
|
||||||
]
|
if (
|
||||||
if not cp.has_active_frontline:
|
meters(garrison.distance_to(control_point))
|
||||||
reserves.extend(garrisons)
|
< ControlPoint.CAPTURE_DISTANCE
|
||||||
continue
|
):
|
||||||
|
blocking.append(garrison)
|
||||||
|
else:
|
||||||
|
defending.append(garrison)
|
||||||
|
|
||||||
for garrison in garrisons:
|
return Garrisons(blocking, defending)
|
||||||
if meters(garrison.distance_to(cp)) < ControlPoint.CAPTURE_DISTANCE:
|
|
||||||
blocking.append(garrison)
|
|
||||||
else:
|
|
||||||
defending.append(garrison)
|
|
||||||
|
|
||||||
return Garrisons(blocking, defending, reserves)
|
|
||||||
|
|||||||
@ -223,17 +223,18 @@ class ObjectiveFinder:
|
|||||||
if not c.is_friendly(self.is_player)
|
if not c.is_friendly(self.is_player)
|
||||||
)
|
)
|
||||||
|
|
||||||
def all_possible_targets(self) -> Iterator[MissionTarget]:
|
def prioritized_unisolated_points(self) -> list[ControlPoint]:
|
||||||
"""Iterates over all possible mission targets in the theater.
|
prioritized = []
|
||||||
|
capturable_later = []
|
||||||
Valid mission targets are control points (airfields and carriers), front
|
for cp in self.game.theater.control_points_for(not self.is_player):
|
||||||
lines, and ground objects (SAM sites, factories, resource extraction
|
if cp.is_isolated:
|
||||||
sites, etc).
|
continue
|
||||||
"""
|
if cp.has_active_frontline:
|
||||||
for cp in self.game.theater.controlpoints:
|
prioritized.append(cp)
|
||||||
yield cp
|
else:
|
||||||
yield from cp.ground_objects
|
capturable_later.append(cp)
|
||||||
yield from self.front_lines()
|
prioritized.extend(self._targets_by_range(capturable_later))
|
||||||
|
return prioritized
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def closest_airfields_to(location: MissionTarget) -> ClosestAirfields:
|
def closest_airfields_to(location: MissionTarget) -> ClosestAirfields:
|
||||||
|
|||||||
@ -7,5 +7,6 @@ from game.htn import CompoundTask, Method
|
|||||||
|
|
||||||
class AttackGarrisons(CompoundTask[TheaterState]):
|
class AttackGarrisons(CompoundTask[TheaterState]):
|
||||||
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
|
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
|
||||||
for garrison in state.enemy_garrisons.in_priority_order:
|
for garrisons in state.enemy_garrisons.values():
|
||||||
yield [PlanBai(garrison)]
|
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:
|
def preconditions_met(self, state: TheaterState) -> bool:
|
||||||
if not super().preconditions_met(state):
|
if not super().preconditions_met(state):
|
||||||
return False
|
return False
|
||||||
if self.target not in state.enemy_garrisons:
|
if not state.has_garrison(self.target):
|
||||||
return False
|
return False
|
||||||
return self.target_area_preconditions_met(state)
|
return self.target_area_preconditions_met(state)
|
||||||
|
|
||||||
def apply_effects(self, state: TheaterState) -> None:
|
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:
|
def propose_flights(self, doctrine: Doctrine) -> None:
|
||||||
self.propose_flight(FlightType.BAI, 2, doctrine.mission_ranges.offensive)
|
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.tasks.frontlinestancetask import FrontLineStanceTask
|
||||||
from game.commander.theaterstate import TheaterState
|
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
|
from gen.ground_forces.combat_stance import CombatStance
|
||||||
|
|
||||||
|
|
||||||
@ -17,20 +14,14 @@ class BreakthroughAttack(FrontLineStanceTask):
|
|||||||
def have_sufficient_front_line_advantage(self) -> bool:
|
def have_sufficient_front_line_advantage(self) -> bool:
|
||||||
return self.ground_force_balance >= 2.0
|
return self.ground_force_balance >= 2.0
|
||||||
|
|
||||||
@property
|
def opposing_garrisons_eliminated(self, state: TheaterState) -> bool:
|
||||||
def opposing_garrisons_eliminated(self) -> bool:
|
garrisons = state.enemy_garrisons[self.enemy_cp]
|
||||||
# TODO: Should operate on TheaterState to account for BAIs planned this turn.
|
return not bool(garrisons.blocking_capture)
|
||||||
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 preconditions_met(self, state: TheaterState) -> bool:
|
def preconditions_met(self, state: TheaterState) -> bool:
|
||||||
if not super().preconditions_met(state):
|
if not super().preconditions_met(state):
|
||||||
return False
|
return False
|
||||||
return self.opposing_garrisons_eliminated
|
return self.opposing_garrisons_eliminated(state)
|
||||||
|
|
||||||
def apply_effects(self, state: TheaterState) -> None:
|
def apply_effects(self, state: TheaterState) -> None:
|
||||||
super().apply_effects(state)
|
super().apply_effects(state)
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from game.theater.theatergroundobject import (
|
|||||||
TheaterGroundObject,
|
TheaterGroundObject,
|
||||||
NavalGroundObject,
|
NavalGroundObject,
|
||||||
IadsGroundObject,
|
IadsGroundObject,
|
||||||
|
VehicleGroupGroundObject,
|
||||||
)
|
)
|
||||||
from game.threatzones import ThreatZones
|
from game.threatzones import ThreatZones
|
||||||
from game.transfers import Convoy, CargoShip
|
from game.transfers import Convoy, CargoShip
|
||||||
@ -41,7 +42,7 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
enemy_convoys: list[Convoy]
|
enemy_convoys: list[Convoy]
|
||||||
enemy_shipping: list[CargoShip]
|
enemy_shipping: list[CargoShip]
|
||||||
enemy_ships: list[NavalGroundObject]
|
enemy_ships: list[NavalGroundObject]
|
||||||
enemy_garrisons: Garrisons
|
enemy_garrisons: dict[ControlPoint, Garrisons]
|
||||||
oca_targets: list[ControlPoint]
|
oca_targets: list[ControlPoint]
|
||||||
strike_targets: list[TheaterGroundObject[Any]]
|
strike_targets: list[TheaterGroundObject[Any]]
|
||||||
enemy_barcaps: list[ControlPoint]
|
enemy_barcaps: list[ControlPoint]
|
||||||
@ -72,6 +73,12 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
self.enemy_ships.remove(target)
|
self.enemy_ships.remove(target)
|
||||||
self._rebuild_threat_zones()
|
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:
|
def clone(self) -> TheaterState:
|
||||||
# Do not use copy.deepcopy. Copying every TGO, control point, etc is absurdly
|
# Do not use copy.deepcopy. Copying every TGO, control point, etc is absurdly
|
||||||
# expensive.
|
# expensive.
|
||||||
@ -89,7 +96,9 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
enemy_convoys=list(self.enemy_convoys),
|
enemy_convoys=list(self.enemy_convoys),
|
||||||
enemy_shipping=list(self.enemy_shipping),
|
enemy_shipping=list(self.enemy_shipping),
|
||||||
enemy_ships=list(self.enemy_ships),
|
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),
|
oca_targets=list(self.oca_targets),
|
||||||
strike_targets=list(self.strike_targets),
|
strike_targets=list(self.strike_targets),
|
||||||
enemy_barcaps=list(self.enemy_barcaps),
|
enemy_barcaps=list(self.enemy_barcaps),
|
||||||
@ -110,6 +119,7 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
finder = ObjectiveFinder(game, player)
|
finder = ObjectiveFinder(game, player)
|
||||||
auto_stance = game.settings.automate_front_line_stance
|
auto_stance = game.settings.automate_front_line_stance
|
||||||
auto_ato = game.settings.auto_ato_behavior is not AutoAtoBehavior.Disabled
|
auto_ato = game.settings.auto_ato_behavior is not AutoAtoBehavior.Disabled
|
||||||
|
ordered_capturable_points = finder.prioritized_unisolated_points()
|
||||||
return TheaterState(
|
return TheaterState(
|
||||||
player=player,
|
player=player,
|
||||||
stance_automation_enabled=auto_stance,
|
stance_automation_enabled=auto_stance,
|
||||||
@ -126,7 +136,9 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
enemy_convoys=list(finder.convoys()),
|
enemy_convoys=list(finder.convoys()),
|
||||||
enemy_shipping=list(finder.cargo_ships()),
|
enemy_shipping=list(finder.cargo_ships()),
|
||||||
enemy_ships=list(finder.enemy_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)),
|
oca_targets=list(finder.oca_targets(min_aircraft=20)),
|
||||||
strike_targets=list(finder.strike_targets()),
|
strike_targets=list(finder.strike_targets()),
|
||||||
enemy_barcaps=list(game.theater.control_points_for(not player)),
|
enemy_barcaps=list(game.theater.control_points_for(not player)),
|
||||||
|
|||||||
@ -342,9 +342,13 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_global(self) -> bool:
|
def is_isolated(self) -> bool:
|
||||||
return not self.connected_points
|
return not self.connected_points
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_global(self) -> bool:
|
||||||
|
return self.is_isolated
|
||||||
|
|
||||||
def transitive_connected_friendly_points(
|
def transitive_connected_friendly_points(
|
||||||
self, seen: Optional[Set[ControlPoint]] = None
|
self, seen: Optional[Set[ControlPoint]] = None
|
||||||
) -> List[ControlPoint]:
|
) -> List[ControlPoint]:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user