Cleanup threat and detection range calculation

- Moved logic from TGO to TheaterGroup and Unit, cleanup
- Fixed an issue with wrong radar threat zone calculation
- Correctly handle dead and alive units in threat calculation (dead units are no more threats...)
- Fixed wrong air_defenses threat zone used for planning (now uses aa-capable tgos instead of all tgos for the CP)
- Remove the might_have_aa property from TGOs and actually check if there is any aa-capable unit present (this is needed as with the recent tgo refactor all type of TGOs can also have anti air units if they have some defined in the layout)
This commit is contained in:
RndName 2022-04-19 17:19:12 +02:00
parent 36d9dda500
commit 5be92cd75e
6 changed files with 104 additions and 116 deletions

View File

@ -40,3 +40,24 @@ class UnitClass(Enum):
TANK = "Tank"
TELAR = "TELAR"
TRACK_RADAR = "TrackRadar"
# All UnitClasses which can have AntiAir capabilities
ANTI_AIR_UNIT_CLASSES = [
UnitClass.AAA,
UnitClass.AIRCRAFT_CARRIER,
UnitClass.CRUISER,
UnitClass.DESTROYER,
UnitClass.EARLY_WARNING_RADAR,
UnitClass.FRIGATE,
UnitClass.HELICOPTER_CARRIER,
UnitClass.LAUNCHER,
UnitClass.MANPAD,
UnitClass.SEARCH_RADAR,
UnitClass.SEARCH_TRACK_RADAR,
UnitClass.SPECIALIZED_RADAR,
UnitClass.SHORAD,
UnitClass.SUBMARINE,
UnitClass.TELAR,
UnitClass.TRACK_RADAR,
]

View File

@ -118,22 +118,17 @@ class LuaGenerator:
else lua_data.get_or_create_item("RedAA")
)
for ground_object in cp.ground_objects:
if ground_object.might_have_aa and not ground_object.is_dead:
for g in ground_object.groups:
threat_range = ground_object.threat_range(g)
for g in ground_object.groups:
threat_range = g.max_threat_range()
if not threat_range:
continue
if not threat_range:
continue
aa_item = coalition_object.add_item()
aa_item.add_key_value("name", ground_object.name)
aa_item.add_key_value("range", str(threat_range.meters))
aa_item.add_key_value(
"positionX", str(ground_object.position.x)
)
aa_item.add_key_value(
"positionY", str(ground_object.position.y)
)
aa_item = coalition_object.add_item()
aa_item.add_key_value("name", ground_object.name)
aa_item.add_key_value("range", str(threat_range.meters))
aa_item.add_key_value("positionX", str(ground_object.position.x))
aa_item.add_key_value("positionY", str(ground_object.position.y))
# Generate IADS Lua Item
iads_object = lua_data.add_item("IADS")

View File

