Attack detecting radars with low priority.

IADS that are in detection range (but not attack range) of missions will
be targeted at very low priority. These will typically only be planned
when no other targets are in range.
This commit is contained in:
Dan Albert 2021-07-12 17:33:45 -07:00
parent 78514b6c2e
commit c0cc5657a7
5 changed files with 62 additions and 26 deletions

View File

@ -14,7 +14,6 @@ from game.theater import (
Airfield,
)
from game.theater.theatergroundobject import (
VehicleGroupGroundObject,
NavalGroundObject,
BuildingGroundObject,
IadsGroundObject,
@ -50,18 +49,6 @@ class ObjectiveFinder:
if isinstance(ground_object, IadsGroundObject):
yield ground_object
def enemy_vehicle_groups(self) -> Iterator[VehicleGroupGroundObject]:
"""Iterates over all enemy vehicle groups."""
for cp in self.enemy_control_points():
for ground_object in cp.ground_objects:
if not isinstance(ground_object, VehicleGroupGroundObject):
continue
if ground_object.is_dead:
continue
yield ground_object
def enemy_ships(self) -> Iterator[NavalGroundObject]:
for cp in self.enemy_control_points():
for ground_object in cp.ground_objects:

View File

@ -1,16 +1,24 @@
from collections import Iterator
from typing import Union
from game.commander.tasks.primitive.antiship import PlanAntiShip
from game.commander.tasks.primitive.dead import PlanDead
from game.commander.theaterstate import TheaterState
from game.htn import CompoundTask, Method
from game.theater.theatergroundobject import IadsGroundObject
from game.theater.theatergroundobject import IadsGroundObject, NavalGroundObject
class DegradeIads(CompoundTask[TheaterState]):
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
for air_defense in state.threatening_air_defenses:
if isinstance(air_defense, IadsGroundObject):
yield [PlanDead(air_defense)]
else:
yield [PlanAntiShip(air_defense)]
yield [self.plan_against(air_defense)]
for detector in state.detecting_air_defenses:
yield [self.plan_against(detector)]
@staticmethod
def plan_against(
target: Union[IadsGroundObject, NavalGroundObject]
) -> Union[PlanDead, PlanAntiShip]:
if isinstance(target, IadsGroundObject):
return PlanDead(target)
return PlanAntiShip(target)

View File

@ -4,6 +4,7 @@ import itertools
import operator
from abc import abstractmethod
from dataclasses import dataclass, field
from enum import unique, IntEnum, auto
from typing import TYPE_CHECKING, Optional, Generic, TypeVar, Iterator, Union
from game.commander.missionproposals import ProposedFlight, EscortType, ProposedMission
@ -23,6 +24,12 @@ if TYPE_CHECKING:
MissionTargetT = TypeVar("MissionTargetT", bound=MissionTarget)
@unique
class RangeType(IntEnum):
Detection = auto()
Threat = auto()
# TODO: Refactor so that we don't need to call up to the mission planner.
# Bypass type checker due to https://github.com/python/mypy/issues/5374
@dataclass # type: ignore
@ -75,8 +82,8 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
EscortType.AirToAir,
)
def iter_iads_threats(
self, state: TheaterState
def iter_iads_ranges(
self, state: TheaterState, range_type: RangeType
) -> Iterator[Union[IadsGroundObject, NavalGroundObject]]:
target_ranges: list[
tuple[Union[IadsGroundObject, NavalGroundObject], Distance]
@ -86,15 +93,21 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
] = itertools.chain(state.enemy_air_defenses, state.enemy_ships)
for target in all_iads:
distance = meters(target.distance_to(self.target))
threat_range = target.max_threat_range()
if not threat_range:
if range_type is RangeType.Detection:
target_range = target.max_detection_range()
elif range_type is RangeType.Threat:
target_range = target.max_threat_range()
else:
raise ValueError(f"Unknown RangeType: {range_type}")
if not target_range:
continue
# IADS out of range of our target area will have a positive
# distance_to_threat and should be pruned. The rest have a decreasing
# distance_to_threat as overlap increases. The most negative distance has
# the greatest coverage of the target and should be treated as the highest
# priority threat.
distance_to_threat = distance - threat_range
distance_to_threat = distance - target_range
if distance_to_threat > meters(0):
continue
target_ranges.append((target, distance_to_threat))
@ -104,11 +117,27 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
for target, _range in target_ranges:
yield target
def iter_detecting_iads(
self, state: TheaterState
) -> Iterator[Union[IadsGroundObject, NavalGroundObject]]:
return self.iter_iads_ranges(state, RangeType.Detection)
def iter_iads_threats(
self, state: TheaterState
) -> Iterator[Union[IadsGroundObject, NavalGroundObject]]:
return self.iter_iads_ranges(state, RangeType.Threat)
def target_area_preconditions_met(
self, state: TheaterState, ignore_iads: bool = False
) -> bool:
"""Checks if the target area has been cleared of threats."""
threatened = False
# Non-blocking, but analyzed so we can pick detectors worth eliminating.
for detector in self.iter_detecting_iads(state):
if detector not in state.detecting_air_defenses:
state.detecting_air_defenses.append(detector)
if not ignore_iads:
for iads_threat in self.iter_iads_threats(state):
threatened = True

View File

@ -13,7 +13,10 @@ from gen.flights.flight import FlightType
@dataclass
class PlanDead(PackagePlanningTask[IadsGroundObject]):
def preconditions_met(self, state: TheaterState) -> bool:
if self.target not in state.threatening_air_defenses:
if (
self.target not in state.threatening_air_defenses
and self.target not in state.detecting_air_defenses
):
return False
return self.target_area_preconditions_met(state, ignore_iads=True)

View File

@ -30,6 +30,7 @@ class TheaterState(WorldState["TheaterState"]):
refueling_targets: list[MissionTarget]
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]
@ -49,12 +50,18 @@ class TheaterState(WorldState["TheaterState"]):
)
def eliminate_air_defense(self, target: IadsGroundObject) -> None:
self.threatening_air_defenses.remove(target)
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:
self.threatening_air_defenses.remove(target)
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()
@ -83,6 +90,7 @@ class TheaterState(WorldState["TheaterState"]):
# 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,
)
@classmethod
@ -95,6 +103,7 @@ class TheaterState(WorldState["TheaterState"]):
refueling_targets=[finder.closest_friendly_control_point()],
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()),