Add threat zone drawing for the new map.

https://github.com/dcs-liberation/dcs_liberation/issues/1097
This commit is contained in:
Dan Albert 2021-05-23 12:24:52 -07:00
parent 5e68dbe1ca
commit eae0d6be94
2 changed files with 172 additions and 18 deletions

View File

@ -2,13 +2,13 @@ from __future__ import annotations
import logging import logging
from datetime import timedelta from datetime import timedelta
from typing import List, Optional, Tuple from typing import List, Optional, Tuple, Union
from PySide2.QtCore import Property, QObject, Signal, Slot from PySide2.QtCore import Property, QObject, Signal, Slot
from dcs import Point from dcs import Point
from dcs.unit import Unit from dcs.unit import Unit
from dcs.vehicles import vehicle_map from dcs.vehicles import vehicle_map
from shapely.geometry import LineString, Point as ShapelyPoint, Polygon from shapely.geometry import LineString, Point as ShapelyPoint, Polygon, MultiPolygon
from game import Game, db from game import Game, db
from game.factions.faction import Faction from game.factions.faction import Faction
@ -19,9 +19,8 @@ from game.theater import (
TheaterGroundObject, TheaterGroundObject,
FrontLine, FrontLine,
LatLon, LatLon,
Airfield,
Carrier,
) )
from game.threatzones import ThreatZones
from game.transfers import MultiGroupTransport, TransportMap from game.transfers import MultiGroupTransport, TransportMap
from game.utils import meters, nautical_miles from game.utils import meters, nautical_miles
from gen.ato import AirTaskingOrder from gen.ato import AirTaskingOrder
@ -519,7 +518,7 @@ class FlightJs(QObject):
return self._selected return self._selected
@Property(list, notify=commitBoundaryChanged) @Property(list, notify=commitBoundaryChanged)
def commitBoundary(self) -> Optional[List[LeafletLatLon]]: def commitBoundary(self) -> List[LeafletLatLon]:
if not isinstance(self.flight.flight_plan, PatrollingFlightPlan): if not isinstance(self.flight.flight_plan, PatrollingFlightPlan):
return [] return []
start = self.flight.flight_plan.patrol_start start = self.flight.flight_plan.patrol_start
@ -535,6 +534,75 @@ class FlightJs(QObject):
return shapely_poly_to_leaflet_points(bubble, self.theater) return shapely_poly_to_leaflet_points(bubble, self.theater)
class ThreatZonesJs(QObject):
fullChanged = Signal()
aircraftChanged = Signal()
airDefensesChanged = Signal()
def __init__(
self,
full: List[List[LeafletLatLon]],
aircraft: List[List[LeafletLatLon]],
air_defenses: List[List[LeafletLatLon]],
) -> None:
super().__init__()
self._full = full
self._aircraft = aircraft
self._air_defenses = air_defenses
@Property(list, notify=fullChanged)
def full(self) -> List[List[LeafletLatLon]]:
return self._full
@Property(list, notify=aircraftChanged)
def aircraft(self) -> List[List[LeafletLatLon]]:
return self._aircraft
@Property(list, notify=airDefensesChanged)
def airDefenses(self) -> List[List[LeafletLatLon]]:
return self._air_defenses
@staticmethod
def polys_to_leaflet(
poly: Union[Polygon, MultiPolygon], theater: ConflictTheater
) -> List[List[LeafletLatLon]]:
if isinstance(poly, MultiPolygon):
polys = poly.geoms
else:
polys = [poly]
return [shapely_poly_to_leaflet_points(poly, theater) for poly in polys]
@classmethod
def from_zones(cls, zones: ThreatZones, theater: ConflictTheater) -> ThreatZonesJs:
return ThreatZonesJs(
cls.polys_to_leaflet(zones.all, theater),
cls.polys_to_leaflet(zones.airbases, theater),
cls.polys_to_leaflet(zones.air_defenses, theater),
)
@classmethod
def empty(cls) -> ThreatZonesJs:
return ThreatZonesJs([], [], [])
class ThreatZoneContainerJs(QObject):
blueChanged = Signal()
redChanged = Signal()
def __init__(self, blue: ThreatZonesJs, red: ThreatZonesJs) -> None:
super().__init__()
self._blue = blue
self._red = red
@Property(ThreatZonesJs, notify=blueChanged)
def blue(self) -> ThreatZonesJs:
return self._blue
@Property(ThreatZonesJs, notify=redChanged)
def red(self) -> ThreatZonesJs:
return self._red
class MapModel(QObject): class MapModel(QObject):
cleared = Signal() cleared = Signal()
@ -544,6 +612,7 @@ class MapModel(QObject):
supplyRoutesChanged = Signal() supplyRoutesChanged = Signal()
flightsChanged = Signal() flightsChanged = Signal()
frontLinesChanged = Signal() frontLinesChanged = Signal()
threatZonesChanged = Signal()
def __init__(self, game_model: GameModel) -> None: def __init__(self, game_model: GameModel) -> None:
super().__init__() super().__init__()
@ -554,6 +623,9 @@ class MapModel(QObject):
self._supply_routes = [] self._supply_routes = []
self._flights = [] self._flights = []
self._front_lines = [] self._front_lines = []
self._threat_zones = ThreatZoneContainerJs(
ThreatZonesJs.empty(), ThreatZonesJs.empty()
)
self._selected_flight_index: Optional[Tuple[int, int]] = None self._selected_flight_index: Optional[Tuple[int, int]] = None
GameUpdateSignal.get_instance().game_loaded.connect(self.on_game_load) GameUpdateSignal.get_instance().game_loaded.connect(self.on_game_load)
GameUpdateSignal.get_instance().flight_paths_changed.connect(self.reset_atos) GameUpdateSignal.get_instance().flight_paths_changed.connect(self.reset_atos)
@ -571,6 +643,9 @@ class MapModel(QObject):
self._ground_objects = [] self._ground_objects = []
self._flights = [] self._flights = []
self._front_lines = [] self._front_lines = []
self._threat_zones = ThreatZoneContainerJs(
ThreatZonesJs.empty(), ThreatZonesJs.empty()
)
self.cleared.emit() self.cleared.emit()
def set_package_selection(self, index: int) -> None: def set_package_selection(self, index: int) -> None:
@ -614,6 +689,7 @@ class MapModel(QObject):
self.reset_routes() self.reset_routes()
self.reset_atos() self.reset_atos()
self.reset_front_lines() self.reset_front_lines()
self.reset_threat_zones()
def on_game_load(self, game: Optional[Game]) -> None: def on_game_load(self, game: Optional[Game]) -> None:
if game is not None: if game is not None:
@ -737,6 +813,21 @@ class MapModel(QObject):
def frontLines(self) -> List[FrontLineJs]: def frontLines(self) -> List[FrontLineJs]:
return self._front_lines return self._front_lines
def reset_threat_zones(self) -> None:
self._threat_zones = ThreatZoneContainerJs(
ThreatZonesJs.from_zones(
self.game.threat_zone_for(player=True), self.game.theater
),
ThreatZonesJs.from_zones(
self.game.threat_zone_for(player=False), self.game.theater
),
)
self.threatZonesChanged.emit()
@Property(ThreatZoneContainerJs, notify=threatZonesChanged)
def threatZones(self) -> ThreatZoneContainerJs:
return self._threat_zones
@property @property
def game(self) -> Game: def game(self) -> Game:
if self.game_model.game is None: if self.game_model.game is None:

