mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
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:
parent
36d9dda500
commit
5be92cd75e
@ -40,3 +40,24 @@ class UnitClass(Enum):
|
|||||||
TANK = "Tank"
|
TANK = "Tank"
|
||||||
TELAR = "TELAR"
|
TELAR = "TELAR"
|
||||||
TRACK_RADAR = "TrackRadar"
|
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,
|
||||||
|
]
|
||||||
|
|||||||
@ -118,22 +118,17 @@ class LuaGenerator:
|
|||||||
else lua_data.get_or_create_item("RedAA")
|
else lua_data.get_or_create_item("RedAA")
|
||||||
)
|
)
|
||||||
for ground_object in cp.ground_objects:
|
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:
|
||||||
for g in ground_object.groups:
|
threat_range = g.max_threat_range()
|
||||||
threat_range = ground_object.threat_range(g)
|
|
||||||
|
|
||||||
if not threat_range:
|
if not threat_range:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
aa_item = coalition_object.add_item()
|
aa_item = coalition_object.add_item()
|
||||||
aa_item.add_key_value("name", ground_object.name)
|
aa_item.add_key_value("name", ground_object.name)
|
||||||
aa_item.add_key_value("range", str(threat_range.meters))
|
aa_item.add_key_value("range", str(threat_range.meters))
|
||||||
aa_item.add_key_value(
|
aa_item.add_key_value("positionX", str(ground_object.position.x))
|
||||||
"positionX", str(ground_object.position.x)
|
aa_item.add_key_value("positionY", str(ground_object.position.y))
|
||||||
)
|
|
||||||
aa_item.add_key_value(
|
|
||||||
"positionY", str(ground_object.position.y)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate IADS Lua Item
|
# Generate IADS Lua Item
|
||||||
iads_object = lua_data.add_item("IADS")
|
iads_object = lua_data.add_item("IADS")
|
||||||
|
|||||||
@ -30,14 +30,8 @@ class TgoJs(BaseModel):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def for_tgo(tgo: TheaterGroundObject) -> TgoJs:
|
def for_tgo(tgo: TheaterGroundObject) -> TgoJs:
|
||||||
if not tgo.might_have_aa:
|
threat_ranges = [group.max_threat_range().meters for group in tgo.groups]
|
||||||
threat_ranges = []
|
detection_ranges = [group.max_detection_range().meters for group in tgo.groups]
|
||||||
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
|
|
||||||
]
|
|
||||||
return TgoJs(
|
return TgoJs(
|
||||||
id=tgo.id,
|
id=tgo.id,
|
||||||
name=tgo.name,
|
name=tgo.name,
|
||||||
|
|||||||
@ -3,13 +3,10 @@ from __future__ import annotations
|
|||||||
import itertools
|
import itertools
|
||||||
import uuid
|
import uuid
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from typing import Type
|
|
||||||
from typing import Any, Iterator, List, Optional, TYPE_CHECKING
|
from typing import Any, Iterator, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
|
|
||||||
from dcs.unittype import VehicleType
|
|
||||||
from dcs.unittype import ShipType
|
|
||||||
from shapely.geometry import Point as ShapelyPoint
|
from shapely.geometry import Point as ShapelyPoint
|
||||||
|
|
||||||
from game.sidc import (
|
from game.sidc import (
|
||||||
@ -25,7 +22,6 @@ from game.sidc import (
|
|||||||
)
|
)
|
||||||
from game.theater.presetlocation import PresetLocation
|
from game.theater.presetlocation import PresetLocation
|
||||||
from .missiontarget import MissionTarget
|
from .missiontarget import MissionTarget
|
||||||
from ..data.radar_db import LAUNCHER_TRACKER_PAIRS, TELARS, TRACK_RADARS
|
|
||||||
from ..utils import Distance, Heading, meters
|
from ..utils import Distance, Heading, meters
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -170,54 +166,29 @@ class TheaterGroundObject(MissionTarget, SidcDescribable, ABC):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_count(self) -> int:
|
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
|
@property
|
||||||
def alive_unit_count(self) -> int:
|
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
|
@property
|
||||||
def might_have_aa(self) -> bool:
|
def has_aa(self) -> bool:
|
||||||
return False
|
"""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
|
@property
|
||||||
def has_live_radar_sam(self) -> bool:
|
def has_live_radar_sam(self) -> bool:
|
||||||
"""Returns True if the ground object contains a unit with working radar SAM."""
|
"""Returns True if the ground object contains a unit with working radar SAM."""
|
||||||
for group in self.groups:
|
return any(g.max_threat_range(radar_only=True) for g 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
|
|
||||||
|
|
||||||
def max_detection_range(self) -> Distance:
|
def max_detection_range(self) -> Distance:
|
||||||
return (
|
"""Calculate the maximum detection range of the ground object"""
|
||||||
max(self.detection_range(g) for g in self.groups)
|
return max((g.max_detection_range() for g in self.groups), default=meters(0))
|
||||||
if self.groups
|
|
||||||
else meters(0)
|
|
||||||
)
|
|
||||||
|
|
||||||
def detection_range(self, group: TheaterGroup) -> Distance:
|
|
||||||
return self._max_range_of_type(group, "detection_range")
|
|
||||||
|
|
||||||
def max_threat_range(self) -> Distance:
|
def max_threat_range(self) -> Distance:
|
||||||
return (
|
"""Calculate the maximum threat range of the ground object"""
|
||||||
max(self.threat_range(g) for g in self.groups) if self.groups else meters(0)
|
return max((g.max_threat_range() for g in self.groups), default=meters(0))
|
||||||
)
|
|
||||||
|
|
||||||
def threat_range(self, group: TheaterGroup, radar_only: bool = False) -> Distance:
|
|
||||||
return self._max_range_of_type(group, "threat_range")
|
|
||||||
|
|
||||||
def threat_poly(self) -> ThreatPoly | None:
|
def threat_poly(self) -> ThreatPoly | None:
|
||||||
if self._threat_poly is None:
|
if self._threat_poly is None:
|
||||||
@ -360,12 +331,6 @@ class BuildingGroundObject(TheaterGroundObject):
|
|||||||
def purchasable(self) -> bool:
|
def purchasable(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def max_threat_range(self) -> Distance:
|
|
||||||
return meters(0)
|
|
||||||
|
|
||||||
def max_detection_range(self) -> Distance:
|
|
||||||
return meters(0)
|
|
||||||
|
|
||||||
|
|
||||||
class NavalGroundObject(TheaterGroundObject, ABC):
|
class NavalGroundObject(TheaterGroundObject, ABC):
|
||||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
@ -375,10 +340,6 @@ class NavalGroundObject(TheaterGroundObject, ABC):
|
|||||||
yield FlightType.ANTISHIP
|
yield FlightType.ANTISHIP
|
||||||
yield from super().mission_types(for_player)
|
yield from super().mission_types(for_player)
|
||||||
|
|
||||||
@property
|
|
||||||
def might_have_aa(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def capturable(self) -> bool:
|
def capturable(self) -> bool:
|
||||||
return False
|
return False
|
||||||
@ -554,36 +515,6 @@ class SamGroundObject(IadsGroundObject):
|
|||||||
if mission_type is not FlightType.DEAD:
|
if mission_type is not FlightType.DEAD:
|
||||||
yield mission_type
|
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
|
@property
|
||||||
def capturable(self) -> bool:
|
def capturable(self) -> bool:
|
||||||
return False
|
return False
|
||||||
@ -642,10 +573,6 @@ class EwrGroundObject(IadsGroundObject):
|
|||||||
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
|
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
|
||||||
return SymbolSet.LAND_EQUIPMENT, LandEquipmentEntity.RADAR
|
return SymbolSet.LAND_EQUIPMENT, LandEquipmentEntity.RADAR
|
||||||
|
|
||||||
@property
|
|
||||||
def might_have_aa(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def capturable(self) -> bool:
|
def capturable(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -2,23 +2,23 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Optional, TYPE_CHECKING, Type
|
from typing import Any, Optional, TYPE_CHECKING, Type
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from dcs.triggers import TriggerZone
|
from dcs.triggers import TriggerZone
|
||||||
from dcs.unittype import ShipType, StaticType, UnitType as DcsUnitType, VehicleType
|
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.groundunittype import GroundUnitType
|
||||||
from game.dcs.shipunittype import ShipUnitType
|
from game.dcs.shipunittype import ShipUnitType
|
||||||
from game.dcs.unittype import UnitType
|
from game.dcs.unittype import UnitType
|
||||||
from game.point_with_heading import PointWithHeading
|
from game.point_with_heading import PointWithHeading
|
||||||
from game.theater.iadsnetwork.iadsrole import IadsRole
|
from game.theater.iadsnetwork.iadsrole import IadsRole
|
||||||
from game.utils import Heading, Distance
|
from game.utils import Heading, Distance, meters
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.layout.layout import LayoutUnit
|
from game.layout.layout import LayoutUnit
|
||||||
from game.sim import GameUpdateEvents
|
from game.sim import GameUpdateEvents
|
||||||
from game.theater import TheaterGroundObject
|
from game.theater.theatergroundobject import TheaterGroundObject
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -91,6 +91,13 @@ class TheaterUnit:
|
|||||||
def is_ship(self) -> bool:
|
def is_ship(self) -> bool:
|
||||||
return issubclass(self.type, ShipType)
|
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
|
@property
|
||||||
def icon(self) -> str:
|
def icon(self) -> str:
|
||||||
return self.type.id
|
return self.type.id
|
||||||
@ -100,6 +107,16 @@ class TheaterUnit:
|
|||||||
# Only let units with UnitType be repairable as we just have prices for them
|
# Only let units with UnitType be repairable as we just have prices for them
|
||||||
return self.unit_type is not None
|
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):
|
class SceneryUnit(TheaterUnit):
|
||||||
"""Special TheaterUnit for handling scenery ground objects"""
|
"""Special TheaterUnit for handling scenery ground objects"""
|
||||||
@ -166,7 +183,41 @@ class TheaterGroup:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def alive_units(self) -> int:
|
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):
|
class IadsGroundGroup(TheaterGroup):
|
||||||
|
|||||||
@ -202,9 +202,9 @@ class ThreatZones:
|
|||||||
"""
|
"""
|
||||||
air_threats = []
|
air_threats = []
|
||||||
air_defenses = []
|
air_defenses = []
|
||||||
for control_point in game.theater.control_points_for(player):
|
for cp in game.theater.control_points_for(player):
|
||||||
air_threats.append(control_point)
|
air_threats.append(cp)
|
||||||
air_defenses.extend(control_point.ground_objects)
|
air_defenses.extend([go for go in cp.ground_objects if go.has_aa])
|
||||||
|
|
||||||
return cls.for_threats(
|
return cls.for_threats(
|
||||||
game.theater, game.faction_for(player).doctrine, air_threats, air_defenses
|
game.theater, game.faction_for(player).doctrine, air_threats, air_defenses
|
||||||
@ -241,17 +241,17 @@ class ThreatZones:
|
|||||||
|
|
||||||
for tgo in air_defenses:
|
for tgo in air_defenses:
|
||||||
for group in tgo.groups:
|
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
|
# Any system with a shorter range than this is not worth
|
||||||
# even avoiding.
|
# even avoiding.
|
||||||
if threat_range > nautical_miles(3):
|
if threat_range > nautical_miles(3):
|
||||||
point = ShapelyPoint(tgo.position.x, tgo.position.y)
|
point = ShapelyPoint(tgo.position.x, tgo.position.y)
|
||||||
threat_zone = point.buffer(threat_range.meters)
|
threat_zone = point.buffer(threat_range.meters)
|
||||||
air_defense_threats.append(threat_zone)
|
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):
|
if radar_threat_range > nautical_miles(3):
|
||||||
point = ShapelyPoint(tgo.position.x, tgo.position.y)
|
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)
|
radar_sam_threats.append(threat_zone)
|
||||||
|
|
||||||
return ThreatZones(
|
return ThreatZones(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user