Add navmesh support to the new map.

https://github.com/dcs-liberation/dcs_liberation/issues/2039
This commit is contained in:
Dan Albert 2022-03-06 23:07:24 -08:00
parent b08b91ca2e
commit 15176223fa
11 changed files with 181 additions and 33 deletions

View File

@ -22,6 +22,7 @@ import {
import FrontLine from "./frontline"; import FrontLine from "./frontline";
import reloadGameState from "./gamestate"; import reloadGameState from "./gamestate";
import { liberationApi } from "./liberationApi"; import { liberationApi } from "./liberationApi";
import { navMeshUpdated } from "./navMeshSlice";
import Tgo from "./tgo"; import Tgo from "./tgo";
import { updateTgo } from "./tgosSlice"; import { updateTgo } from "./tgosSlice";
import { threatZonesUpdated } from "./threatZonesSlice"; import { threatZonesUpdated } from "./threatZonesSlice";
@ -72,6 +73,16 @@ export const handleStreamedEvents = (
dispatch(endCombat(id)); dispatch(endCombat(id));
} }
for (const blue of events.navmesh_updates) {
dispatch(
liberationApi.endpoints.getNavmesh.initiate({ forPlayer: blue })
).then((result) => {
if (result.data) {
dispatch(navMeshUpdated({ blue: blue, mesh: result.data }));
}
});
}
if (events.threat_zones_updated) { if (events.threat_zones_updated) {
dispatch(liberationApi.endpoints.getThreatZones.initiate()).then( dispatch(liberationApi.endpoints.getThreatZones.initiate()).then(
(result) => { (result) => {

View File

@ -1,7 +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 { NavMeshes, 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";
@ -13,5 +13,6 @@ export default interface Game {
front_lines: FrontLine[]; front_lines: FrontLine[];
flights: Flight[]; flights: Flight[];
threat_zones: ThreatZoneContainer; threat_zones: ThreatZoneContainer;
navmeshes: NavMeshes;
map_center: LatLngLiteral | null; map_center: LatLngLiteral | null;
} }

View File

@ -280,7 +280,7 @@ export type GetThreatZonesApiResponse =
/** status 200 Successful Response */ ThreatZoneContainer; /** status 200 Successful Response */ ThreatZoneContainer;
export type GetThreatZonesApiArg = void; export type GetThreatZonesApiArg = void;
export type GetNavmeshApiResponse = export type GetNavmeshApiResponse =
/** status 200 Successful Response */ NavMeshPoly[]; /** status 200 Successful Response */ NavMesh;
export type GetNavmeshApiArg = { export type GetNavmeshApiArg = {
forPlayer: boolean; forPlayer: boolean;
}; };
@ -414,23 +414,6 @@ export type SupplyRoute = {
blue: boolean; blue: boolean;
active_transports: string[]; active_transports: string[];
}; };
export type Game = {
control_points: ControlPoint[];
tgos: Tgo[];
supply_routes: SupplyRoute[];
front_lines: FrontLine[];
flights: Flight[];
map_center: LatLng;
};
export type MapZones = {
inclusion: number[][][];
exclusion: number[][][];
sea: number[][][];
};
export type UnculledZone = {
position: LatLng;
radius: number;
};
export type ThreatZones = { export type ThreatZones = {
full: number[][][]; full: number[][][];
aircraft: number[][][]; aircraft: number[][][];
@ -445,6 +428,32 @@ export type NavMeshPoly = {
poly: number[][]; poly: number[][];
threatened: boolean; threatened: boolean;
}; };
export type NavMesh = {
polys: NavMeshPoly[];
};
export type NavMeshes = {
blue: NavMesh;
red: NavMesh;
};
export type Game = {
control_points: ControlPoint[];
tgos: Tgo[];
supply_routes: SupplyRoute[];
front_lines: FrontLine[];
flights: Flight[];
threat_zones: ThreatZoneContainer;
navmeshes: NavMeshes;
map_center: LatLng;
};
export type MapZones = {
inclusion: number[][][];
exclusion: number[][][];
sea: number[][][];
};
export type UnculledZone = {
position: LatLng;
radius: number;
};
export const { export const {
useListControlPointsQuery, useListControlPointsQuery,
useGetControlPointByIdQuery, useGetControlPointByIdQuery,

View File

@ -0,0 +1,50 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { NavMesh, NavMeshPoly } from "./liberationApi";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface NavMeshState {
blue: NavMeshPoly[];
red: NavMeshPoly[];
}
const initialState: NavMeshState = {
blue: [],
red: [],
};
interface INavMeshUpdate {
blue: boolean;
mesh: NavMesh;
}
const navMeshSlice = createSlice({
name: "navmesh",
initialState: initialState,
reducers: {
updated: (state, action: PayloadAction<INavMeshUpdate>) => {
const polys = action.payload.mesh.polys;
if (action.payload.blue) {
state.blue = polys;
} else {
state.red = polys;
}
},
},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.blue = action.payload.navmeshes.blue.polys;
state.red = action.payload.navmeshes.red.polys;
});
builder.addCase(gameUnloaded, (state) => {
state.blue = [];
state.red = [];
});
},
});
export const { updated: navMeshUpdated } = navMeshSlice.actions;
export const selectNavMeshes = (state: RootState) => state.navmeshes;
export default navMeshSlice.reducer;

View File

@ -4,6 +4,7 @@ import controlPointsReducer from "../api/controlPointsSlice";
import flightsReducer from "../api/flightsSlice"; import flightsReducer from "../api/flightsSlice";
import frontLinesReducer from "../api/frontLinesSlice"; import frontLinesReducer from "../api/frontLinesSlice";
import mapReducer from "../api/mapSlice"; import mapReducer from "../api/mapSlice";
import navMeshReducer from "../api/navMeshSlice";
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 threatZonesReducer from "../api/threatZonesSlice";
@ -16,6 +17,7 @@ export const store = configureStore({
flights: flightsReducer, flights: flightsReducer,
frontLines: frontLinesReducer, frontLines: frontLinesReducer,
map: mapReducer, map: mapReducer,
navmeshes: navMeshReducer,
supplyRoutes: supplyRoutesReducer, supplyRoutes: supplyRoutesReducer,
tgos: tgosReducer, tgos: tgosReducer,
threatZones: threatZonesReducer, threatZones: threatZonesReducer,

View File

@ -6,6 +6,7 @@ import CombatLayer from "../combatlayer";
import ControlPointsLayer from "../controlpointslayer"; import ControlPointsLayer from "../controlpointslayer";
import FlightPlansLayer from "../flightplanslayer"; import FlightPlansLayer from "../flightplanslayer";
import FrontLinesLayer from "../frontlineslayer"; import FrontLinesLayer from "../frontlineslayer";
import NavMeshLayer from "../navmesh/NavMeshLayer";
import SupplyRoutesLayer from "../supplyrouteslayer"; import SupplyRoutesLayer from "../supplyrouteslayer";
import TgosLayer from "../tgoslayer/TgosLayer"; import TgosLayer from "../tgoslayer/TgosLayer";
import { CoalitionThreatZones } from "../threatzones"; import { CoalitionThreatZones } from "../threatzones";
@ -87,6 +88,12 @@ export default function LiberationMap() {
<LayersControl position="topleft"> <LayersControl position="topleft">
<CoalitionThreatZones blue={true} /> <CoalitionThreatZones blue={true} />
<CoalitionThreatZones blue={false} /> <CoalitionThreatZones blue={false} />
<LayersControl.Overlay name="Blue navmesh">
<NavMeshLayer blue={true} />
</LayersControl.Overlay>
<LayersControl.Overlay name="Red navmesh">
<NavMeshLayer blue={false} />
</LayersControl.Overlay>
</LayersControl> </LayersControl>
</MapContainer> </MapContainer>
); );

View File

@ -0,0 +1,33 @@
import { selectNavMeshes } from "../../api/navMeshSlice";
import { useAppSelector } from "../../app/hooks";
import { LatLng } from "leaflet";
import { LayerGroup, Polygon } from "react-leaflet";
interface NavMeshLayerProps {
blue: boolean;
}
export default function NavMeshLayer(props: NavMeshLayerProps) {
const meshes = useAppSelector(selectNavMeshes);
const mesh = props.blue ? meshes.blue : meshes.red;
return (
<LayerGroup>
{mesh.map((zone, idx) => {
const positions = zone.poly.map(([lat, lng]) => new LatLng(lat, lng));
return (
<Polygon
key={idx}
positions={positions}
color="#000000"
weight={1}
fill
fillColor={zone.threatened ? "#ff0000" : "#00ff00"}
fillOpacity={0.1}
noClip
interactive={false}
/>
);
})}
</LayerGroup>
);
}

View File

@ -0,0 +1 @@
export { default } from "./NavMeshLayer";

View File

@ -9,6 +9,7 @@ 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.mapzones.models import ThreatZoneContainerJs
from game.server.navmesh.models import NavMeshesJs
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
@ -23,6 +24,7 @@ class GameJs(BaseModel):
front_lines: list[FrontLineJs] front_lines: list[FrontLineJs]
flights: list[FlightJs] flights: list[FlightJs]
threat_zones: ThreatZoneContainerJs threat_zones: ThreatZoneContainerJs
navmeshes: NavMeshesJs
map_center: LeafletPoint map_center: LeafletPoint
class Config: class Config:
@ -37,5 +39,6 @@ class GameJs(BaseModel):
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), threat_zones=ThreatZoneContainerJs.for_game(game),
navmeshes=NavMeshesJs.from_game(game),
map_center=game.theater.terrain.map_view_default.position.latlng(), map_center=game.theater.terrain.map_view_default.position.latlng(),
) )

View File

@ -1,8 +1,14 @@
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 LeafletPoly from game.server.leaflet import LeafletPoly, ShapelyUtil
if TYPE_CHECKING:
from game import Game
from game.navmesh import NavMesh
class NavMeshPolyJs(BaseModel): class NavMeshPolyJs(BaseModel):
@ -11,3 +17,37 @@ class NavMeshPolyJs(BaseModel):
class Config: class Config:
title = "NavMeshPoly" title = "NavMeshPoly"
class NavMeshJs(BaseModel):
polys: list[NavMeshPolyJs]
class Config:
title = "NavMesh"
@staticmethod
def from_navmesh(navmesh: NavMesh, game: Game) -> NavMeshJs:
return NavMeshJs(
polys=[
NavMeshPolyJs(
poly=ShapelyUtil.poly_to_leaflet(p.poly, game.theater),
threatened=p.threatened,
)
for p in navmesh.polys
]
)
class NavMeshesJs(BaseModel):
blue: NavMeshJs
red: NavMeshJs
class Config:
title = "NavMeshes"
@staticmethod
def from_game(game: Game) -> NavMeshesJs:
return NavMeshesJs(
blue=NavMeshJs.from_navmesh(game.blue.nav_mesh, game),
red=NavMeshJs.from_navmesh(game.red.nav_mesh, game),
)

View File

@ -2,21 +2,12 @@ from fastapi import APIRouter, Depends
from game import Game from game import Game
from game.server import GameContext from game.server import GameContext
from .models import NavMeshPolyJs from .models import NavMeshJs
from ..leaflet import ShapelyUtil
router: APIRouter = APIRouter(prefix="/navmesh") router: APIRouter = APIRouter(prefix="/navmesh")
@router.get("/", operation_id="get_navmesh", response_model=list[NavMeshPolyJs]) @router.get("/", operation_id="get_navmesh", response_model=NavMeshJs)
def get( def get(for_player: bool, game: Game = Depends(GameContext.require)) -> NavMeshJs:
for_player: bool, game: Game = Depends(GameContext.require)
) -> list[NavMeshPolyJs]:
mesh = game.coalition_for(for_player).nav_mesh mesh = game.coalition_for(for_player).nav_mesh
return [ return NavMeshJs.from_navmesh(mesh, game)
NavMeshPolyJs(
poly=ShapelyUtil.poly_to_leaflet(p.poly, game.theater),
threatened=p.threatened,
)
for p in mesh.polys
]