mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
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
99 lines
3.5 KiB
Python
99 lines
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
from functools import singledispatchmethod
|
|
from typing import TYPE_CHECKING, Union
|
|
|
|
from dcs.mapping import Point as DcsPoint
|
|
from shapely.geometry import (
|
|
LineString,
|
|
MultiPolygon,
|
|
Point as ShapelyPoint,
|
|
Polygon,
|
|
)
|
|
from shapely.geometry.base import BaseGeometry
|
|
from shapely.ops import unary_union
|
|
|
|
from game.utils import nautical_miles
|
|
from gen.flights.flight import Flight
|
|
|
|
if TYPE_CHECKING:
|
|
from game import Game
|
|
|
|
|
|
ThreatPoly = Union[MultiPolygon, Polygon]
|
|
|
|
|
|
class ThreatZones:
|
|
def __init__(self, airbases: ThreatPoly, air_defenses: ThreatPoly) -> None:
|
|
self.airbases = airbases
|
|
self.air_defenses = air_defenses
|
|
self.all = unary_union([airbases, air_defenses])
|
|
|
|
def threatened(self, position: BaseGeometry) -> bool:
|
|
return self.all.intersects(position)
|
|
|
|
def path_threatened(self, a: DcsPoint, b: DcsPoint) -> bool:
|
|
return self.threatened(LineString(
|
|
[self.dcs_to_shapely_point(a), self.dcs_to_shapely_point(b)]))
|
|
|
|
@singledispatchmethod
|
|
def threatened_by_aircraft(self, target) -> bool:
|
|
raise NotImplementedError
|
|
|
|
@threatened_by_aircraft.register
|
|
def _threatened_by_aircraft_geom(self, position: BaseGeometry) -> bool:
|
|
return self.airbases.intersects(position)
|
|
|
|
@threatened_by_aircraft.register
|
|
def _threatened_by_aircraft_flight(self, flight: Flight) -> bool:
|
|
return self.threatened_by_aircraft(LineString((
|
|
self.dcs_to_shapely_point(p.position) for p in flight.points
|
|
)))
|
|
|
|
@singledispatchmethod
|
|
def threatened_by_air_defense(self, target) -> bool:
|
|
raise NotImplementedError
|
|
|
|
@threatened_by_air_defense.register
|
|
def _threatened_by_air_defense_geom(self, position: BaseGeometry) -> bool:
|
|
return self.air_defenses.intersects(position)
|
|
|
|
@threatened_by_air_defense.register
|
|
def _threatened_by_air_defense_flight(self, flight: Flight) -> bool:
|
|
return self.threatened_by_air_defense(LineString((
|
|
self.dcs_to_shapely_point(p.position) for p in flight.points
|
|
)))
|
|
|
|
@classmethod
|
|
def for_faction(cls, game: Game, player: bool) -> ThreatZones:
|
|
opposing_doctrine = game.faction_for(not player).doctrine
|
|
|
|
airbases = []
|
|
air_defenses = []
|
|
for control_point in game.theater.controlpoints:
|
|
if control_point.captured != player:
|
|
continue
|
|
if control_point.runway_is_operational():
|
|
point = ShapelyPoint(control_point.position.x,
|
|
control_point.position.y)
|
|
cap_threat_range = (opposing_doctrine.cap_max_distance_from_cp +
|
|
opposing_doctrine.cap_engagement_range)
|
|
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)
|
|
|
|
return cls(
|
|
airbases=unary_union(airbases),
|
|
air_defenses=unary_union(air_defenses)
|
|
)
|
|
|
|
@staticmethod
|
|
def dcs_to_shapely_point(point: DcsPoint) -> ShapelyPoint:
|
|
return ShapelyPoint(point.x, point.y) |