View File

@ -1,15 +1,3 @@
/*
* TODO:
*
* - Culling
* - Threat zones
* - Navmeshes
* - Time of day/weather themeing
* - Exclusion zones
* - "Actual" front line
* - Debug flight plan drawing
*/
const Colors = Object.freeze({ const Colors = Object.freeze({
Blue: "#0084ff", Blue: "#0084ff",
Red: "#c85050", Red: "#c85050",
@ -172,6 +160,14 @@ const redFlightPlansLayer = L.layerGroup();
const selectedFlightPlansLayer = L.layerGroup(); const selectedFlightPlansLayer = L.layerGroup();
const allFlightPlansLayer = L.layerGroup(); const allFlightPlansLayer = L.layerGroup();
const blueFullThreatZones = L.layerGroup();
const blueAircraftThreatZones = L.layerGroup();
const blueAirDefenseThreatZones = L.layerGroup();
const redFullThreatZones = L.layerGroup();
const redAircraftThreatZones = L.layerGroup();
const redAirDefenseThreatZones = L.layerGroup();
L.control L.control
.groupedLayers( .groupedLayers(
baseLayers, baseLayers,
@ -197,8 +193,27 @@ L.control
"Show all red": redFlightPlansLayer, "Show all red": redFlightPlansLayer,
"Show all": allFlightPlansLayer, "Show all": allFlightPlansLayer,
}, },
"Blue Threat Zones": {
Hide: L.layerGroup().addTo(map),
Full: blueFullThreatZones,
Aircraft: blueAircraftThreatZones,
"Air Defenses": blueAirDefenseThreatZones,
}, },
{ collapsed: false, exclusiveGroups: ["Flight Plans"] } "Red Threat Zones": {
Hide: L.layerGroup().addTo(map),
Full: redFullThreatZones,
Aircraft: redAircraftThreatZones,
"Air Defenses": redAirDefenseThreatZones,
},
},
{
collapsed: false,
exclusiveGroups: [
"Flight Plans",
"Blue Threat Zones",
"Red Threat Zones",
],
}
) )
.addTo(map); .addTo(map);
@ -213,6 +228,7 @@ new QWebChannel(qt.webChannelTransport, function (channel) {
game.supplyRoutesChanged.connect(drawSupplyRoutes); game.supplyRoutesChanged.connect(drawSupplyRoutes);
game.frontLinesChanged.connect(drawFrontLines); game.frontLinesChanged.connect(drawFrontLines);
game.flightsChanged.connect(drawFlightPlans); game.flightsChanged.connect(drawFlightPlans);
game.threatZonesChanged.connect(drawThreatZones);
}); });
function recenterMap(center) { function recenterMap(center) {
@ -702,6 +718,52 @@ function drawFlightPlans() {
} }
} }
function _drawThreatZones(zones, layer, player) {
const color = player ? Colors.Blue : Colors.Red;
for (const zone of zones) {
L.polyline(zone, {
color: color,
weight: 1,
fill: true,
fillOpacity: 0.4,
noClip: true,
}).addTo(layer);
}
}
function drawThreatZones() {
blueFullThreatZones.clearLayers();
blueAircraftThreatZones.clearLayers();
blueAirDefenseThreatZones.clearLayers();
redFullThreatZones.clearLayers();
redAircraftThreatZones.clearLayers();
redAirDefenseThreatZones.clearLayers();
_drawThreatZones(game.threatZones.blue.full, blueFullThreatZones, true);
_drawThreatZones(
game.threatZones.blue.aircraft,
blueAircraftThreatZones,
true
);
_drawThreatZones(
game.threatZones.blue.airDefenses,
blueAirDefenseThreatZones,
true
);
_drawThreatZones(game.threatZones.red.full, redFullThreatZones, false);
_drawThreatZones(
game.threatZones.red.aircraft,
redAircraftThreatZones,
false
);
_drawThreatZones(
game.threatZones.red.airDefenses,
redAirDefenseThreatZones,
false
);
}
function drawInitialMap() { function drawInitialMap() {
recenterMap(game.mapCenter); recenterMap(game.mapCenter);
drawControlPoints(); drawControlPoints();
@ -709,6 +771,7 @@ function drawInitialMap() {
drawSupplyRoutes(); drawSupplyRoutes();
drawFrontLines(); drawFrontLines();
drawFlightPlans(); drawFlightPlans();
drawThreatZones();
} }
function clearAllLayers() { function clearAllLayers() {