mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Add navmesh support to the new map.
https://github.com/dcs-liberation/dcs_liberation/issues/2039
This commit is contained in:
parent
b08b91ca2e
commit
15176223fa
@ -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) => {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
50
client/src/api/navMeshSlice.ts
Normal file
50
client/src/api/navMeshSlice.ts
Normal 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;
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
33
client/src/components/navmesh/NavMeshLayer.tsx
Normal file
33
client/src/components/navmesh/NavMeshLayer.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
client/src/components/navmesh/index.ts
Normal file
1
client/src/components/navmesh/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from "./NavMeshLayer";
|
||||||
@ -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(),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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),
|
||||||
|
)
|
||||||
|
|||||||
@ -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
|
|
||||||
]
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user