Add culling exclusion zones display to the new map.

https://github.com/dcs-liberation/dcs_liberation/issues/2158
This commit is contained in:
Raffson 2022-06-15 03:57:04 +02:00 committed by GitHub
parent c5ff8777be
commit ad7032064d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 123 additions and 11 deletions

View File

@ -479,6 +479,7 @@ export type Game = {
threat_zones: ThreatZoneContainer; threat_zones: ThreatZoneContainer;
navmeshes: NavMeshes; navmeshes: NavMeshes;
map_center?: LatLng; map_center?: LatLng;
unculled_zones: UnculledZone[];
}; };
export type MapZones = { export type MapZones = {
inclusion: LatLng[][]; inclusion: LatLng[][];

View File

@ -28,6 +28,7 @@ import {
import { navMeshUpdated } from "./navMeshSlice"; import { navMeshUpdated } from "./navMeshSlice";
import { updateTgo } from "./tgosSlice"; import { updateTgo } from "./tgosSlice";
import { threatZonesUpdated } from "./threatZonesSlice"; import { threatZonesUpdated } from "./threatZonesSlice";
import { unculledZonesUpdated } from "./unculledZonesSlice";
import { LatLng } from "leaflet"; import { LatLng } from "leaflet";
import { updateIadsConnection } from "./iadsNetworkSlice"; import { updateIadsConnection } from "./iadsNetworkSlice";
import { IadsConnection } from "./_liberationApi"; import { IadsConnection } from "./_liberationApi";
@ -87,6 +88,16 @@ export const handleStreamedEvents = (
}); });
} }
if (events.unculled_zones_updated) {
backend.get(`/map-zones/unculled`).then(
(result) => {
if (result.data) {
dispatch(unculledZonesUpdated(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

@ -0,0 +1,36 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { UnculledZone } from "./liberationApi";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
interface UnculledZonesState {
zones: UnculledZone[];
}
const initialState: UnculledZonesState = {
zones: [],
};
export const unculledZonesSlice = createSlice({
name: "unculledZonesState",
initialState,
reducers: {
updated: (state, action: PayloadAction<UnculledZone[]>) => {
state.zones = action.payload;
},
},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.zones = action.payload.unculled_zones;
});
builder.addCase(gameUnloaded, (state) => {
state.zones = initialState.zones;
});
},
});
export const { updated: unculledZonesUpdated } = unculledZonesSlice.actions;
export const selectUnculledZones = (state: RootState) => state.unculledZones;
export default unculledZonesSlice.reducer;

View File

@ -9,6 +9,7 @@ import supplyRoutesReducer from "../api/supplyRoutesSlice";
import tgosReducer from "../api/tgosSlice"; import tgosReducer from "../api/tgosSlice";
import iadsNetworkReducer from "../api/iadsNetworkSlice"; import iadsNetworkReducer from "../api/iadsNetworkSlice";
import threatZonesReducer from "../api/threatZonesSlice"; import threatZonesReducer from "../api/threatZonesSlice";
import unculledZonesReducer from "../api/unculledZonesSlice";
import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit"; import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
export const store = configureStore({ export const store = configureStore({
@ -24,6 +25,7 @@ export const store = configureStore({
tgos: tgosReducer, tgos: tgosReducer,
threatZones: threatZonesReducer, threatZones: threatZonesReducer,
[baseApi.reducerPath]: baseApi.reducer, [baseApi.reducerPath]: baseApi.reducer,
unculledZones: unculledZonesReducer,
}, },
middleware: (getDefaultMiddleware) => middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(baseApi.middleware), getDefaultMiddleware().concat(baseApi.middleware),

View File

@ -0,0 +1,47 @@
import { UnculledZone } from "../../api/liberationApi";
import { selectUnculledZones } from "../../api/unculledZonesSlice";
import { useAppSelector } from "../../app/hooks";
import { LayerGroup, LayersControl, Circle } from "react-leaflet";
interface CullingExclusionCirclesProps {
zones: UnculledZone[];
}
const CullingExclusionCircles = (props: CullingExclusionCirclesProps) => {
return (
<>
<LayerGroup>
{props.zones.map((zone, idx) => {
return (
<Circle
key={idx}
center={zone.position}
radius={zone.radius}
color="#b4ff8c"
fill={false}
interactive={false}
/>
);
})}
</LayerGroup>
</>
);
};
export default function CullingExclusionZones() {
const data = useAppSelector(selectUnculledZones).zones;
var cez = <></>;
if (!data) {
console.log("Empty response when loading culling exclusion zones");
} else {
cez = (
<CullingExclusionCircles zones={data}></CullingExclusionCircles>
);
}
return (
<LayersControl.Overlay name="Culling exclusion zones">
{cez}
</LayersControl.Overlay>
);
}

View File

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

View File

@ -18,6 +18,7 @@ import { useEffect, useRef } from "react";
import { BasemapLayer } from "react-esri-leaflet"; import { BasemapLayer } from "react-esri-leaflet";
import { LayersControl, MapContainer, ScaleControl } from "react-leaflet"; import { LayersControl, MapContainer, ScaleControl } from "react-leaflet";
import Iadsnetworklayer from "../iadsnetworklayer"; import Iadsnetworklayer from "../iadsnetworklayer";
import CullingExclusionZones from "../cullingexclusionzones/CullingExclusionZones"
import LeafletRuler from "../ruler/Ruler"; import LeafletRuler from "../ruler/Ruler";
export default function LiberationMap() { export default function LiberationMap() {
@ -109,6 +110,7 @@ export default function LiberationMap() {
<NavMeshLayer blue={false} /> <NavMeshLayer blue={false} />
</LayersControl.Overlay> </LayersControl.Overlay>
<TerrainZonesLayers /> <TerrainZonesLayers />
<CullingExclusionZones />
<WaypointDebugZonesControls /> <WaypointDebugZonesControls />
</LayersControl> </LayersControl>
</MapContainer> </MapContainer>

View File

@ -217,7 +217,7 @@ class Game:
naming.namegen = self.name_generator naming.namegen = self.name_generator
LuaPluginManager.load_settings(self.settings) LuaPluginManager.load_settings(self.settings)
ObjectiveDistanceCache.set_theater(self.theater) ObjectiveDistanceCache.set_theater(self.theater)
self.compute_unculled_zones() self.compute_unculled_zones(GameUpdateEvents())
if not game_still_initializing: if not game_still_initializing:
# We don't need to push events that happen during load. The UI will fully # We don't need to push events that happen during load. The UI will fully
# reset when we're done. # reset when we're done.
@ -417,7 +417,7 @@ class Game:
# Update cull zones # Update cull zones
with logged_duration("Computing culling positions"): with logged_duration("Computing culling positions"):
self.compute_unculled_zones() self.compute_unculled_zones(events)
def message(self, title: str, text: str = "") -> None: def message(self, title: str, text: str = "") -> None:
self.informations.append(Information(title, text, turn=self.turn)) self.informations.append(Information(title, text, turn=self.turn))
@ -459,7 +459,7 @@ class Game:
def navmesh_for(self, player: bool) -> NavMesh: def navmesh_for(self, player: bool) -> NavMesh:
return self.coalition_for(player).nav_mesh return self.coalition_for(player).nav_mesh
def compute_unculled_zones(self) -> None: def compute_unculled_zones(self, events: GameUpdateEvents) -> None:
""" """
Compute the current conflict position(s) used for culling calculation Compute the current conflict position(s) used for culling calculation
""" """
@ -514,6 +514,7 @@ class Game:
zones.append(package.target.position) zones.append(package.target.position)
self.__culling_zones = zones self.__culling_zones = zones
events.update_unculled_zones()
def add_destroyed_units(self, data: dict[str, Union[float, str]]) -> None: def add_destroyed_units(self, data: dict[str, Union[float, str]]) -> None:
pos = Point( pos = Point(

View File

@ -8,7 +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.mapzones.models import ThreatZoneContainerJs, UnculledZoneJs
from game.server.navmesh.models import NavMeshesJs 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
@ -28,6 +28,7 @@ class GameJs(BaseModel):
threat_zones: ThreatZoneContainerJs threat_zones: ThreatZoneContainerJs
navmeshes: NavMeshesJs navmeshes: NavMeshesJs
map_center: LeafletPoint | None map_center: LeafletPoint | None
unculled_zones: list[UnculledZoneJs]
class Config: class Config:
title = "Game" title = "Game"
@ -44,4 +45,5 @@ class GameJs(BaseModel):
threat_zones=ThreatZoneContainerJs.for_game(game), threat_zones=ThreatZoneContainerJs.for_game(game),
navmeshes=NavMeshesJs.from_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(),
unculled_zones=UnculledZoneJs.from_game(game),
) )

View File

@ -28,6 +28,16 @@ class UnculledZoneJs(BaseModel):
class Config: class Config:
title = "UnculledZone" title = "UnculledZone"
@staticmethod
def from_game(game: Game) -> list[UnculledZoneJs]:
return [
UnculledZoneJs(
position=zone.latlng(),
radius=game.settings.perf_culling_distance * 1000,
)
for zone in game.get_culling_zones()
]
class ThreatZonesJs(BaseModel): class ThreatZonesJs(BaseModel):
full: list[LeafletPoly] full: list[LeafletPoly]

View File

@ -27,12 +27,7 @@ def get_terrain(game: Game = Depends(GameContext.require)) -> MapZonesJs:
def get_unculled_zones( def get_unculled_zones(
game: Game = Depends(GameContext.require), game: Game = Depends(GameContext.require),
) -> list[UnculledZoneJs]: ) -> list[UnculledZoneJs]:
return [ return UnculledZoneJs.from_game(game)
UnculledZoneJs(
position=zone.latlng(), radius=game.settings.perf_culling_distance * 1000
)
for zone in game.get_culling_zones()
]
@router.get( @router.get(

View File

@ -22,6 +22,7 @@ from PySide2.QtWidgets import (
import qt_ui.uiconstants as CONST import qt_ui.uiconstants as CONST
from game.game import Game from game.game import Game
from game.server import EventStream
from game.settings import ( from game.settings import (
BooleanOption, BooleanOption,
BoundedFloatOption, BoundedFloatOption,
@ -31,6 +32,7 @@ from game.settings import (
OptionDescription, OptionDescription,
Settings, Settings,
) )
from game.sim import GameUpdateEvents
from qt_ui.widgets.QLabeledWidget import QLabeledWidget from qt_ui.widgets.QLabeledWidget import QLabeledWidget
from qt_ui.widgets.spinsliders import FloatSpinSlider, TimeInputs from qt_ui.widgets.spinsliders import FloatSpinSlider, TimeInputs
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
@ -356,7 +358,9 @@ class QSettingsWindow(QDialog):
self.cheat_options.show_base_capture_cheat self.cheat_options.show_base_capture_cheat
) )
self.game.compute_unculled_zones() events = GameUpdateEvents()
self.game.compute_unculled_zones(events)
EventStream.put_nowait(events)
GameUpdateSignal.get_instance().updateGame(self.game) GameUpdateSignal.get_instance().updateGame(self.game)
def onSelectionChanged(self): def onSelectionChanged(self):