dcs_liberation/qt_ui/displayoptions.py
Dan Albert bff905fae5 Use navmeshes to improve TARCAP flight plans.
Started with TARCAP because they're simple, but will follow and extend
this to the other flight plans next.

This works by building navigation meshes (navmeshes) of the theater
based on the threat regions. A navmesh is created for each faction to
allow the unique pathing around each side's threats. Navmeshes are built
such that there are nav edges around threat zones to allow the planner
to pick waypoints that (slightly) route around threats before
approaching the target.

Using the navmesh, routes are found using A*. Performance appears
adequate, and could probably be improved with a cache if needed since
the small number of origin points means many flights will share portions
of their flight paths.

This adds a few visual debugging tools to the map. They're disabled by
default, but changing the local `debug` variable in `DisplayOptions` to
`True` will make them appear in the display options menu. These are:

* Display navmeshes (red and blue). Displaying either navmesh will draw
  each navmesh polygon on the map view and highlight the mesh that
  contains the cursor. Neighbors are indicated by a small yellow line
  pointing from the center of the polygon's edge/vertext that is shared
  with its neighbor toward the centroid of the zone.
* Shortest path from control point to mouse location. The first control
  point for the selected faction is arbitrarily selected, and the
  shortest path from that control point to the mouse cursor will be
  drawn on the map.
* TARCAP plan near mouse location. A TARCAP will be planned from the
  faction's first control point to the target nearest the mouse cursor.

https://github.com/Khopa/dcs_liberation/issues/292
2020-12-23 17:09:34 -08:00

113 lines
4.2 KiB
Python

"""Visibility options for the game map."""
from dataclasses import dataclass, field
from typing import Iterator, Optional, Union
@dataclass
class DisplayRule:
name: str
_value: bool
debug_only: bool = field(default=False)
@property
def menu_text(self) -> str:
return self.name
@property
def value(self) -> bool:
return self._value
@value.setter
def value(self, value: bool) -> None:
from qt_ui.widgets.map.QLiberationMap import QLiberationMap
self._value = value
if QLiberationMap.instance is not None:
QLiberationMap.instance.reload_scene()
QLiberationMap.instance.update()
def __bool__(self) -> bool:
return self.value
class DisplayGroup:
def __init__(self, name: Optional[str], debug_only: bool = False) -> None:
self.name = name
self.debug_only = debug_only
def __iter__(self) -> Iterator[DisplayRule]:
# Python 3.6 enforces that __dict__ is order preserving by default.
for value in self.__dict__.values():
if isinstance(value, DisplayRule):
yield value
class FlightPathOptions(DisplayGroup):
def __init__(self) -> None:
super().__init__("Flight Paths")
self.hide = DisplayRule("Hide Flight Paths", False)
self.only_selected = DisplayRule("Show Selected Flight Path", False)
self.all = DisplayRule("Show All Flight Paths", True)
class ThreatZoneOptions(DisplayGroup):
def __init__(self, coalition_name: str) -> None:
super().__init__(f"{coalition_name} Threat Zones")
self.none = DisplayRule(
f"Hide {coalition_name.lower()} threat zones", True)
self.all = DisplayRule(
f"Show full {coalition_name.lower()} threat zones", False)
self.aircraft = DisplayRule(
f"Show {coalition_name.lower()} aircraft threat tones", False)
self.air_defenses = DisplayRule(
f"Show {coalition_name.lower()} air defenses threat zones", False)
class NavMeshOptions(DisplayGroup):
def __init__(self) -> None:
super().__init__("Navmeshes", debug_only=True)
self.hide = DisplayRule("DEBUG Hide Navmeshes", True)
self.blue_navmesh = DisplayRule("DEBUG Show blue navmesh", False)
self.red_navmesh = DisplayRule("DEBUG Show red navmesh", False)
class PathDebugOptions(DisplayGroup):
def __init__(self) -> None:
super().__init__("Shortest paths", debug_only=True)
self.hide = DisplayRule("DEBUG Hide paths", True)
self.shortest_path = DisplayRule("DEBUG Show shortest path", False)
self.blue_tarcap = DisplayRule("DEBUG Show blue TARCAP plan", False)
self.red_tarcap = DisplayRule("DEBUG Show red TARCAP plan", False)
class DisplayOptions:
ground_objects = DisplayRule("Ground Objects", True)
control_points = DisplayRule("Control Points", True)
lines = DisplayRule("Lines", True)
sam_ranges = DisplayRule("Ally SAM Threat Range", False)
enemy_sam_ranges = DisplayRule("Enemy SAM Threat Range", True)
detection_range = DisplayRule("SAM Detection Range", False)
map_poly = DisplayRule("Map Polygon Debug Mode", False)
waypoint_info = DisplayRule("Waypoint Information", True)
culling = DisplayRule("Display Culling Zones", False)
flight_paths = FlightPathOptions()
actual_frontline_pos = DisplayRule("Display Actual Frontline Location",
False)
blue_threat_zones = ThreatZoneOptions("Blue")
red_threat_zones = ThreatZoneOptions("Red")
navmeshes = NavMeshOptions()
path_debug = PathDebugOptions()
@classmethod
def menu_items(cls) -> Iterator[Union[DisplayGroup, DisplayRule]]:
debug = False # Set to True to enable debug options.
# Python 3.6 enforces that __dict__ is order preserving by default.
for value in cls.__dict__.values():
if isinstance(value, DisplayRule):
if value.debug_only and not debug:
continue
yield value
elif isinstance(value, DisplayGroup):
if value.debug_only and not debug:
continue
yield value