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 reloadGameState from "./gamestate";
import { liberationApi } from "./liberationApi";
import { navMeshUpdated } from "./navMeshSlice";
import Tgo from "./tgo";
import { updateTgo } from "./tgosSlice";
import { threatZonesUpdated } from "./threatZonesSlice";
@ -72,6 +73,16 @@ export const handleStreamedEvents = (
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) {
dispatch(liberationApi.endpoints.getThreatZones.initiate()).then(
(result) => {

View File

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

View File

@ -280,7 +280,7 @@ export type GetThreatZonesApiResponse =
/** status 200 Successful Response */ ThreatZoneContainer;
export type GetThreatZonesApiArg = void;
export type GetNavmeshApiResponse =
/** status 200 Successful Response */ NavMeshPoly[];
/** status 200 Successful Response */ NavMesh;
export type GetNavmeshApiArg = {
forPlayer: boolean;
};
@ -414,23 +414,6 @@ export type SupplyRoute = {
blue: boolean;
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 = {
full: number[][][];
aircraft: number[][][];
@ -445,6 +428,32 @@ export type NavMeshPoly = {
poly: number[][];
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 {
useListControlPointsQuery,
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 frontLinesReducer from "../api/frontLinesSlice";
import mapReducer from "../api/mapSlice";
import navMeshReducer from "../api/navMeshSlice";
import supplyRoutesReducer from "../api/supplyRoutesSlice";
import tgosReducer from "../api/tgosSlice";
import threatZonesReducer from "../api/threatZonesSlice";
@ -16,6 +17,7 @@ export const store = configureStore({
flights: flightsReducer,
frontLines: frontLinesReducer,
map: mapReducer,
navmeshes: navMeshReducer,
supplyRoutes: supplyRoutesReducer,
tgos: tgosReducer,
threatZones: threatZonesReducer,

View File

@ -6,6 +6,7 @@ import CombatLayer from "../combatlayer";
import ControlPointsLayer from "../controlpointslayer";
import FlightPlansLayer from "../flightplanslayer";
import FrontLinesLayer from "../frontlineslayer";
import NavMeshLayer from "../navmesh/NavMeshLayer";
import SupplyRoutesLayer from "../supplyrouteslayer";
import TgosLayer from "../tgoslayer/TgosLayer";
import { CoalitionThreatZones } from "../threatzones";
@ -87,6 +88,12 @@ export default function LiberationMap() {
<LayersControl position="topleft">
<CoalitionThreatZones blue={true} />
<CoalitionThreatZones blue={false} />
<LayersControl.Overlay name="Blue navmesh">
<NavMeshLayer blue={true} />
</LayersControl.Overlay>
<LayersControl.Overlay name="Red navmesh">
<NavMeshLayer blue={false} />
</LayersControl.Overlay>
</LayersControl>
</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.leaflet import LeafletPoint
from game.server.mapzones.models import ThreatZoneContainerJs
from game.server.navmesh.models import NavMeshesJs
from game.server.supplyroutes.models import SupplyRouteJs
from game.server.tgos.models import TgoJs
@ -23,6 +24,7 @@ class GameJs(BaseModel):
front_lines: list[FrontLineJs]
flights: list[FlightJs]
threat_zones: ThreatZoneContainerJs
navmeshes: NavMeshesJs
map_center: LeafletPoint
class Config:
@ -37,5 +39,6 @@ class GameJs(BaseModel):
front_lines=FrontLineJs.all_in_game(game),
flights=FlightJs.all_in_game(game, with_waypoints=True),
threat_zones=ThreatZoneContainerJs.for_game(game),
navmeshes=NavMeshesJs.from_game(game),
map_center=game.theater.terrain.map_view_default.position.latlng(),
)

View File

@ -1,8 +1,14 @@
from __future__ import annotations
from typing import TYPE_CHECKING
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):
@ -11,3 +17,37 @@ class NavMeshPolyJs(BaseModel):
class Config:
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.server import GameContext
from .models import NavMeshPolyJs
from ..leaflet import ShapelyUtil
from .models import NavMeshJs
router: APIRouter = APIRouter(prefix="/navmesh")
@router.get("/", operation_id="get_navmesh", response_model=list[NavMeshPolyJs])
def get(
for_player: bool, game: Game = Depends(GameContext.require)
) -> list[NavMeshPolyJs]:
@router.get("/", operation_id="get_navmesh", response_model=NavMeshJs)
def get(for_player: bool, game: Game = Depends(GameContext.require)) -> NavMeshJs:
mesh = game.coalition_for(for_player).nav_mesh
return [
NavMeshPolyJs(
poly=ShapelyUtil.poly_to_leaflet(p.poly, game.theater),
threatened=p.threatened,
)
for p in mesh.polys
]
return NavMeshJs.from_navmesh(mesh, game)