Add threat zone support to the new map.

https://github.com/dcs-liberation/dcs_liberation/issues/2039
This commit is contained in:
Dan Albert
2022-03-06 19:30:23 -08:00
parent 30aebf2546
commit dc4762a03b
12 changed files with 199 additions and 5 deletions

View File

@@ -21,8 +21,10 @@ import {
} from "./frontLinesSlice";
import FrontLine from "./frontline";
import reloadGameState from "./gamestate";
import { liberationApi } from "./liberationApi";
import Tgo from "./tgo";
import { updateTgo } from "./tgosSlice";
import { threatZonesUpdated } from "./threatZonesSlice";
import { LatLng } from "leaflet";
interface GameUpdateEvents {
@@ -68,6 +70,16 @@ export const handleStreamedEvents = (
dispatch(endCombat(id));
}
if (events.threat_zones_updated) {
dispatch(liberationApi.endpoints.getThreatZones.initiate()).then(
(result) => {
if (result.data) {
dispatch(threatZonesUpdated(result.data));
}
}
);
}
for (const flight of events.new_flights) {
dispatch(registerFlight(flight));
}

View File

@@ -1,6 +1,7 @@
import { ControlPoint } from "./controlpoint";
import { Flight } from "./flight";
import FrontLine from "./frontline";
import { ThreatZoneContainer } from "./liberationApi";
import SupplyRoute from "./supplyroute";
import Tgo from "./tgo";
import { LatLngLiteral } from "leaflet";
@@ -11,5 +12,6 @@ export default interface Game {
supply_routes: SupplyRoute[];
front_lines: FrontLine[];
flights: Flight[];
threat_zones: ThreatZoneContainer;
map_center: LatLngLiteral | null;
}

View File

@@ -0,0 +1,49 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { ThreatZoneContainer } from "./liberationApi";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
interface ThreatZonesState {
zones: ThreatZoneContainer;
}
const initialState: ThreatZonesState = {
zones: {
blue: {
full: [],
aircraft: [],
air_defenses: [],
radar_sams: [],
},
red: {
full: [],
aircraft: [],
air_defenses: [],
radar_sams: [],
},
},
};
export const threatZonesSlice = createSlice({
name: "threatZonesState",
initialState,
reducers: {
updated: (state, action: PayloadAction<ThreatZoneContainer>) => {
state.zones = action.payload;
},
},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.zones = action.payload.threat_zones;
});
builder.addCase(gameUnloaded, (state) => {
state.zones = initialState.zones;
});
},
});
export const { updated: threatZonesUpdated } = threatZonesSlice.actions;
export const selectThreatZones = (state: RootState) => state.threatZones;
export default threatZonesSlice.reducer;

View File

@@ -6,6 +6,7 @@ import frontLinesReducer from "../api/frontLinesSlice";
import mapReducer from "../api/mapSlice";
import supplyRoutesReducer from "../api/supplyRoutesSlice";
import tgosReducer from "../api/tgosSlice";
import threatZonesReducer from "../api/threatZonesSlice";
import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
export const store = configureStore({
@@ -17,6 +18,7 @@ export const store = configureStore({
map: mapReducer,
supplyRoutes: supplyRoutesReducer,
tgos: tgosReducer,
threatZones: threatZonesReducer,
[baseApi.reducerPath]: baseApi.reducer,
},
middleware: (getDefaultMiddleware) =>

View File

@@ -8,6 +8,11 @@ import FlightPlansLayer from "../flightplanslayer";
import FrontLinesLayer from "../frontlineslayer";
import SupplyRoutesLayer from "../supplyrouteslayer";
import TgosLayer from "../tgoslayer/TgosLayer";
import { CoalitionThreatZones } from "../threatzones";
import {
ThreatZonesLayer,
ThreatZoneFilter,
} from "../threatzones/ThreatZonesLayer";
import "./LiberationMap.css";
import { Map } from "leaflet";
import { useEffect, useRef } from "react";
@@ -83,6 +88,10 @@ export default function LiberationMap() {
<FlightPlansLayer blue={false} />
</LayersControl.Overlay>
</LayersControl>
<LayersControl position="topleft">
<CoalitionThreatZones blue={true} />
<CoalitionThreatZones blue={false} />
</LayersControl>
</MapContainer>
);
}

View File

@@ -0,0 +1,35 @@
import { ThreatZoneFilter, ThreatZonesLayer } from "./ThreatZonesLayer";
import { LayersControl } from "react-leaflet";
interface CoalitionThreatZonesProps {
blue: boolean;
}
export function CoalitionThreatZones(props: CoalitionThreatZonesProps) {
const color = props.blue ? "Blue" : "Red";
return (
<>
<LayersControl.Overlay name={`${color} threat zones: full`}>
<ThreatZonesLayer blue={props.blue} filter={ThreatZoneFilter.FULL} />
</LayersControl.Overlay>
<LayersControl.Overlay name={`${color} threat zones: aircraft`}>
<ThreatZonesLayer
blue={props.blue}
filter={ThreatZoneFilter.AIRCRAFT}
/>
</LayersControl.Overlay>
<LayersControl.Overlay name={`${color} threat zones: air defenses`}>
<ThreatZonesLayer
blue={props.blue}
filter={ThreatZoneFilter.AIR_DEFENSES}
/>
</LayersControl.Overlay>
<LayersControl.Overlay name={`${color} threat zones: radar SAMs`}>
<ThreatZonesLayer
blue={props.blue}
filter={ThreatZoneFilter.RADAR_SAMS}
/>
</LayersControl.Overlay>
</>
);
}

View File

@@ -0,0 +1,24 @@
import { LatLng } from "leaflet";
import { Polygon } from "react-leaflet";
interface ThreatZoneProps {
poly: number[][];
blue: boolean;
}
export default function ThreatZone(props: ThreatZoneProps) {
const color = props.blue ? "#0084ff" : "#c85050";
// TODO: Fix response model so the type can be used directly.
const positions = props.poly.map(([lat, lng]) => new LatLng(lat, lng));
return (
<Polygon
positions={positions}
color={color}
weight={1}
fill
fillOpacity={0.4}
noClip
interactive={false}
/>
);
}

View File

@@ -0,0 +1,43 @@
import { selectThreatZones } from "../../api/threatZonesSlice";
import { useAppSelector } from "../../app/hooks";
import ThreatZone from "./ThreatZone";
import { LayerGroup } from "react-leaflet";
export enum ThreatZoneFilter {
FULL,
AIRCRAFT,
AIR_DEFENSES,
RADAR_SAMS,
}
interface ThreatZonesLayerProps {
blue: boolean;
filter: ThreatZoneFilter;
}
export function ThreatZonesLayer(props: ThreatZonesLayerProps) {
const allZones = useAppSelector(selectThreatZones).zones;
const zones = props.blue ? allZones.blue : allZones.red;
var filtered;
switch (props.filter) {
case ThreatZoneFilter.FULL:
filtered = zones.full;
break;
case ThreatZoneFilter.AIRCRAFT:
filtered = zones.aircraft;
break;
case ThreatZoneFilter.AIR_DEFENSES:
filtered = zones.air_defenses;
break;
case ThreatZoneFilter.RADAR_SAMS:
filtered = zones.radar_sams;
break;
}
return (
<LayerGroup>
{filtered.map((poly, idx) => (
<ThreatZone key={idx} poly={poly} blue={props.blue} />
))}
</LayerGroup>
);
}

View File

@@ -0,0 +1,2 @@
export { ThreatZonesLayer, ThreatZoneFilter } from "./ThreatZonesLayer";
export { CoalitionThreatZones } from "./CoalitionThreatZones";