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
This commit is contained in:
Dan Albert 2020-12-26 15:52:36 -08:00
parent d946a9e526
commit a6dc3d2aff
4 changed files with 32 additions and 31 deletions

View File

@ -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. * **[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]** FOBs generate only $10M per turn (previously $20M like airbases).
* **[Economy]** Carriers and off-map spawns generate no income (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 # 2.3.3

View File

@ -157,39 +157,36 @@ class TheaterGroundObject(MissionTarget):
return True return True
return False 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: if not self.might_have_aa:
return meters(0) return meters(0)
max_range = meters(0) max_range = meters(0)
for group in self.groups: for u in group.units:
for u in group.units: unit = db.unit_type_from_name(u.type)
unit = db.unit_type_from_name(u.type) if unit is None:
if unit is None: logging.error(f"Unknown unit type {u.type}")
logging.error(f"Unknown unit type {u.type}") continue
continue
# Some units in pydcs have detection_range/threat_range defined, # Some units in pydcs have detection_range/threat_range defined,
# but explicitly set to None. # but explicitly set to None.
unit_range = getattr(unit, range_type, None) unit_range = getattr(unit, range_type, None)
if unit_range is not None: if unit_range is not None:
max_range = max(max_range, meters(unit_range)) max_range = max(max_range, meters(unit_range))
return max_range return max_range
@property def detection_range(self, group: Group) -> Distance:
def detection_range(self) -> Distance: return self._max_range_of_type(group, "detection_range")
return self._max_range_of_type("detection_range")
@property def threat_range(self, group: Group) -> Distance:
def threat_range(self) -> Distance: if not self.detection_range(group):
if not self.detection_range:
# For simple SAMs like shilkas, the unit has both a threat and # For simple SAMs like shilkas, the unit has both a threat and
# detection range. For complex sites like SA-2s, the launcher has a # detection range. For complex sites like SA-2s, the launcher has a
# threat range and the search/track radars have detection ranges. If # 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, # 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. # so it's not actually a threat even if it still has launchers.
return meters(0) return meters(0)
return self._max_range_of_type("threat_range") return self._max_range_of_type(group, "threat_range")
class BuildingGroundObject(TheaterGroundObject): class BuildingGroundObject(TheaterGroundObject):

View File

@ -95,13 +95,14 @@ class ThreatZones:
airbases.append(point.buffer(cap_threat_range.meters)) airbases.append(point.buffer(cap_threat_range.meters))
for tgo in control_point.ground_objects: for tgo in control_point.ground_objects:
threat_range = tgo.threat_range for group in tgo.groups:
# Any system with a shorter range than this is not worth even threat_range = tgo.threat_range(group)
# avoiding. # Any system with a shorter range than this is not worth
if threat_range > nautical_miles(3): # even avoiding.
point = ShapelyPoint(tgo.position.x, tgo.position.y) if threat_range > nautical_miles(3):
threat_zone = point.buffer(threat_range.meters) point = ShapelyPoint(tgo.position.x, tgo.position.y)
air_defenses.append(threat_zone) threat_zone = point.buffer(threat_range.meters)
air_defenses.append(threat_zone)
return cls( return cls(
airbases=unary_union(airbases), airbases=unary_union(airbases),

View File

@ -28,6 +28,7 @@ from PySide2.QtWidgets import (
from dcs import Point from dcs import Point
from dcs.planes import F_16C_50 from dcs.planes import F_16C_50
from dcs.mapping import point_from_heading from dcs.mapping import point_from_heading
from dcs.unitgroup import Group
from shapely.geometry import ( from shapely.geometry import (
LineString, LineString,
MultiPolygon, MultiPolygon,
@ -36,7 +37,7 @@ from shapely.geometry import (
) )
import qt_ui.uiconstants as CONST import qt_ui.uiconstants as CONST
from game import Game, db from game import Game
from game.navmesh import NavMesh from game.navmesh import NavMesh
from game.theater import ControlPoint, Enum from game.theater import ControlPoint, Enum
from game.theater.conflicttheater import FrontLine, ReferencePoint from game.theater.conflicttheater import FrontLine, ReferencePoint
@ -446,10 +447,10 @@ class QLiberationMap(QGraphicsView):
return ((DisplayOptions.sam_ranges and cp.captured) or return ((DisplayOptions.sam_ranges and cp.captured) or
(DisplayOptions.enemy_sam_ranges and not cp.captured)) (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) go_pos = self._transform_point(ground_object.position)
detection_range = ground_object.detection_range detection_range = ground_object.detection_range(group)
threat_range = ground_object.threat_range threat_range = ground_object.threat_range(group)
if threat_range: if threat_range:
threat_pos = self._transform_point( threat_pos = self._transform_point(
ground_object.position + Point(threat_range.meters, ground_object.position + Point(threat_range.meters,
@ -482,7 +483,8 @@ class QLiberationMap(QGraphicsView):
should_display = self.should_display_ground_objects_at(cp) should_display = self.should_display_ground_objects_at(cp)
if ground_object.might_have_aa and should_display: 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) added_objects.append(ground_object.obj_name)
def reload_scene(self): def reload_scene(self):