Improve detection of functional radar SAMs.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/1109
This commit is contained in:
Dan Albert 2021-05-23 11:28:43 -07:00
parent eae0d6be94
commit ddd6e7d18f
6 changed files with 126 additions and 16 deletions

View File

@ -22,7 +22,38 @@ from dcs.ships import (
)
from dcs.vehicles import AirDefence
UNITS_WITH_RADAR = [
TELARS = {
AirDefence.SAM_SA_19_Tunguska_Grison,
AirDefence.SAM_SA_11_Buk_Gadfly_Fire_Dome_TEL,
AirDefence.SAM_SA_8_Osa_Gecko_TEL,
AirDefence.SAM_SA_15_Tor_Gauntlet,
AirDefence.SAM_Roland_ADS,
}
TRACK_RADARS = {
AirDefence.SAM_SA_6_Kub_Straight_Flush_STR,
AirDefence.SAM_SA_3_S_125_Low_Blow_TR,
AirDefence.SAM_SA_10_S_300_Grumble_Flap_Lid_TR,
AirDefence.SAM_Hawk_TR__AN_MPQ_46,
AirDefence.SAM_Patriot_STR,
AirDefence.SAM_SA_2_S_75_Fan_Song_TR,
AirDefence.SAM_Rapier_Blindfire_TR,
AirDefence.HQ_7_Self_Propelled_STR,
}
LAUNCHER_TRACKER_PAIRS = {
AirDefence.SAM_SA_6_Kub_Gainful_TEL: AirDefence.SAM_SA_6_Kub_Straight_Flush_STR,
AirDefence.SAM_SA_3_S_125_Goa_LN: AirDefence.SAM_SA_3_S_125_Low_Blow_TR,
AirDefence.SAM_SA_10_S_300_Grumble_TEL_D: AirDefence.SAM_SA_10_S_300_Grumble_Flap_Lid_TR,
AirDefence.SAM_SA_10_S_300_Grumble_TEL_C: AirDefence.SAM_SA_10_S_300_Grumble_Flap_Lid_TR,
AirDefence.SAM_Hawk_LN_M192: AirDefence.SAM_Hawk_TR__AN_MPQ_46,
AirDefence.SAM_Patriot_LN: AirDefence.SAM_Patriot_STR,
AirDefence.SAM_SA_2_S_75_Guideline_LN: AirDefence.SAM_SA_2_S_75_Fan_Song_TR,
AirDefence.SAM_Rapier_LN: AirDefence.SAM_Rapier_Blindfire_TR,
AirDefence.HQ_7_Self_Propelled_LN: AirDefence.HQ_7_Self_Propelled_STR,
}
UNITS_WITH_RADAR = {
# Radars
AirDefence.SAM_SA_19_Tunguska_Grison,
AirDefence.SAM_SA_11_Buk_Gadfly_Fire_Dome_TEL,
@ -74,4 +105,4 @@ UNITS_WITH_RADAR = [
Type_052B_Destroyer,
Type_054A_Frigate,
Type_052C_Destroyer,
]
}

View File

@ -8,9 +8,15 @@ from dcs.mapping import Point
from dcs.triggers import TriggerZone
from dcs.unit import Unit
from dcs.unitgroup import Group
from dcs.unittype import VehicleType
from .. import db
from ..data.radar_db import UNITS_WITH_RADAR
from ..data.radar_db import (
UNITS_WITH_RADAR,
TRACK_RADARS,
TELARS,
LAUNCHER_TRACKER_PAIRS,
)
from ..utils import Distance, meters
if TYPE_CHECKING:
@ -166,14 +172,7 @@ class TheaterGroundObject(MissionTarget):
def detection_range(self, group: Group) -> Distance:
return self._max_range_of_type(group, "detection_range")
def threat_range(self, group: Group) -> Distance:
if not self.detection_range(group):
# For simple SAMs like shilkas, the unit has both a threat and
# detection range. For complex sites like SA-2s, the launcher has a
# threat range and the search/track radars have detection ranges. If
# the site has no detection range it has no radars and can't fire,
# so it's not actually a threat even if it still has launchers.
return meters(0)
def threat_range(self, group: Group, radar_only: bool = False) -> Distance:
return self._max_range_of_type(group, "threat_range")
@property
@ -459,6 +458,38 @@ class SamGroundObject(BaseDefenseGroundObject):
def might_have_aa(self) -> bool:
return True
def threat_range(self, group: Group, 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:
unit_type = db.unit_type_from_name(unit.type)
if unit_type is None or not issubclass(unit_type, VehicleType):
continue
if unit_type in TRACK_RADARS:
live_trs.add(unit_type)
elif unit_type in TELARS:
max_telar_range = max(
max_telar_range, meters(getattr(unit_type, "threat_range", 0))
)
elif unit_type in LAUNCHER_TRACKER_PAIRS:
launchers.add(unit_type)
else:
max_non_radar = max(
max_non_radar, meters(getattr(unit_type, "threat_range", 0))
)
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(getattr(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)
class VehicleGroupGroundObject(BaseDefenseGroundObject):
def __init__(

View File

@ -15,7 +15,6 @@ from shapely.ops import nearest_points, unary_union
from game.theater import ControlPoint
from game.utils import Distance, meters, nautical_miles
from gen import Conflict
from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.flights.flight import Flight
@ -27,9 +26,12 @@ ThreatPoly = Union[MultiPolygon, Polygon]
class ThreatZones:
def __init__(self, airbases: ThreatPoly, air_defenses: ThreatPoly) -> None:
def __init__(
self, airbases: ThreatPoly, air_defenses: ThreatPoly, radar_sam_threats
) -> None:
self.airbases = airbases
self.air_defenses = air_defenses
self.radar_sam_threats = radar_sam_threats
self.all = unary_union([airbases, air_defenses])
def closest_boundary(self, point: DcsPoint) -> DcsPoint:
@ -83,6 +85,20 @@ class ThreatZones:
LineString((self.dcs_to_shapely_point(p.position) for p in flight.points))
)
@singledispatchmethod
def threatened_by_radar_sam(self, target) -> bool:
raise NotImplementedError
@threatened_by_radar_sam.register
def _threatened_by_radar_sam_geom(self, position: BaseGeometry) -> bool:
return self.radar_sam_threats.intersects(position)
@threatened_by_radar_sam.register
def _threatened_by_radar_sam_flight(self, flight: Flight) -> bool:
return self.threatened_by_radar_sam(
LineString((self.dcs_to_shapely_point(p.position) for p in flight.points))
)
@classmethod
def closest_enemy_airbase(
cls, location: ControlPoint, max_distance: Distance
@ -134,6 +150,7 @@ class ThreatZones:
"""
air_threats = []
air_defenses = []
radar_sam_threats = []
for control_point in game.theater.controlpoints:
if control_point.captured != player:
continue
@ -151,9 +168,16 @@ class ThreatZones:
point = ShapelyPoint(tgo.position.x, tgo.position.y)
threat_zone = point.buffer(threat_range.meters)
air_defenses.append(threat_zone)
radar_threat_range = tgo.threat_range(group, 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)
radar_sam_threats.append(threat_zone)
return cls(
airbases=unary_union(air_threats), air_defenses=unary_union(air_defenses)
airbases=unary_union(air_threats),
air_defenses=unary_union(air_defenses),
radar_sam_threats=unary_union(radar_sam_threats),
)
@staticmethod

View File

@ -833,7 +833,7 @@ class CoalitionMissionPlanner:
for flight in builder.package.flights:
if self.threat_zones.threatened_by_aircraft(flight):
threats[EscortType.AirToAir] = True
if self.threat_zones.threatened_by_air_defense(flight):
if self.threat_zones.threatened_by_radar_sam(flight):
threats[EscortType.Sead] = True
return threats

View File

@ -538,17 +538,20 @@ class ThreatZonesJs(QObject):
fullChanged = Signal()
aircraftChanged = Signal()
airDefensesChanged = Signal()
radarSamsChanged = Signal()
def __init__(
self,
full: List[List[LeafletLatLon]],
aircraft: List[List[LeafletLatLon]],
air_defenses: List[List[LeafletLatLon]],
radar_sams: List[List[LeafletLatLon]],
) -> None:
super().__init__()
self._full = full
self._aircraft = aircraft
self._air_defenses = air_defenses
self._radar_sams = radar_sams
@Property(list, notify=fullChanged)
def full(self) -> List[List[LeafletLatLon]]:
@ -562,6 +565,10 @@ class ThreatZonesJs(QObject):
def airDefenses(self) -> List[List[LeafletLatLon]]:
return self._air_defenses
@Property(list, notify=radarSamsChanged)
def radarSams(self) -> List[List[LeafletLatLon]]:
return self._radar_sams
@staticmethod
def polys_to_leaflet(
poly: Union[Polygon, MultiPolygon], theater: ConflictTheater
@ -578,11 +585,12 @@ class ThreatZonesJs(QObject):
cls.polys_to_leaflet(zones.all, theater),
cls.polys_to_leaflet(zones.airbases, theater),
cls.polys_to_leaflet(zones.air_defenses, theater),
cls.polys_to_leaflet(zones.radar_sam_threats, theater),
)
@classmethod
def empty(cls) -> ThreatZonesJs:
return ThreatZonesJs([], [], [])
return ThreatZonesJs([], [], [], [])
class ThreatZoneContainerJs(QObject):

View File

@ -163,10 +163,12 @@ const allFlightPlansLayer = L.layerGroup();
const blueFullThreatZones = L.layerGroup();
const blueAircraftThreatZones = L.layerGroup();
const blueAirDefenseThreatZones = L.layerGroup();
const blueRadarSamThreatZones = L.layerGroup();
const redFullThreatZones = L.layerGroup();
const redAircraftThreatZones = L.layerGroup();
const redAirDefenseThreatZones = L.layerGroup();
const redRadarSamThreatZones = L.layerGroup();
L.control
.groupedLayers(
@ -198,12 +200,14 @@ L.control
Full: blueFullThreatZones,
Aircraft: blueAircraftThreatZones,
"Air Defenses": blueAirDefenseThreatZones,
"Radar SAMs": blueRadarSamThreatZones,
},
"Red Threat Zones": {
Hide: L.layerGroup().addTo(map),
Full: redFullThreatZones,
Aircraft: redAircraftThreatZones,
"Air Defenses": redAirDefenseThreatZones,
"Radar SAMs": redRadarSamThreatZones,
},
},
{
@ -735,9 +739,11 @@ function drawThreatZones() {
blueFullThreatZones.clearLayers();
blueAircraftThreatZones.clearLayers();
blueAirDefenseThreatZones.clearLayers();
blueRadarSamThreatZones.clearLayers();
redFullThreatZones.clearLayers();
redAircraftThreatZones.clearLayers();
redAirDefenseThreatZones.clearLayers();
redRadarSamThreatZones.clearLayers();
_drawThreatZones(game.threatZones.blue.full, blueFullThreatZones, true);
_drawThreatZones(
@ -750,6 +756,11 @@ function drawThreatZones() {
blueAirDefenseThreatZones,
true
);
_drawThreatZones(
game.threatZones.blue.radarSams,
blueRadarSamThreatZones,
true
);
_drawThreatZones(game.threatZones.red.full, redFullThreatZones, false);
_drawThreatZones(
@ -762,6 +773,11 @@ function drawThreatZones() {
redAirDefenseThreatZones,
false
);
_drawThreatZones(
game.threatZones.red.radarSams,
redRadarSamThreatZones,
false
);
}
function drawInitialMap() {