@ -30,14 +30,8 @@ class TgoJs(BaseModel):
@staticmethod
def for_tgo(tgo: TheaterGroundObject) -> TgoJs:
if not tgo.might_have_aa:
threat_ranges = []
detection_ranges = []
else:
threat_ranges = [tgo.threat_range(group).meters for group in tgo.groups]
detection_ranges = [
tgo.detection_range(group).meters for group in tgo.groups
]
threat_ranges = [group.max_threat_range().meters for group in tgo.groups]
detection_ranges = [group.max_detection_range().meters for group in tgo.groups]
return TgoJs(
id=tgo.id,
name=tgo.name,

View File

@ -3,13 +3,10 @@ from __future__ import annotations
import itertools
import uuid
from abc import ABC
from typing import Type
from typing import Any, Iterator, List, Optional, TYPE_CHECKING
from dcs.mapping import Point
from dcs.unittype import VehicleType
from dcs.unittype import ShipType
from shapely.geometry import Point as ShapelyPoint
from game.sidc import (
@ -25,7 +22,6 @@ from game.sidc import (
)
from game.theater.presetlocation import PresetLocation
from .missiontarget import MissionTarget
from ..data.radar_db import LAUNCHER_TRACKER_PAIRS, TELARS, TRACK_RADARS
from ..utils import Distance, Heading, meters
if TYPE_CHECKING:
@ -170,54 +166,29 @@ class TheaterGroundObject(MissionTarget, SidcDescribable, ABC):
@property
def unit_count(self) -> int:
return sum([g.unit_count for g in self.groups])
return sum(g.unit_count for g in self.groups)
@property
def alive_unit_count(self) -> int:
return sum([g.alive_units for g in self.groups])
return sum(g.alive_units for g in self.groups)
@property
def might_have_aa(self) -> bool:
return False
def has_aa(self) -> bool:
"""Returns True if the ground object contains a working anti air unit"""
return any(u.alive and u.is_anti_air for u in self.units)
@property
def has_live_radar_sam(self) -> bool:
"""Returns True if the ground object contains a unit with working radar SAM."""
for group in self.groups:
if self.threat_range(group, radar_only=True):
return True
return False
def _max_range_of_type(self, group: TheaterGroup, range_type: str) -> Distance:
if not self.might_have_aa:
return meters(0)
max_range = meters(0)
for u in group.units:
# Some units in pydcs have detection_range/threat_range defined,
# but explicitly set to None.
unit_range = getattr(u.type, range_type, None)
if unit_range is not None:
max_range = max(max_range, meters(unit_range))
return max_range
return any(g.max_threat_range(radar_only=True) for g in self.groups)
def max_detection_range(self) -> Distance:
return (
max(self.detection_range(g) for g in self.groups)
if self.groups
else meters(0)
)
def detection_range(self, group: TheaterGroup) -> Distance:
return self._max_range_of_type(group, "detection_range")
"""Calculate the maximum detection range of the ground object"""
return max((g.max_detection_range() for g in self.groups), default=meters(0))
def max_threat_range(self) -> Distance:
return (
max(self.threat_range(g) for g in self.groups) if self.groups else meters(0)
)
def threat_range(self, group: TheaterGroup, radar_only: bool = False) -> Distance:
return self._max_range_of_type(group, "threat_range")
"""Calculate the maximum threat range of the ground object"""
return max((g.max_threat_range() for g in self.groups), default=meters(0))
def threat_poly(self) -> ThreatPoly | None:
if self._threat_poly is None:
@ -360,12 +331,6 @@ class BuildingGroundObject(TheaterGroundObject):
def purchasable(self) -> bool:
return False
def max_threat_range(self) -> Distance:
return meters(0)
def max_detection_range(self) -> Distance:
return meters(0)
class NavalGroundObject(TheaterGroundObject, ABC):
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
@ -375,10 +340,6 @@ class NavalGroundObject(TheaterGroundObject, ABC):
yield FlightType.ANTISHIP
yield from super().mission_types(for_player)
@property
def might_have_aa(self) -> bool:
return True
@property
def capturable(self) -> bool:
return False
@ -554,36 +515,6 @@ class SamGroundObject(IadsGroundObject):
if mission_type is not FlightType.DEAD:
yield mission_type
@property
def might_have_aa(self) -> bool:
return True
def threat_range(self, group: TheaterGroup, radar_only: bool = False) -> Distance:
max_non_radar = meters(0)
live_trs = set()
max_telar_range = meters(0)
launchers = set()
for unit in group.units:
if not unit.alive or not issubclass(unit.type, VehicleType):
continue
unit_type = unit.type
if unit_type in TRACK_RADARS:
live_trs.add(unit_type)
elif unit_type in TELARS:
max_telar_range = max(max_telar_range, meters(unit_type.threat_range))
elif unit_type in LAUNCHER_TRACKER_PAIRS:
launchers.add(unit_type)
else:
max_non_radar = max(max_non_radar, meters(unit_type.threat_range))
max_tel_range = meters(0)
for launcher in launchers:
if LAUNCHER_TRACKER_PAIRS[launcher] in live_trs:
max_tel_range = max(max_tel_range, meters(launcher.threat_range))
if radar_only:
return max(max_tel_range, max_telar_range)
else:
return max(max_tel_range, max_telar_range, max_non_radar)
@property
def capturable(self) -> bool:
return False
@ -642,10 +573,6 @@ class EwrGroundObject(IadsGroundObject):
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
return SymbolSet.LAND_EQUIPMENT, LandEquipmentEntity.RADAR
@property
def might_have_aa(self) -> bool:
return True
@property
def capturable(self) -> bool:
return False

View File

@ -2,23 +2,23 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Optional, TYPE_CHECKING, Type
from enum import Enum
from dcs.triggers import TriggerZone
from dcs.unittype import ShipType, StaticType, UnitType as DcsUnitType, VehicleType
from game.data.groups import GroupTask
from game.data.radar_db import LAUNCHER_TRACKER_PAIRS, TELARS, TRACK_RADARS
from game.data.units import ANTI_AIR_UNIT_CLASSES
from game.dcs.groundunittype import GroundUnitType
from game.dcs.shipunittype import ShipUnitType
from game.dcs.unittype import UnitType
from game.point_with_heading import PointWithHeading
from game.theater.iadsnetwork.iadsrole import IadsRole
from game.utils import Heading, Distance
from game.utils import Heading, Distance, meters
if TYPE_CHECKING:
from game.layout.layout import LayoutUnit
from game.sim import GameUpdateEvents
from game.theater import TheaterGroundObject
from game.theater.theatergroundobject import TheaterGroundObject
@dataclass
@ -91,6 +91,13 @@ class TheaterUnit:
def is_ship(self) -> bool:
return issubclass(self.type, ShipType)
@property
def is_anti_air(self) -> bool:
return (
self.unit_type is not None
and self.unit_type.unit_class in ANTI_AIR_UNIT_CLASSES
)
@property
def icon(self) -> str:
return self.type.id
@ -100,6 +107,16 @@ class TheaterUnit:
# Only let units with UnitType be repairable as we just have prices for them
return self.unit_type is not None
@property
def detection_range(self) -> Distance:
unit_range = getattr(self.type, "detection_range", None)
return meters(unit_range if unit_range is not None and self.alive else 0)
@property
def threat_range(self) -> Distance:
unit_range = getattr(self.type, "threat_range", None)
return meters(unit_range if unit_range is not None and self.alive else 0)
class SceneryUnit(TheaterUnit):
"""Special TheaterUnit for handling scenery ground objects"""
@ -166,7 +183,41 @@ class TheaterGroup:
@property
def alive_units(self) -> int:
return sum([unit.alive for unit in self.units])
return sum(unit.alive for unit in self.units)
def max_detection_range(self) -> Distance:
"""Calculate the maximum detection range of the TheaterGroup"""
ranges = (u.detection_range for u in self.units if u.is_anti_air)
return max(ranges, default=meters(0))
def max_threat_range(self, radar_only: bool = False) -> Distance:
"""Calculate the maximum threat range of the TheaterGroup.
This also checks for Launcher and Tracker Pairs and if they are functioning or not. Allows to also use only radar emitting units for the calculation with the parameter."""
max_non_radar = meters(0)
max_telar_range = meters(0)
max_tel_range = meters(0)
live_trs = set()
launchers: dict[Type[VehicleType], Distance] = {}
for unit in self.units:
if not unit.alive or not unit.is_anti_air:
continue
if unit.type in TRACK_RADARS:
live_trs.add(unit.type)
elif unit.type in TELARS:
max_telar_range = max(max_telar_range, unit.threat_range)
elif (
issubclass(unit.type, VehicleType)
and unit.type in LAUNCHER_TRACKER_PAIRS
):
launchers[unit.type] = unit.threat_range
else:
max_non_radar = max(max_non_radar, unit.threat_range)
for launcher, threat_range in launchers.items():
if LAUNCHER_TRACKER_PAIRS[launcher] in live_trs:
max_tel_range = max(max_tel_range, threat_range)
if radar_only:
return max(max_tel_range, max_telar_range)
return max(max_tel_range, max_telar_range, max_non_radar)
class IadsGroundGroup(TheaterGroup):

View File

@ -202,9 +202,9 @@ class ThreatZones:
"""
air_threats = []
air_defenses = []
for control_point in game.theater.control_points_for(player):
air_threats.append(control_point)
air_defenses.extend(control_point.ground_objects)
for cp in game.theater.control_points_for(player):
air_threats.append(cp)
air_defenses.extend([go for go in cp.ground_objects if go.has_aa])
return cls.for_threats(
game.theater, game.faction_for(player).doctrine, air_threats, air_defenses
@ -241,17 +241,17 @@ class ThreatZones:
for tgo in air_defenses:
for group in tgo.groups:
threat_range = tgo.threat_range(group)
threat_range = group.max_threat_range()
# Any system with a shorter range than this is not worth
# even avoiding.
if threat_range > nautical_miles(3):
point = ShapelyPoint(tgo.position.x, tgo.position.y)
threat_zone = point.buffer(threat_range.meters)
air_defense_threats.append(threat_zone)
radar_threat_range = tgo.threat_range(group, radar_only=True)
radar_threat_range = group.max_threat_range(radar_only=True)
if radar_threat_range > nautical_miles(3):
point = ShapelyPoint(tgo.position.x, tgo.position.y)
threat_zone = point.buffer(threat_range.meters)
threat_zone = point.buffer(radar_threat_range.meters)
radar_sam_threats.append(threat_zone)
return ThreatZones(