mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Partial implementation of TGO display.
No threat/detection circles yet. https://github.com/dcs-liberation/dcs_liberation/issues/2039
This commit is contained in:
parent
1cd77a4a77
commit
64b01c471b
23
client/src/api/tgo.ts
Normal file
23
client/src/api/tgo.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { LatLng } from "leaflet";
|
||||||
|
|
||||||
|
export enum TgoType {
|
||||||
|
AIR_DEFENSE = "Air defenses",
|
||||||
|
FACTORY = "Factories",
|
||||||
|
SHIP = "Ships",
|
||||||
|
OTHER = "Other ground objects",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tgo {
|
||||||
|
name: string;
|
||||||
|
control_point_name: string;
|
||||||
|
category: string;
|
||||||
|
blue: boolean;
|
||||||
|
position: LatLng;
|
||||||
|
units: string[];
|
||||||
|
threat_ranges: number[];
|
||||||
|
detection_ranges: number[];
|
||||||
|
dead: boolean;
|
||||||
|
sidc: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Tgo;
|
||||||
51
client/src/api/tgosSlice.ts
Normal file
51
client/src/api/tgosSlice.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { Tgo, TgoType } from "./tgo";
|
||||||
|
|
||||||
|
import { RootState } from "../app/store";
|
||||||
|
|
||||||
|
interface TgosState {
|
||||||
|
tgosByType: { [key: string]: Tgo[] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: TgosState = {
|
||||||
|
tgosByType: Object.fromEntries(
|
||||||
|
Object.values(TgoType).map((key) => [key, []])
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tgosSlice = createSlice({
|
||||||
|
name: "tgos",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setTgos: (state, action: PayloadAction<Tgo[]>) => {
|
||||||
|
state.tgosByType = initialState.tgosByType;
|
||||||
|
for (const key of Object.values(TgoType)) {
|
||||||
|
state.tgosByType[key] = [];
|
||||||
|
}
|
||||||
|
for (const tgo of action.payload) {
|
||||||
|
var type;
|
||||||
|
switch (tgo.category) {
|
||||||
|
case "aa":
|
||||||
|
type = TgoType.AIR_DEFENSE;
|
||||||
|
break;
|
||||||
|
case "factory":
|
||||||
|
type = TgoType.FACTORY;
|
||||||
|
break;
|
||||||
|
case "ship":
|
||||||
|
type = TgoType.SHIP;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
type = TgoType.OTHER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state.tgosByType[type].push(tgo);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setTgos } = tgosSlice.actions;
|
||||||
|
|
||||||
|
export const selectTgos = (state: RootState) => state.tgos;
|
||||||
|
|
||||||
|
export default tgosSlice.reducer;
|
||||||
@ -2,11 +2,13 @@ import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
|
|||||||
|
|
||||||
import controlPointsReducer from "../api/controlPointsSlice";
|
import controlPointsReducer from "../api/controlPointsSlice";
|
||||||
import flightsReducer from "../api/flightsSlice";
|
import flightsReducer from "../api/flightsSlice";
|
||||||
|
import tgosReducer from "../api/tgosSlice";
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
flights: flightsReducer,
|
flights: flightsReducer,
|
||||||
controlPoints: controlPointsReducer,
|
controlPoints: controlPointsReducer,
|
||||||
|
tgos: tgosReducer,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { BasemapLayer } from "react-esri-leaflet";
|
|||||||
import ControlPointsLayer from "../controlpointslayer";
|
import ControlPointsLayer from "../controlpointslayer";
|
||||||
import FlightPlansLayer from "../flightplanslayer";
|
import FlightPlansLayer from "../flightplanslayer";
|
||||||
import { LatLng } from "leaflet";
|
import { LatLng } from "leaflet";
|
||||||
|
import { TgoType } from "../../api/tgo";
|
||||||
|
import TgosLayer from "../tgoslayer/TgosLayer";
|
||||||
|
|
||||||
interface GameProps {
|
interface GameProps {
|
||||||
mapCenter: LatLng;
|
mapCenter: LatLng;
|
||||||
@ -28,6 +30,13 @@ export default function LiberationMap(props: GameProps) {
|
|||||||
<LayersControl.Overlay name="Control points" checked>
|
<LayersControl.Overlay name="Control points" checked>
|
||||||
<ControlPointsLayer />
|
<ControlPointsLayer />
|
||||||
</LayersControl.Overlay>
|
</LayersControl.Overlay>
|
||||||
|
{Object.values(TgoType).map((type) => {
|
||||||
|
return (
|
||||||
|
<LayersControl.Overlay name={type} checked>
|
||||||
|
<TgosLayer type={type as TgoType} />
|
||||||
|
</LayersControl.Overlay>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<LayersControl.Overlay name="All blue flight plans" checked>
|
<LayersControl.Overlay name="All blue flight plans" checked>
|
||||||
<FlightPlansLayer blue={true} />
|
<FlightPlansLayer blue={true} />
|
||||||
</LayersControl.Overlay>
|
</LayersControl.Overlay>
|
||||||
|
|||||||
37
client/src/components/tgos/Tgo.tsx
Normal file
37
client/src/components/tgos/Tgo.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Icon, Point } from "leaflet";
|
||||||
|
import { Marker, Popup } from "react-leaflet";
|
||||||
|
|
||||||
|
import { Symbol as MilSymbol } from "milsymbol";
|
||||||
|
import { Tgo as TgoModel } from "../../api/tgo";
|
||||||
|
|
||||||
|
function iconForTgo(cp: TgoModel) {
|
||||||
|
const symbol = new MilSymbol(cp.sidc, {
|
||||||
|
size: 24,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Icon({
|
||||||
|
iconUrl: symbol.toDataURL(),
|
||||||
|
iconAnchor: new Point(symbol.getAnchor().x, symbol.getAnchor().y),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TgoProps {
|
||||||
|
tgo: TgoModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Tgo(props: TgoProps) {
|
||||||
|
return (
|
||||||
|
<Marker position={props.tgo.position} icon={iconForTgo(props.tgo)}>
|
||||||
|
<Popup>
|
||||||
|
{`${props.tgo.name} (${props.tgo.control_point_name})`}
|
||||||
|
<br />
|
||||||
|
{props.tgo.units.map((unit) => (
|
||||||
|
<>
|
||||||
|
{unit}
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</Popup>
|
||||||
|
</Marker>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
client/src/components/tgos/index.ts
Normal file
1
client/src/components/tgos/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from "./Tgo";
|
||||||
22
client/src/components/tgoslayer/TgosLayer.tsx
Normal file
22
client/src/components/tgoslayer/TgosLayer.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { LayerGroup } from "react-leaflet";
|
||||||
|
import Tgo from "../tgos/Tgo";
|
||||||
|
import { TgoType } from "../../api/tgo";
|
||||||
|
import { selectTgos } from "../../api/tgosSlice";
|
||||||
|
import { useAppSelector } from "../../app/hooks";
|
||||||
|
|
||||||
|
interface TgosLayerProps {
|
||||||
|
type: TgoType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TgosLayer(props: TgosLayerProps) {
|
||||||
|
const allTgos = useAppSelector(selectTgos);
|
||||||
|
const tgos = allTgos.tgosByType[props.type];
|
||||||
|
console.dir(Object.entries(TgoType));
|
||||||
|
return (
|
||||||
|
<LayerGroup>
|
||||||
|
{tgos.map((tgo) => {
|
||||||
|
return <Tgo key={tgo.name} tgo={tgo} />;
|
||||||
|
})}
|
||||||
|
</LayerGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
client/src/components/tgoslayer/index.ts
Normal file
1
client/src/components/tgoslayer/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from "./TgosLayer";
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import { ControlPoint } from "../api/controlpoint";
|
import { ControlPoint } from "../api/controlpoint";
|
||||||
import { Flight } from "../api/flight";
|
import { Flight } from "../api/flight";
|
||||||
|
import Tgo from "../api/tgo";
|
||||||
import backend from "../api/backend";
|
import backend from "../api/backend";
|
||||||
import { registerFlight } from "../api/flightsSlice";
|
import { registerFlight } from "../api/flightsSlice";
|
||||||
import { setControlPoints } from "../api/controlPointsSlice";
|
import { setControlPoints } from "../api/controlPointsSlice";
|
||||||
|
import { setTgos } from "../api/tgosSlice";
|
||||||
import { useAppDispatch } from "../app/hooks";
|
import { useAppDispatch } from "../app/hooks";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
@ -21,6 +23,14 @@ export const useInitialGameState = () => {
|
|||||||
dispatch(setControlPoints(response.data as ControlPoint[]));
|
dispatch(setControlPoints(response.data as ControlPoint[]));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
backend
|
||||||
|
.get("/tgos")
|
||||||
|
.catch((error) => console.log(`Error fetching TGOs: ${error}`))
|
||||||
|
.then((response) => {
|
||||||
|
if (response != null) {
|
||||||
|
dispatch(setTgos(response.data as Tgo[]));
|
||||||
|
}
|
||||||
|
});
|
||||||
backend
|
backend
|
||||||
.get("/flights?with_waypoints=true")
|
.get("/flights?with_waypoints=true")
|
||||||
.catch((error) => console.log(`Error fetching flights: ${error}`))
|
.catch((error) => console.log(`Error fetching flights: ${error}`))
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from . import (
|
|||||||
flights,
|
flights,
|
||||||
mapzones,
|
mapzones,
|
||||||
navmesh,
|
navmesh,
|
||||||
|
tgos,
|
||||||
waypoints,
|
waypoints,
|
||||||
)
|
)
|
||||||
from .security import ApiKeyManager
|
from .security import ApiKeyManager
|
||||||
@ -24,6 +25,7 @@ app.include_router(eventstream.router)
|
|||||||
app.include_router(flights.router)
|
app.include_router(flights.router)
|
||||||
app.include_router(mapzones.router)
|
app.include_router(mapzones.router)
|
||||||
app.include_router(navmesh.router)
|
app.include_router(navmesh.router)
|
||||||
|
app.include_router(tgos.router)
|
||||||
app.include_router(waypoints.router)
|
app.include_router(waypoints.router)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
game/server/tgos/__init__.py
Normal file
1
game/server/tgos/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .routes import router
|
||||||
40
game/server/tgos/models.py
Normal file
40
game/server/tgos/models.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from game.server.leaflet import LeafletPoint
|
||||||
|
from game.theater import TheaterGroundObject
|
||||||
|
|
||||||
|
|
||||||
|
class TgoJs(BaseModel):
|
||||||
|
name: str
|
||||||
|
control_point_name: str
|
||||||
|
category: str
|
||||||
|
blue: bool
|
||||||
|
position: LeafletPoint
|
||||||
|
units: list[str]
|
||||||
|
threat_ranges: list[float]
|
||||||
|
detection_ranges: list[float]
|
||||||
|
dead: bool
|
||||||
|
sidc: str
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def for_tgo(tgo: TheaterGroundObject) -> TgoJs:
|
||||||
|
if not tgo.might_have_aa:
|
||||||
|
threat_ranges = []
|
||||||
|
detection_ranges = []
|
||||||
|
else:
|
||||||
|
threat_ranges = [tgo.threat_range(group).meters for group in tgo.groups]
|
||||||
|
detection_ranges = [tgo.threat_range(group).meters for group in tgo.groups]
|
||||||
|
return TgoJs(
|
||||||
|
name=tgo.name,
|
||||||
|
control_point_name=tgo.control_point.name,
|
||||||
|
category=tgo.category,
|
||||||
|
blue=tgo.control_point.captured,
|
||||||
|
position=tgo.position.latlng(),
|
||||||
|
units=[unit.display_name for unit in tgo.units],
|
||||||
|
threat_ranges=threat_ranges,
|
||||||
|
detection_ranges=detection_ranges,
|
||||||
|
dead=tgo.is_dead,
|
||||||
|
sidc=str(tgo.sidc()),
|
||||||
|
)
|
||||||
17
game/server/tgos/routes.py
Normal file
17
game/server/tgos/routes.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
|
from game import Game
|
||||||
|
from .models import TgoJs
|
||||||
|
from ..dependencies import GameContext
|
||||||
|
|
||||||
|
router: APIRouter = APIRouter(prefix="/tgos")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
def list_tgos(game: Game = Depends(GameContext.get)) -> list[TgoJs]:
|
||||||
|
tgos = []
|
||||||
|
for control_point in game.theater.controlpoints:
|
||||||
|
for tgo in control_point.connected_objectives:
|
||||||
|
if not tgo.is_control_point:
|
||||||
|
tgos.append(TgoJs.for_tgo(tgo))
|
||||||
|
return tgos
|
||||||
Loading…
x
Reference in New Issue
Block a user