mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Prioritize ammo depots when appropriate.
The AI will now prioritize targeting ammo depots if the current deployable enemy forces outnumber the friendly cap by 50% or more.
This commit is contained in:
parent
9568bc7ea6
commit
587034ad03
@ -4,10 +4,13 @@ from dataclasses import dataclass
|
|||||||
from game.commander.tasks.compound.destroyenemygroundunits import (
|
from game.commander.tasks.compound.destroyenemygroundunits import (
|
||||||
DestroyEnemyGroundUnits,
|
DestroyEnemyGroundUnits,
|
||||||
)
|
)
|
||||||
|
from game.commander.tasks.compound.reduceenemyfrontlinecapacity import (
|
||||||
|
ReduceEnemyFrontLineCapacity,
|
||||||
|
)
|
||||||
from game.commander.tasks.primitive.breakthroughattack import BreakthroughAttack
|
from game.commander.tasks.primitive.breakthroughattack import BreakthroughAttack
|
||||||
from game.commander.theaterstate import TheaterState
|
from game.commander.theaterstate import TheaterState
|
||||||
from game.htn import CompoundTask, Method
|
from game.htn import CompoundTask, Method
|
||||||
from game.theater import FrontLine
|
from game.theater import FrontLine, ControlPoint
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@ -17,3 +20,32 @@ class CaptureBase(CompoundTask[TheaterState]):
|
|||||||
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
|
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
|
||||||
yield [BreakthroughAttack(self.front_line, state.context.coalition.player)]
|
yield [BreakthroughAttack(self.front_line, state.context.coalition.player)]
|
||||||
yield [DestroyEnemyGroundUnits(self.front_line)]
|
yield [DestroyEnemyGroundUnits(self.front_line)]
|
||||||
|
if self.worth_destroying_ammo_depots(state):
|
||||||
|
yield [ReduceEnemyFrontLineCapacity(self.enemy_cp(state))]
|
||||||
|
|
||||||
|
def enemy_cp(self, state: TheaterState) -> ControlPoint:
|
||||||
|
return self.front_line.control_point_hostile_to(state.context.coalition.player)
|
||||||
|
|
||||||
|
def units_deployable(self, state: TheaterState, player: bool) -> int:
|
||||||
|
cp = self.front_line.control_point_friendly_to(player)
|
||||||
|
ammo_depots = list(state.ammo_dumps_at(cp))
|
||||||
|
return cp.deployable_front_line_units_with(len(ammo_depots))
|
||||||
|
|
||||||
|
def unit_cap(self, state: TheaterState, player: bool) -> int:
|
||||||
|
cp = self.front_line.control_point_friendly_to(player)
|
||||||
|
ammo_depots = list(state.ammo_dumps_at(cp))
|
||||||
|
return cp.front_line_capacity_with(len(ammo_depots))
|
||||||
|
|
||||||
|
def enemy_has_ammo_dumps(self, state: TheaterState) -> bool:
|
||||||
|
return bool(state.ammo_dumps_at(self.enemy_cp(state)))
|
||||||
|
|
||||||
|
def worth_destroying_ammo_depots(self, state: TheaterState) -> bool:
|
||||||
|
if not self.enemy_has_ammo_dumps(state):
|
||||||
|
return False
|
||||||
|
|
||||||
|
friendly_cap = self.unit_cap(state, state.context.coalition.player)
|
||||||
|
enemy_deployable = self.units_deployable(state, state.context.coalition.player)
|
||||||
|
|
||||||
|
# If the enemy can currently deploy 50% more units than we possibly could, it's
|
||||||
|
# worth killing an ammo depot.
|
||||||
|
return enemy_deployable / friendly_cap > 1.5
|
||||||
|
|||||||
@ -1,19 +1,16 @@
|
|||||||
from collections import Iterator
|
from collections import Iterator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from game.commander.tasks.primitive.aggressiveattack import AggressiveAttack
|
from game.commander.tasks.primitive.strike import PlanStrike
|
||||||
from game.commander.tasks.primitive.cas import PlanCas
|
|
||||||
from game.commander.tasks.primitive.eliminationattack import EliminationAttack
|
|
||||||
from game.commander.theaterstate import TheaterState
|
from game.commander.theaterstate import TheaterState
|
||||||
from game.htn import CompoundTask, Method
|
from game.htn import CompoundTask, Method
|
||||||
from game.theater import FrontLine
|
from game.theater import ControlPoint
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class DestroyEnemyGroundUnits(CompoundTask[TheaterState]):
|
class ReduceEnemyFrontLineCapacity(CompoundTask[TheaterState]):
|
||||||
front_line: FrontLine
|
control_point: ControlPoint
|
||||||
|
|
||||||
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
|
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
|
||||||
yield [EliminationAttack(self.front_line, state.context.coalition.player)]
|
for ammo_dump in state.ammo_dumps_at(self.control_point):
|
||||||
yield [AggressiveAttack(self.front_line, state.context.coalition.player)]
|
yield [PlanStrike(ammo_dump)]
|
||||||
yield [PlanCas(self.front_line)]
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import itertools
|
import itertools
|
||||||
import math
|
import math
|
||||||
|
from collections import Iterator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Any, Union, Optional
|
from typing import TYPE_CHECKING, Any, Union, Optional
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ from game.theater.theatergroundobject import (
|
|||||||
NavalGroundObject,
|
NavalGroundObject,
|
||||||
IadsGroundObject,
|
IadsGroundObject,
|
||||||
VehicleGroupGroundObject,
|
VehicleGroupGroundObject,
|
||||||
|
BuildingGroundObject,
|
||||||
)
|
)
|
||||||
from game.threatzones import ThreatZones
|
from game.threatzones import ThreatZones
|
||||||
from gen.ground_forces.combat_stance import CombatStance
|
from gen.ground_forces.combat_stance import CombatStance
|
||||||
@ -88,6 +90,15 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
def eliminate_garrison(self, target: VehicleGroupGroundObject) -> None:
|
def eliminate_garrison(self, target: VehicleGroupGroundObject) -> None:
|
||||||
self.enemy_garrisons[target.control_point].eliminate(target)
|
self.enemy_garrisons[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:
|
||||||
|
yield 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.
|
||||||
|
|||||||
@ -40,7 +40,11 @@ from gen.ground_forces.combat_stance import CombatStance
|
|||||||
from gen.runways import RunwayAssigner, RunwayData
|
from gen.runways import RunwayAssigner, RunwayData
|
||||||
from .base import Base
|
from .base import Base
|
||||||
from .missiontarget import MissionTarget
|
from .missiontarget import MissionTarget
|
||||||
from .theatergroundobject import GenericCarrierGroundObject, TheaterGroundObject
|
from .theatergroundobject import (
|
||||||
|
GenericCarrierGroundObject,
|
||||||
|
TheaterGroundObject,
|
||||||
|
BuildingGroundObject,
|
||||||
|
)
|
||||||
from ..dcs.aircrafttype import AircraftType
|
from ..dcs.aircrafttype import AircraftType
|
||||||
from ..dcs.groundunittype import GroundUnitType
|
from ..dcs.groundunittype import GroundUnitType
|
||||||
from ..utils import nautical_miles
|
from ..utils import nautical_miles
|
||||||
@ -728,30 +732,47 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def deployable_front_line_units(self) -> int:
|
def deployable_front_line_units(self) -> int:
|
||||||
return min(self.frontline_unit_count_limit, self.base.total_armor)
|
return self.deployable_front_line_units_with(self.active_ammo_depots_count)
|
||||||
|
|
||||||
|
def deployable_front_line_units_with(self, ammo_depot_count: int) -> int:
|
||||||
|
return min(
|
||||||
|
self.front_line_capacity_with(ammo_depot_count), self.base.total_armor
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def front_line_capacity_with(cls, ammo_depot_count: int) -> int:
|
||||||
|
return (
|
||||||
|
FREE_FRONTLINE_UNIT_SUPPLY
|
||||||
|
+ ammo_depot_count * AMMO_DEPOT_FRONTLINE_UNIT_CONTRIBUTION
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def frontline_unit_count_limit(self) -> int:
|
def frontline_unit_count_limit(self) -> int:
|
||||||
return (
|
return self.front_line_capacity_with(self.active_ammo_depots_count)
|
||||||
FREE_FRONTLINE_UNIT_SUPPLY
|
|
||||||
+ self.active_ammo_depots_count * AMMO_DEPOT_FRONTLINE_UNIT_CONTRIBUTION
|
@property
|
||||||
)
|
def all_ammo_depots(self) -> Iterator[BuildingGroundObject]:
|
||||||
|
for tgo in self.connected_objectives:
|
||||||
|
if not tgo.is_ammo_depot:
|
||||||
|
continue
|
||||||
|
assert isinstance(tgo, BuildingGroundObject)
|
||||||
|
yield tgo
|
||||||
|
|
||||||
|
@property
|
||||||
|
def active_ammo_depots(self) -> Iterator[BuildingGroundObject]:
|
||||||
|
for tgo in self.all_ammo_depots:
|
||||||
|
if not tgo.is_dead:
|
||||||
|
yield tgo
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active_ammo_depots_count(self) -> int:
|
def active_ammo_depots_count(self) -> int:
|
||||||
"""Return the number of available ammo depots"""
|
"""Return the number of available ammo depots"""
|
||||||
return len(
|
return len(list(self.active_ammo_depots))
|
||||||
[
|
|
||||||
obj
|
|
||||||
for obj in self.connected_objectives
|
|
||||||
if obj.category == "ammo" and not obj.is_dead
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_ammo_depots_count(self) -> int:
|
def total_ammo_depots_count(self) -> int:
|
||||||
"""Return the number of ammo depots, including dead ones"""
|
"""Return the number of ammo depots, including dead ones"""
|
||||||
return len([obj for obj in self.connected_objectives if obj.category == "ammo"])
|
return len(list(self.all_ammo_depots))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def strike_targets(self) -> Sequence[Union[MissionTarget, Unit]]:
|
def strike_targets(self) -> Sequence[Union[MissionTarget, Unit]]:
|
||||||
|
|||||||
@ -181,6 +181,10 @@ class TheaterGroundObject(MissionTarget, Generic[GroupT]):
|
|||||||
def threat_range(self, group: GroupT, radar_only: bool = False) -> Distance:
|
def threat_range(self, group: GroupT, radar_only: bool = False) -> Distance:
|
||||||
return self._max_range_of_type(group, "threat_range")
|
return self._max_range_of_type(group, "threat_range")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_ammo_depot(self) -> bool:
|
||||||
|
return self.category == "ammo"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_factory(self) -> bool:
|
def is_factory(self) -> bool:
|
||||||
return self.category == "factory"
|
return self.category == "factory"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user