From 7e4390d7435572d6c2d0ecf1f1072e26e2b78edd Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 12 Jul 2021 16:59:49 -0700 Subject: [PATCH] Improve prioritization of garrison targeting. Garrison groups should be preferred with the following priority: 1. Groups blocking base capture 2. Groups at bases connected to an active front line 3. Rear guard units Previously they were being prioritized based on the distance to the closest friendy control point, which is similar to this but an aggressively placed carrier could throw it off. --- game/commander/garrisons.py | 63 +++++++++++++++++++ game/commander/objectivefinder.py | 8 --- .../tasks/compound/attackgarrisons.py | 2 +- game/commander/tasks/primitive/bai.py | 2 +- game/commander/theaterstate.py | 9 +-- 5 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 game/commander/garrisons.py diff --git a/game/commander/garrisons.py b/game/commander/garrisons.py new file mode 100644 index 00000000..27b43de2 --- /dev/null +++ b/game/commander/garrisons.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from collections import Iterator +from dataclasses import dataclass + +from game.theater import ConflictTheater +from game.theater.theatergroundobject import VehicleGroupGroundObject +from game.utils import meters, nautical_miles + + +@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: + """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. + """ + blocking = [] + defending = [] + reserves = [] + for cp in theater.control_points_for(player_owned): + garrisons = [ + tgo + for tgo in cp.ground_objects + if isinstance(tgo, VehicleGroupGroundObject) + ] + if not cp.has_active_frontline: + reserves.extend(garrisons) + continue + + for garrison in garrisons: + # Not sure what distance DCS uses, but assuming it's about 2NM since + # that's roughly the distance of the circle on the map. + if meters(garrison.distance_to(cp)) < nautical_miles(2): + blocking.append(garrison) + else: + defending.append(garrison) + + return Garrisons(blocking, defending, reserves) diff --git a/game/commander/objectivefinder.py b/game/commander/objectivefinder.py index e2bab894..a565fc86 100644 --- a/game/commander/objectivefinder.py +++ b/game/commander/objectivefinder.py @@ -65,14 +65,6 @@ class ObjectiveFinder: yield ground_object - def threatening_vehicle_groups(self) -> Iterator[VehicleGroupGroundObject]: - """Iterates over enemy vehicle groups near friendly control points. - - Groups are sorted by their closest proximity to any friendly control - point (airfield or fleet). - """ - return self._targets_by_range(self.enemy_vehicle_groups()) - def enemy_ships(self) -> Iterator[NavalGroundObject]: for cp in self.enemy_control_points(): for ground_object in cp.ground_objects: diff --git a/game/commander/tasks/compound/attackgarrisons.py b/game/commander/tasks/compound/attackgarrisons.py index f8281597..89a0943c 100644 --- a/game/commander/tasks/compound/attackgarrisons.py +++ b/game/commander/tasks/compound/attackgarrisons.py @@ -7,5 +7,5 @@ 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: + for garrison in state.enemy_garrisons.in_priority_order: yield [PlanBai(garrison)] diff --git a/game/commander/tasks/primitive/bai.py b/game/commander/tasks/primitive/bai.py index c0dc328c..bbbabb96 100644 --- a/game/commander/tasks/primitive/bai.py +++ b/game/commander/tasks/primitive/bai.py @@ -17,7 +17,7 @@ class PlanBai(PackagePlanningTask[VehicleGroupGroundObject]): return self.target_area_preconditions_met(state) def apply_effects(self, state: TheaterState) -> None: - state.enemy_garrisons.remove(self.target) + state.enemy_garrisons.eliminate(self.target) def propose_flights(self, doctrine: Doctrine) -> None: self.propose_flight(FlightType.BAI, 2, doctrine.mission_ranges.offensive) diff --git a/game/commander/theaterstate.py b/game/commander/theaterstate.py index c1508e34..a206b6e7 100644 --- a/game/commander/theaterstate.py +++ b/game/commander/theaterstate.py @@ -1,16 +1,17 @@ from __future__ import annotations +import dataclasses import itertools from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Union +from game.commander.garrisons import Garrisons from game.commander.objectivefinder import ObjectiveFinder from game.data.doctrine import Doctrine from game.htn import WorldState from game.theater import ControlPoint, FrontLine, MissionTarget from game.theater.theatergroundobject import ( TheaterGroundObject, - VehicleGroupGroundObject, NavalGroundObject, IadsGroundObject, ) @@ -32,7 +33,7 @@ class TheaterState(WorldState["TheaterState"]): enemy_convoys: list[Convoy] enemy_shipping: list[CargoShip] enemy_ships: list[NavalGroundObject] - enemy_garrisons: list[VehicleGroupGroundObject] + enemy_garrisons: Garrisons oca_targets: list[ControlPoint] strike_targets: list[TheaterGroundObject[Any]] enemy_barcaps: list[ControlPoint] @@ -69,7 +70,7 @@ class TheaterState(WorldState["TheaterState"]): enemy_convoys=list(self.enemy_convoys), enemy_shipping=list(self.enemy_shipping), enemy_ships=list(self.enemy_ships), - enemy_garrisons=list(self.enemy_garrisons), + enemy_garrisons=dataclasses.replace(self.enemy_garrisons), oca_targets=list(self.oca_targets), strike_targets=list(self.strike_targets), enemy_barcaps=list(self.enemy_barcaps), @@ -97,7 +98,7 @@ class TheaterState(WorldState["TheaterState"]): enemy_convoys=list(finder.convoys()), enemy_shipping=list(finder.cargo_ships()), enemy_ships=list(finder.enemy_ships()), - enemy_garrisons=list(finder.threatening_vehicle_groups()), + enemy_garrisons=Garrisons.from_theater(game.theater, not player), 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)),