From a6dc3d2affadaa2ee1472bf8af92d7a1c5db8799 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 26 Dec 2020 15:52:36 -0800 Subject: [PATCH] Handle threat/detection per group. Some SAMs have multiple groups (such as an SA-10 group with its accompanying SA-15 and SA-19 groups). This shows each group's threat and detection separately on the map, and also makes it so that an SA-10 with dead radars will no longer contribute to the threat zone just because the shilka next to it still has a functioning radar. https://github.com/Khopa/dcs_liberation/issues/647 Fixes https://github.com/Khopa/dcs_liberation/issues/672 --- changelog.md | 1 + game/theater/theatergroundobject.py | 35 +++++++++++++---------------- game/threatzones.py | 15 +++++++------ qt_ui/widgets/map/QLiberationMap.py | 12 +++++----- 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/changelog.md b/changelog.md index 3db324c2..ded88a57 100644 --- a/changelog.md +++ b/changelog.md @@ -15,6 +15,7 @@ Saves from 2.3 are not compatible with 2.4. * **[Balance]** Opfor now gains income using the same rules as the player, significantly increasing their income relative to the player for most campaigns. * **[Economy]** FOBs generate only $10M per turn (previously $20M like airbases). * **[Economy]** Carriers and off-map spawns generate no income (previously $20M like airbases). +* **[UI]** Multi-SAM objectives now show threat and detection rings per group. # 2.3.3 diff --git a/game/theater/theatergroundobject.py b/game/theater/theatergroundobject.py index c89f41fc..1322510b 100644 --- a/game/theater/theatergroundobject.py +++ b/game/theater/theatergroundobject.py @@ -157,39 +157,36 @@ class TheaterGroundObject(MissionTarget): return True return False - def _max_range_of_type(self, range_type: str) -> Distance: + def _max_range_of_type(self, group: Group, range_type: str) -> Distance: if not self.might_have_aa: return meters(0) max_range = meters(0) - for group in self.groups: - for u in group.units: - unit = db.unit_type_from_name(u.type) - if unit is None: - logging.error(f"Unknown unit type {u.type}") - continue + for u in group.units: + unit = db.unit_type_from_name(u.type) + if unit is None: + logging.error(f"Unknown unit type {u.type}") + continue - # Some units in pydcs have detection_range/threat_range defined, - # but explicitly set to None. - unit_range = getattr(unit, range_type, None) - if unit_range is not None: - max_range = max(max_range, meters(unit_range)) + # Some units in pydcs have detection_range/threat_range defined, + # but explicitly set to None. + unit_range = getattr(unit, range_type, None) + if unit_range is not None: + max_range = max(max_range, meters(unit_range)) return max_range - @property - def detection_range(self) -> Distance: - return self._max_range_of_type("detection_range") + def detection_range(self, group: Group) -> Distance: + return self._max_range_of_type(group, "detection_range") - @property - def threat_range(self) -> Distance: - if not self.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) - return self._max_range_of_type("threat_range") + return self._max_range_of_type(group, "threat_range") class BuildingGroundObject(TheaterGroundObject): diff --git a/game/threatzones.py b/game/threatzones.py index 61874e14..9acfa5b9 100644 --- a/game/threatzones.py +++ b/game/threatzones.py @@ -95,13 +95,14 @@ class ThreatZones: airbases.append(point.buffer(cap_threat_range.meters)) for tgo in control_point.ground_objects: - threat_range = tgo.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_defenses.append(threat_zone) + for group in tgo.groups: + threat_range = tgo.threat_range(group) + # 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_defenses.append(threat_zone) return cls( airbases=unary_union(airbases), diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index 70b1099c..ca93f744 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -28,6 +28,7 @@ from PySide2.QtWidgets import ( from dcs import Point from dcs.planes import F_16C_50 from dcs.mapping import point_from_heading +from dcs.unitgroup import Group from shapely.geometry import ( LineString, MultiPolygon, @@ -36,7 +37,7 @@ from shapely.geometry import ( ) import qt_ui.uiconstants as CONST -from game import Game, db +from game import Game from game.navmesh import NavMesh from game.theater import ControlPoint, Enum from game.theater.conflicttheater import FrontLine, ReferencePoint @@ -446,10 +447,10 @@ class QLiberationMap(QGraphicsView): return ((DisplayOptions.sam_ranges and cp.captured) or (DisplayOptions.enemy_sam_ranges and not cp.captured)) - def draw_threat_range(self, scene: QGraphicsScene, ground_object: TheaterGroundObject, cp: ControlPoint) -> None: + def draw_threat_range(self, scene: QGraphicsScene, group: Group, ground_object: TheaterGroundObject, cp: ControlPoint) -> None: go_pos = self._transform_point(ground_object.position) - detection_range = ground_object.detection_range - threat_range = ground_object.threat_range + detection_range = ground_object.detection_range(group) + threat_range = ground_object.threat_range(group) if threat_range: threat_pos = self._transform_point( ground_object.position + Point(threat_range.meters, @@ -482,7 +483,8 @@ class QLiberationMap(QGraphicsView): should_display = self.should_display_ground_objects_at(cp) if ground_object.might_have_aa and should_display: - self.draw_threat_range(scene, ground_object, cp) + for group in ground_object.groups: + self.draw_threat_range(scene, group, ground_object, cp) added_objects.append(ground_object.obj_name) def reload_scene(self):