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
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 dcs import Point
from dcs.unit import Unit
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.factions.faction import Faction
@ -19,9 +19,8 @@ from game.theater import (
TheaterGroundObject,
FrontLine,
LatLon,
Airfield,
Carrier,
)
from game.threatzones import ThreatZones
from game.transfers import MultiGroupTransport, TransportMap
from game.utils import meters, nautical_miles
from gen.ato import AirTaskingOrder
@ -519,7 +518,7 @@ class FlightJs(QObject):
return self._selected
@Property(list, notify=commitBoundaryChanged)
def commitBoundary(self) -> Optional[List[LeafletLatLon]]:
def commitBoundary(self) -> List[LeafletLatLon]:
if not isinstance(self.flight.flight_plan, PatrollingFlightPlan):
return []
start = self.flight.flight_plan.patrol_start
@ -535,6 +534,75 @@ class FlightJs(QObject):
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):
cleared = Signal()
@ -544,6 +612,7 @@ class MapModel(QObject):
supplyRoutesChanged = Signal()
flightsChanged = Signal()
frontLinesChanged = Signal()
threatZonesChanged = Signal()
def __init__(self, game_model: GameModel) -> None:
super().__init__()
@ -554,6 +623,9 @@ class MapModel(QObject):
self._supply_routes = []
self._flights = []
self._front_lines = []
self._threat_zones = ThreatZoneContainerJs(
ThreatZonesJs.empty(), ThreatZonesJs.empty()
)
self._selected_flight_index: Optional[Tuple[int, int]] = None
GameUpdateSignal.get_instance().game_loaded.connect(self.on_game_load)
GameUpdateSignal.get_instance().flight_paths_changed.connect(self.reset_atos)
@ -571,6 +643,9 @@ class MapModel(QObject):
self._ground_objects = []
self._flights = []
self._front_lines = []
self._threat_zones = ThreatZoneContainerJs(
ThreatZonesJs.empty(), ThreatZonesJs.empty()
)
self.cleared.emit()
def set_package_selection(self, index: int) -> None:
@ -614,6 +689,7 @@ class MapModel(QObject):
self.reset_routes()
self.reset_atos()
self.reset_front_lines()
self.reset_threat_zones()
def on_game_load(self, game: Optional[Game]) -> None:
if game is not None:
@ -737,6 +813,21 @@ class MapModel(QObject):
def frontLines(self) -> List[FrontLineJs]:
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
def game(self) -> Game:
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({
Blue: "#0084ff",
Red: "#c85050",
@ -172,6 +160,14 @@ const redFlightPlansLayer = L.layerGroup();
const selectedFlightPlansLayer = 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
.groupedLayers(
baseLayers,
@ -197,8 +193,27 @@ L.control
"Show all red": redFlightPlansLayer,
"Show all": allFlightPlansLayer,
},
"Blue Threat Zones": {
Hide: L.layerGroup().addTo(map),
Full: blueFullThreatZones,
Aircraft: blueAircraftThreatZones,
"Air Defenses": blueAirDefenseThreatZones,
},
"Red Threat Zones": {
Hide: L.layerGroup().addTo(map),
Full: redFullThreatZones,
Aircraft: redAircraftThreatZones,
"Air Defenses": redAirDefenseThreatZones,
},
},
{ collapsed: false, exclusiveGroups: ["Flight Plans"] }
{
collapsed: false,
exclusiveGroups: [
"Flight Plans",
"Blue Threat Zones",
"Red Threat Zones",
],
}
)
.addTo(map);
@ -213,6 +228,7 @@ new QWebChannel(qt.webChannelTransport, function (channel) {
game.supplyRoutesChanged.connect(drawSupplyRoutes);
game.frontLinesChanged.connect(drawFrontLines);
game.flightsChanged.connect(drawFlightPlans);
game.threatZonesChanged.connect(drawThreatZones);
});
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() {
recenterMap(game.mapCenter);
drawControlPoints();
@ -709,6 +771,7 @@ function drawInitialMap() {
drawSupplyRoutes();
drawFrontLines();
drawFlightPlans();
drawThreatZones();
}
function clearAllLayers() {