mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
refactor to enum typing and many other fixes fix tests attempt to fix some typescript more typescript fixes more typescript test fixes revert all API changes update to pydcs mypy fixes Use properties to check if player is blue/red/neutral update requirements.txt black -_- bump pydcs and fix mypy add opponent property bump pydcs
225 lines
9.0 KiB
Python
225 lines
9.0 KiB
Python
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
import itertools
|
|
import math
|
|
from collections.abc import Iterator
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from typing import Optional, TYPE_CHECKING, Union, Dict
|
|
|
|
from game.commander.battlepositions import BattlePositions
|
|
from game.commander.objectivefinder import ObjectiveFinder
|
|
from game.db import GameDb
|
|
from game.ground_forces.combat_stance import CombatStance
|
|
from game.htn import WorldState
|
|
from game.profiling import MultiEventTracer
|
|
from game.settings import Settings
|
|
from game.theater import (
|
|
ConflictTheater,
|
|
ControlPoint,
|
|
FrontLine,
|
|
MissionTarget,
|
|
Player,
|
|
)
|
|
from game.theater.theatergroundobject import (
|
|
BuildingGroundObject,
|
|
IadsGroundObject,
|
|
NavalGroundObject,
|
|
TheaterGroundObject,
|
|
VehicleGroupGroundObject,
|
|
)
|
|
from game.threatzones import ThreatZones
|
|
|
|
if TYPE_CHECKING:
|
|
from game import Game
|
|
from game.coalition import Coalition
|
|
from game.transfers import Convoy, CargoShip
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PersistentContext:
|
|
game_db: GameDb
|
|
coalition: Coalition
|
|
theater: ConflictTheater
|
|
turn: int
|
|
now: datetime
|
|
settings: Settings
|
|
tracer: MultiEventTracer
|
|
|
|
|
|
@dataclass
|
|
class TheaterState(WorldState["TheaterState"]):
|
|
context: PersistentContext
|
|
barcaps_needed: dict[ControlPoint, int]
|
|
active_front_lines: list[FrontLine]
|
|
front_line_stances: dict[FrontLine, Optional[CombatStance]]
|
|
vulnerable_front_lines: list[FrontLine]
|
|
aewc_targets: list[MissionTarget]
|
|
refueling_targets: list[MissionTarget]
|
|
recovery_targets: dict[ControlPoint, int]
|
|
enemy_air_defenses: list[IadsGroundObject]
|
|
threatening_air_defenses: list[Union[IadsGroundObject, NavalGroundObject]]
|
|
detecting_air_defenses: list[Union[IadsGroundObject, NavalGroundObject]]
|
|
enemy_convoys: list[Convoy]
|
|
enemy_shipping: list[CargoShip]
|
|
enemy_ships: list[NavalGroundObject]
|
|
enemy_battle_positions: dict[ControlPoint, BattlePositions]
|
|
oca_targets: list[ControlPoint]
|
|
strike_targets: list[TheaterGroundObject]
|
|
enemy_barcaps: list[ControlPoint]
|
|
threat_zones: ThreatZones
|
|
vulnerable_control_points: list[ControlPoint]
|
|
control_point_priority_queue: list[ControlPoint]
|
|
priority_cp: Optional[ControlPoint]
|
|
|
|
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.context.theater,
|
|
self.context.coalition.opponent.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:
|
|
if target in self.threatening_air_defenses:
|
|
self.threatening_air_defenses.remove(target)
|
|
if target in self.detecting_air_defenses:
|
|
self.detecting_air_defenses.remove(target)
|
|
self.enemy_air_defenses.remove(target)
|
|
self._rebuild_threat_zones()
|
|
|
|
def eliminate_ship(self, target: NavalGroundObject) -> None:
|
|
if target in self.threatening_air_defenses:
|
|
self.threatening_air_defenses.remove(target)
|
|
if target in self.detecting_air_defenses:
|
|
self.detecting_air_defenses.remove(target)
|
|
self.enemy_ships.remove(target)
|
|
self._rebuild_threat_zones()
|
|
|
|
def has_battle_position(self, target: VehicleGroupGroundObject) -> bool:
|
|
return target in self.enemy_battle_positions[target.control_point]
|
|
|
|
def eliminate_battle_position(self, target: VehicleGroupGroundObject) -> None:
|
|
self.enemy_battle_positions[target.control_point].eliminate(target)
|
|
|
|
def ammo_dumps_at(
|
|
self, control_point: ControlPoint
|
|
) -> Iterator[BuildingGroundObject]:
|
|
for target in self.strike_targets:
|
|
if target.control_point != control_point:
|
|
continue
|
|
if target.is_ammo_depot:
|
|
assert isinstance(target, BuildingGroundObject)
|
|
yield target
|
|
|
|
def clone(self) -> TheaterState:
|
|
# Do not use copy.deepcopy. Copying every TGO, control point, etc is absurdly
|
|
# expensive.
|
|
return TheaterState(
|
|
context=self.context,
|
|
barcaps_needed=dict(self.barcaps_needed),
|
|
active_front_lines=list(self.active_front_lines),
|
|
front_line_stances=dict(self.front_line_stances),
|
|
vulnerable_front_lines=list(self.vulnerable_front_lines),
|
|
aewc_targets=list(self.aewc_targets),
|
|
refueling_targets=list(self.refueling_targets),
|
|
recovery_targets=dict(self.recovery_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_battle_positions={
|
|
cp: dataclasses.replace(g)
|
|
for cp, g in self.enemy_battle_positions.items()
|
|
},
|
|
oca_targets=list(self.oca_targets),
|
|
strike_targets=list(self.strike_targets),
|
|
enemy_barcaps=list(self.enemy_barcaps),
|
|
threat_zones=self.threat_zones,
|
|
# 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 battle_positions 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,
|
|
detecting_air_defenses=self.detecting_air_defenses,
|
|
vulnerable_control_points=self.vulnerable_control_points,
|
|
control_point_priority_queue=self.control_point_priority_queue,
|
|
priority_cp=self.priority_cp,
|
|
)
|
|
|
|
@classmethod
|
|
def from_game(
|
|
cls, game: Game, player: Player, now: datetime, tracer: MultiEventTracer
|
|
) -> TheaterState:
|
|
coalition = game.coalition_for(player)
|
|
finder = ObjectiveFinder(game, player)
|
|
ordered_capturable_points = finder.prioritized_points()
|
|
|
|
context = PersistentContext(
|
|
game.db,
|
|
coalition,
|
|
game.theater,
|
|
game.turn,
|
|
now,
|
|
game.settings,
|
|
tracer,
|
|
)
|
|
|
|
# Plan enough rounds of CAP that the target has coverage over the expected
|
|
# mission duration.
|
|
mission_duration = game.settings.desired_player_mission_duration.total_seconds()
|
|
barcap_duration = game.settings.desired_barcap_mission_duration.total_seconds()
|
|
barcap_rounds = math.ceil(mission_duration / barcap_duration)
|
|
|
|
battle_postitions: Dict[ControlPoint, BattlePositions] = {
|
|
cp: BattlePositions.for_control_point(cp)
|
|
for cp in ordered_capturable_points
|
|
}
|
|
|
|
vulnerable_control_points = [
|
|
cp
|
|
for cp, bp in battle_postitions.items()
|
|
if not bp.blocking_capture or cp.is_fleet
|
|
]
|
|
|
|
aewc_targets = [cp for cp in finder.friendly_control_points() if cp.is_carrier]
|
|
aewc_targets.append(finder.farthest_friendly_control_point())
|
|
|
|
return TheaterState(
|
|
context=context,
|
|
barcaps_needed={
|
|
cp: 2 * barcap_rounds if cp.is_fleet else barcap_rounds
|
|
for cp in finder.vulnerable_control_points()
|
|
},
|
|
active_front_lines=list(finder.front_lines()),
|
|
front_line_stances={f: None for f in finder.front_lines()},
|
|
vulnerable_front_lines=list(finder.front_lines()),
|
|
aewc_targets=list(aewc_targets),
|
|
refueling_targets=[finder.closest_friendly_control_point()],
|
|
recovery_targets={cp: 0 for cp in finder.friendly_naval_control_points()},
|
|
enemy_air_defenses=list(finder.enemy_air_defenses()),
|
|
threatening_air_defenses=[],
|
|
detecting_air_defenses=[],
|
|
enemy_convoys=list(finder.convoys()),
|
|
enemy_shipping=list(finder.cargo_ships()),
|
|
enemy_ships=list(finder.enemy_ships()),
|
|
enemy_battle_positions=battle_postitions,
|
|
oca_targets=list(
|
|
finder.oca_targets(
|
|
min_aircraft=game.settings.oca_target_autoplanner_min_aircraft_count
|
|
)
|
|
),
|
|
strike_targets=list(finder.strike_targets()),
|
|
enemy_barcaps=list(game.theater.control_points_for(player.opponent)),
|
|
threat_zones=game.threat_zone_for(player.opponent),
|
|
vulnerable_control_points=vulnerable_control_points,
|
|
control_point_priority_queue=ordered_capturable_points,
|
|
priority_cp=(
|
|
ordered_capturable_points[0] if ordered_capturable_points else None
|
|
),
|
|
)
|