mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
This alters the DEAD task planning to be the *least* preferred task, but prevents other tasks from being planned unless they are excepted to be clear of air defenses first. Even so, missions are a guaranteed success so those other missions will still get SEAD escorts if there's potential for a SAM in the area. This means that air defenses that are not protecting a more useful target (like a convoy, armor column, building, etc) will no longer be considered by the mission planner. This isn't *quite* right since we currently only check the target area for air defenses rather than the entire flight plan, so there's a chance that we ignore IADS that have threatened ingress points (though that's mostly solved by the flight plan layout). This also is still slightly limited because it's not checking for aircraft availability at this stage yet, so we may aggressively plan missions that we should be skipping unless we can guarantee that the DEAD mission was planned. However, that's not new behavior.
107 lines
4.6 KiB
Python
107 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
import itertools
|
|
from dataclasses import dataclass
|
|
from typing import TYPE_CHECKING, Any, Union
|
|
|
|
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,
|
|
)
|
|
from game.threatzones import ThreatZones
|
|
from game.transfers import Convoy, CargoShip
|
|
|
|
if TYPE_CHECKING:
|
|
from game import Game
|
|
|
|
|
|
@dataclass
|
|
class TheaterState(WorldState["TheaterState"]):
|
|
vulnerable_control_points: list[ControlPoint]
|
|
vulnerable_front_lines: list[FrontLine]
|
|
aewc_targets: list[MissionTarget]
|
|
refueling_targets: list[MissionTarget]
|
|
enemy_air_defenses: list[IadsGroundObject]
|
|
threatening_air_defenses: list[Union[IadsGroundObject, NavalGroundObject]]
|
|
enemy_convoys: list[Convoy]
|
|
enemy_shipping: list[CargoShip]
|
|
enemy_ships: list[NavalGroundObject]
|
|
enemy_garrisons: list[VehicleGroupGroundObject]
|
|
oca_targets: list[ControlPoint]
|
|
strike_targets: list[TheaterGroundObject[Any]]
|
|
enemy_barcaps: list[ControlPoint]
|
|
threat_zones: ThreatZones
|
|
opposing_doctrine: Doctrine
|
|
|
|
def _rebuild_threat_zones(self) -> None:
|
|
"""Recreates the theater's threat zones based on the current planned state."""
|
|
self.threat_zones = ThreatZones.for_threats(
|
|
self.opposing_doctrine,
|
|
barcap_locations=self.enemy_barcaps,
|
|
air_defenses=itertools.chain(self.enemy_air_defenses, self.enemy_ships),
|
|
)
|
|
|
|
def eliminate_air_defense(self, target: IadsGroundObject) -> None:
|
|
self.threatening_air_defenses.remove(target)
|
|
self.enemy_air_defenses.remove(target)
|
|
self._rebuild_threat_zones()
|
|
|
|
def eliminate_ship(self, target: NavalGroundObject) -> None:
|
|
self.threatening_air_defenses.remove(target)
|
|
self.enemy_ships.remove(target)
|
|
self._rebuild_threat_zones()
|
|
|
|
def clone(self) -> TheaterState:
|
|
# Do not use copy.deepcopy. Copying every TGO, control point, etc is absurdly
|
|
# expensive.
|
|
return TheaterState(
|
|
vulnerable_control_points=list(self.vulnerable_control_points),
|
|
vulnerable_front_lines=list(self.vulnerable_front_lines),
|
|
aewc_targets=list(self.aewc_targets),
|
|
refueling_targets=list(self.refueling_targets),
|
|
enemy_air_defenses=list(self.enemy_air_defenses),
|
|
enemy_convoys=list(self.enemy_convoys),
|
|
enemy_shipping=list(self.enemy_shipping),
|
|
enemy_ships=list(self.enemy_ships),
|
|
enemy_garrisons=list(self.enemy_garrisons),
|
|
oca_targets=list(self.oca_targets),
|
|
strike_targets=list(self.strike_targets),
|
|
enemy_barcaps=list(self.enemy_barcaps),
|
|
threat_zones=self.threat_zones,
|
|
opposing_doctrine=self.opposing_doctrine,
|
|
# Persistent properties are not copied. These are a way for failed subtasks
|
|
# to communicate requirements to other tasks. For example, the task to
|
|
# attack enemy garrisons might fail because the target area has IADS
|
|
# protection. In that case, the preconditions of PlanBai would fail, but
|
|
# would add the IADS that prevented it from being planned to the list of
|
|
# IADS threats so that DegradeIads will consider it a threat later.
|
|
threatening_air_defenses=self.threatening_air_defenses,
|
|
)
|
|
|
|
@classmethod
|
|
def from_game(cls, game: Game, player: bool) -> TheaterState:
|
|
finder = ObjectiveFinder(game, player)
|
|
return TheaterState(
|
|
vulnerable_control_points=list(finder.vulnerable_control_points()),
|
|
vulnerable_front_lines=list(finder.front_lines()),
|
|
aewc_targets=[finder.farthest_friendly_control_point()],
|
|
refueling_targets=[finder.closest_friendly_control_point()],
|
|
enemy_air_defenses=list(finder.enemy_air_defenses()),
|
|
threatening_air_defenses=[],
|
|
enemy_convoys=list(finder.convoys()),
|
|
enemy_shipping=list(finder.cargo_ships()),
|
|
enemy_ships=list(finder.enemy_ships()),
|
|
enemy_garrisons=list(finder.threatening_vehicle_groups()),
|
|
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)),
|
|
threat_zones=game.threat_zone_for(not player),
|
|
opposing_doctrine=game.faction_for(not player).doctrine,
|
|
)
|