mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Add threat zone support to the new map.
https://github.com/dcs-liberation/dcs_liberation/issues/2039
This commit is contained in:
parent
30aebf2546
commit
dc4762a03b
@ -21,8 +21,10 @@ import {
|
|||||||
} from "./frontLinesSlice";
|
} from "./frontLinesSlice";
|
||||||
import FrontLine from "./frontline";
|
import FrontLine from "./frontline";
|
||||||
import reloadGameState from "./gamestate";
|
import reloadGameState from "./gamestate";
|
||||||
|
import { liberationApi } from "./liberationApi";
|
||||||
import Tgo from "./tgo";
|
import Tgo from "./tgo";
|
||||||
import { updateTgo } from "./tgosSlice";
|
import { updateTgo } from "./tgosSlice";
|
||||||
|
import { threatZonesUpdated } from "./threatZonesSlice";
|
||||||
import { LatLng } from "leaflet";
|
import { LatLng } from "leaflet";
|
||||||
|
|
||||||
interface GameUpdateEvents {
|
interface GameUpdateEvents {
|
||||||
@ -68,6 +70,16 @@ export const handleStreamedEvents = (
|
|||||||
dispatch(endCombat(id));
|
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) {
|
for (const flight of events.new_flights) {
|
||||||
dispatch(registerFlight(flight));
|
dispatch(registerFlight(flight));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { ControlPoint } from "./controlpoint";
|
import { ControlPoint } from "./controlpoint";
|
||||||
import { Flight } from "./flight";
|
import { Flight } from "./flight";
|
||||||
import FrontLine from "./frontline";
|
import FrontLine from "./frontline";
|
||||||
|
import { ThreatZoneContainer } from "./liberationApi";
|
||||||
import SupplyRoute from "./supplyroute";
|
import SupplyRoute from "./supplyroute";
|
||||||
import Tgo from "./tgo";
|
import Tgo from "./tgo";
|
||||||
import { LatLngLiteral } from "leaflet";
|
import { LatLngLiteral } from "leaflet";
|
||||||
@ -11,5 +12,6 @@ export default interface Game {
|
|||||||
supply_routes: SupplyRoute[];
|
supply_routes: SupplyRoute[];
|
||||||
front_lines: FrontLine[];
|
front_lines: FrontLine[];
|
||||||
flights: Flight[];
|
flights: Flight[];
|
||||||
|
threat_zones: ThreatZoneContainer;
|
||||||
map_center: LatLngLiteral | null;
|
map_center: LatLngLiteral | null;
|
||||||
}
|
}
|
||||||
|
|||||||
49
client/src/api/threatZonesSlice.ts
Normal file
49
client/src/api/threatZonesSlice.ts
Normal 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;
|
||||||
@ -6,6 +6,7 @@ import frontLinesReducer from "../api/frontLinesSlice";
|
|||||||
import mapReducer from "../api/mapSlice";
|
import mapReducer from "../api/mapSlice";
|
||||||
import supplyRoutesReducer from "../api/supplyRoutesSlice";
|
import supplyRoutesReducer from "../api/supplyRoutesSlice";
|
||||||
import tgosReducer from "../api/tgosSlice";
|
import tgosReducer from "../api/tgosSlice";
|
||||||
|
import threatZonesReducer from "../api/threatZonesSlice";
|
||||||
import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
|
import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
@ -17,6 +18,7 @@ export const store = configureStore({
|
|||||||
map: mapReducer,
|
map: mapReducer,
|
||||||
supplyRoutes: supplyRoutesReducer,
|
supplyRoutes: supplyRoutesReducer,
|
||||||
tgos: tgosReducer,
|
tgos: tgosReducer,
|
||||||
|
threatZones: threatZonesReducer,
|
||||||
[baseApi.reducerPath]: baseApi.reducer,
|
[baseApi.reducerPath]: baseApi.reducer,
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
|
|||||||
@ -8,6 +8,11 @@ import FlightPlansLayer from "../flightplanslayer";
|
|||||||
import FrontLinesLayer from "../frontlineslayer";
|
import FrontLinesLayer from "../frontlineslayer";
|
||||||
import SupplyRoutesLayer from "../supplyrouteslayer";
|
import SupplyRoutesLayer from "../supplyrouteslayer";
|
||||||
import TgosLayer from "../tgoslayer/TgosLayer";
|
import TgosLayer from "../tgoslayer/TgosLayer";
|
||||||
|
import { CoalitionThreatZones } from "../threatzones";
|
||||||
|
import {
|
||||||
|
ThreatZonesLayer,
|
||||||
|
ThreatZoneFilter,
|
||||||
|
} from "../threatzones/ThreatZonesLayer";
|
||||||
import "./LiberationMap.css";
|
import "./LiberationMap.css";
|
||||||
import { Map } from "leaflet";
|
import { Map } from "leaflet";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
@ -83,6 +88,10 @@ export default function LiberationMap() {
|
|||||||
<FlightPlansLayer blue={false} />
|
<FlightPlansLayer blue={false} />
|
||||||
</LayersControl.Overlay>
|
</LayersControl.Overlay>
|
||||||
</LayersControl>
|
</LayersControl>
|
||||||
|
<LayersControl position="topleft">
|
||||||
|
<CoalitionThreatZones blue={true} />
|
||||||
|
<CoalitionThreatZones blue={false} />
|
||||||
|
</LayersControl>
|
||||||
</MapContainer>
|
</MapContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
35
client/src/components/threatzones/CoalitionThreatZones.tsx
Normal file
35
client/src/components/threatzones/CoalitionThreatZones.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
client/src/components/threatzones/ThreatZone.tsx
Normal file
24
client/src/components/threatzones/ThreatZone.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
43
client/src/components/threatzones/ThreatZonesLayer.tsx
Normal file
43
client/src/components/threatzones/ThreatZonesLayer.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
2
client/src/components/threatzones/index.ts
Normal file
2
client/src/components/threatzones/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { ThreatZonesLayer, ThreatZoneFilter } from "./ThreatZonesLayer";
|
||||||
|
export { CoalitionThreatZones } from "./CoalitionThreatZones";
|
||||||
@ -8,6 +8,7 @@ from game.server.controlpoints.models import ControlPointJs
|
|||||||
from game.server.flights.models import FlightJs
|
from game.server.flights.models import FlightJs
|
||||||
from game.server.frontlines.models import FrontLineJs
|
from game.server.frontlines.models import FrontLineJs
|
||||||
from game.server.leaflet import LeafletPoint
|
from game.server.leaflet import LeafletPoint
|
||||||
|
from game.server.mapzones.models import ThreatZoneContainerJs
|
||||||
from game.server.supplyroutes.models import SupplyRouteJs
|
from game.server.supplyroutes.models import SupplyRouteJs
|
||||||
from game.server.tgos.models import TgoJs
|
from game.server.tgos.models import TgoJs
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ class GameJs(BaseModel):
|
|||||||
supply_routes: list[SupplyRouteJs]
|
supply_routes: list[SupplyRouteJs]
|
||||||
front_lines: list[FrontLineJs]
|
front_lines: list[FrontLineJs]
|
||||||
flights: list[FlightJs]
|
flights: list[FlightJs]
|
||||||
|
threat_zones: ThreatZoneContainerJs
|
||||||
map_center: LeafletPoint
|
map_center: LeafletPoint
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
@ -34,5 +36,6 @@ class GameJs(BaseModel):
|
|||||||
supply_routes=SupplyRouteJs.all_in_game(game),
|
supply_routes=SupplyRouteJs.all_in_game(game),
|
||||||
front_lines=FrontLineJs.all_in_game(game),
|
front_lines=FrontLineJs.all_in_game(game),
|
||||||
flights=FlightJs.all_in_game(game, with_waypoints=True),
|
flights=FlightJs.all_in_game(game, with_waypoints=True),
|
||||||
|
threat_zones=ThreatZoneContainerJs.for_game(game),
|
||||||
map_center=game.theater.terrain.map_view_default.position.latlng(),
|
map_center=game.theater.terrain.map_view_default.position.latlng(),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,11 +1,16 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from game.server.leaflet import LeafletPoint, LeafletPoly, ShapelyUtil
|
from game.server.leaflet import LeafletPoint, LeafletPoly, ShapelyUtil
|
||||||
from game.theater import ConflictTheater
|
from game.theater import ConflictTheater
|
||||||
from game.threatzones import ThreatZones
|
from game.threatzones import ThreatZones
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game import Game
|
||||||
|
|
||||||
|
|
||||||
class MapZonesJs(BaseModel):
|
class MapZonesJs(BaseModel):
|
||||||
inclusion: list[LeafletPoly]
|
inclusion: list[LeafletPoly]
|
||||||
@ -49,3 +54,14 @@ class ThreatZoneContainerJs(BaseModel):
|
|||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
title = "ThreatZoneContainer"
|
title = "ThreatZoneContainer"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def for_game(game: Game) -> ThreatZoneContainerJs:
|
||||||
|
return ThreatZoneContainerJs(
|
||||||
|
blue=ThreatZonesJs.from_zones(
|
||||||
|
game.threat_zone_for(player=True), game.theater
|
||||||
|
),
|
||||||
|
red=ThreatZonesJs.from_zones(
|
||||||
|
game.threat_zone_for(player=False), game.theater
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
|||||||
|
|
||||||
from game import Game
|
from game import Game
|
||||||
from game.server import GameContext
|
from game.server import GameContext
|
||||||
from .models import MapZonesJs, ThreatZoneContainerJs, ThreatZonesJs, UnculledZoneJs
|
from .models import MapZonesJs, ThreatZoneContainerJs, UnculledZoneJs
|
||||||
from ..leaflet import ShapelyUtil
|
from ..leaflet import ShapelyUtil
|
||||||
|
|
||||||
router: APIRouter = APIRouter(prefix="/map-zones")
|
router: APIRouter = APIRouter(prefix="/map-zones")
|
||||||
@ -41,7 +41,4 @@ def get_unculled_zones(
|
|||||||
def get_threat_zones(
|
def get_threat_zones(
|
||||||
game: Game = Depends(GameContext.require),
|
game: Game = Depends(GameContext.require),
|
||||||
) -> ThreatZoneContainerJs:
|
) -> ThreatZoneContainerJs:
|
||||||
return ThreatZoneContainerJs(
|
return ThreatZoneContainerJs.for_game(game)
|
||||||
blue=ThreatZonesJs.from_zones(game.threat_zone_for(player=True), game.theater),
|
|
||||||
red=ThreatZonesJs.from_zones(game.threat_zone_for(player=False), game.theater),
|
|
||||||
)
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user