mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Handle map reset when the game is loaded/unloaded.
https://github.com/dcs-liberation/dcs_liberation/issues/2039 Partial fix for https://github.com/dcs-liberation/dcs_liberation/issues/2045 (now works in the new map, old one not fixed yet).
This commit is contained in:
parent
995e28cb32
commit
73fcfcec7b
@ -1,17 +1,14 @@
|
|||||||
import LiberationMap from "./components/liberationmap";
|
import LiberationMap from "./components/liberationmap";
|
||||||
import useEventStream from "./hooks/useEventSteam";
|
import useEventStream from "./hooks/useEventSteam";
|
||||||
import useInitialGameState from "./hooks/useInitialGameState";
|
import useInitialGameState from "./hooks/useInitialGameState";
|
||||||
import { LatLng } from "leaflet";
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const mapCenter: LatLng = new LatLng(25.58, 54.9);
|
|
||||||
|
|
||||||
useInitialGameState();
|
useInitialGameState();
|
||||||
useEventStream();
|
useEventStream();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<LiberationMap mapCenter={mapCenter} />
|
<LiberationMap />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
5
client/src/api/actions.ts
Normal file
5
client/src/api/actions.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import Game from "./game";
|
||||||
|
import { createAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
export const gameLoaded = createAction<Game>("game/loaded");
|
||||||
|
export const gameUnloaded = createAction("game/unloaded");
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { RootState } from "../app/store";
|
import { RootState } from "../app/store";
|
||||||
|
import { gameLoaded, gameUnloaded } from "./actions";
|
||||||
import { ControlPoint } from "./controlpoint";
|
import { ControlPoint } from "./controlpoint";
|
||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
@ -14,21 +15,28 @@ export const controlPointsSlice = createSlice({
|
|||||||
name: "controlPoints",
|
name: "controlPoints",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setControlPoints: (state, action: PayloadAction<ControlPoint[]>) => {
|
|
||||||
state.controlPoints = {};
|
|
||||||
for (const cp of action.payload) {
|
|
||||||
state.controlPoints[cp.id] = cp;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateControlPoint: (state, action: PayloadAction<ControlPoint>) => {
|
updateControlPoint: (state, action: PayloadAction<ControlPoint>) => {
|
||||||
const cp = action.payload;
|
const cp = action.payload;
|
||||||
state.controlPoints[cp.id] = cp;
|
state.controlPoints[cp.id] = cp;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(gameLoaded, (state, action) => {
|
||||||
|
state.controlPoints = action.payload.control_points.reduce(
|
||||||
|
(acc: { [key: number]: ControlPoint }, curr) => {
|
||||||
|
acc[curr.id] = curr;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
builder.addCase(gameUnloaded, (state) => {
|
||||||
|
state.controlPoints = {};
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setControlPoints, updateControlPoint } =
|
export const { updateControlPoint } = controlPointsSlice.actions;
|
||||||
controlPointsSlice.actions;
|
|
||||||
|
|
||||||
export const selectControlPoints = (state: RootState) => state.controlPoints;
|
export const selectControlPoints = (state: RootState) => state.controlPoints;
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { AppDispatch } from "../app/store";
|
import { AppDispatch } from "../app/store";
|
||||||
|
import { gameUnloaded } from "./actions";
|
||||||
import backend from "./backend";
|
import backend from "./backend";
|
||||||
import Combat from "./combat";
|
import Combat from "./combat";
|
||||||
import { endCombat, newCombat, updateCombat } from "./combatSlice";
|
import { endCombat, newCombat, updateCombat } from "./combatSlice";
|
||||||
@ -19,6 +20,7 @@ import {
|
|||||||
updateFrontLine,
|
updateFrontLine,
|
||||||
} from "./frontLinesSlice";
|
} from "./frontLinesSlice";
|
||||||
import FrontLine from "./frontline";
|
import FrontLine from "./frontline";
|
||||||
|
import reloadGameState from "./gamestate";
|
||||||
import Tgo from "./tgo";
|
import Tgo from "./tgo";
|
||||||
import { updateTgo } from "./tgosSlice";
|
import { updateTgo } from "./tgosSlice";
|
||||||
import { LatLng } from "leaflet";
|
import { LatLng } from "leaflet";
|
||||||
@ -41,6 +43,8 @@ interface GameUpdateEvents {
|
|||||||
deleted_front_lines: string[];
|
deleted_front_lines: string[];
|
||||||
updated_tgos: string[];
|
updated_tgos: string[];
|
||||||
updated_control_points: number[];
|
updated_control_points: number[];
|
||||||
|
reset_on_map_center: LatLng | null;
|
||||||
|
game_unloaded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const handleStreamedEvents = (
|
export const handleStreamedEvents = (
|
||||||
@ -114,4 +118,12 @@ export const handleStreamedEvents = (
|
|||||||
dispatch(updateControlPoint(cp));
|
dispatch(updateControlPoint(cp));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (events.reset_on_map_center != null) {
|
||||||
|
reloadGameState(dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events.game_unloaded) {
|
||||||
|
dispatch(gameUnloaded());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { RootState } from "../app/store";
|
import { RootState } from "../app/store";
|
||||||
|
import { gameLoaded, gameUnloaded } from "./actions";
|
||||||
import { Flight } from "./flight";
|
import { Flight } from "./flight";
|
||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
import { LatLng } from "leaflet";
|
import { LatLng } from "leaflet";
|
||||||
@ -17,9 +18,6 @@ export const flightsSlice = createSlice({
|
|||||||
name: "flights",
|
name: "flights",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
clearFlights: (state) => {
|
|
||||||
state.flights = {};
|
|
||||||
},
|
|
||||||
registerFlight: (state, action: PayloadAction<Flight>) => {
|
registerFlight: (state, action: PayloadAction<Flight>) => {
|
||||||
const flight = action.payload;
|
const flight = action.payload;
|
||||||
if (flight.id in state.flights) {
|
if (flight.id in state.flights) {
|
||||||
@ -51,10 +49,25 @@ export const flightsSlice = createSlice({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(gameLoaded, (state, action) => {
|
||||||
|
state.selected = null;
|
||||||
|
state.flights = action.payload.flights.reduce(
|
||||||
|
(acc: { [key: string]: Flight }, curr) => {
|
||||||
|
acc[curr.id] = curr;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
builder.addCase(gameUnloaded, (state) => {
|
||||||
|
state.selected = null;
|
||||||
|
state.flights = {};
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
clearFlights,
|
|
||||||
registerFlight,
|
registerFlight,
|
||||||
unregisterFlight,
|
unregisterFlight,
|
||||||
updateFlight,
|
updateFlight,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { RootState } from "../app/store";
|
import { RootState } from "../app/store";
|
||||||
|
import { gameLoaded, gameUnloaded } from "./actions";
|
||||||
import FrontLine from "./frontline";
|
import FrontLine from "./frontline";
|
||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
@ -14,12 +15,6 @@ export const frontLinesSlice = createSlice({
|
|||||||
name: "frontLines",
|
name: "frontLines",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setFrontLines: (state, action: PayloadAction<FrontLine[]>) => {
|
|
||||||
state.fronts = {};
|
|
||||||
for (const front of action.payload) {
|
|
||||||
state.fronts[front.id] = front;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addFrontLine: (state, action: PayloadAction<FrontLine>) => {
|
addFrontLine: (state, action: PayloadAction<FrontLine>) => {
|
||||||
const front = action.payload;
|
const front = action.payload;
|
||||||
state.fronts[front.id] = front;
|
state.fronts[front.id] = front;
|
||||||
@ -32,9 +27,23 @@ export const frontLinesSlice = createSlice({
|
|||||||
delete state.fronts[action.payload];
|
delete state.fronts[action.payload];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(gameLoaded, (state, action) => {
|
||||||
|
state.fronts = action.payload.front_lines.reduce(
|
||||||
|
(acc: { [key: string]: FrontLine }, curr) => {
|
||||||
|
acc[curr.id] = curr;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
builder.addCase(gameUnloaded, (state) => {
|
||||||
|
state.fronts = {};
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setFrontLines, addFrontLine, updateFrontLine, deleteFrontLine } =
|
export const { addFrontLine, updateFrontLine, deleteFrontLine } =
|
||||||
frontLinesSlice.actions;
|
frontLinesSlice.actions;
|
||||||
|
|
||||||
export const selectFrontLines = (state: RootState) => state.frontLines;
|
export const selectFrontLines = (state: RootState) => state.frontLines;
|
||||||
|
|||||||
15
client/src/api/game.ts
Normal file
15
client/src/api/game.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { ControlPoint } from "./controlpoint";
|
||||||
|
import { Flight } from "./flight";
|
||||||
|
import FrontLine from "./frontline";
|
||||||
|
import SupplyRoute from "./supplyroute";
|
||||||
|
import Tgo from "./tgo";
|
||||||
|
import { LatLngLiteral } from "leaflet";
|
||||||
|
|
||||||
|
export default interface Game {
|
||||||
|
control_points: ControlPoint[];
|
||||||
|
tgos: Tgo[];
|
||||||
|
supply_routes: SupplyRoute[];
|
||||||
|
front_lines: FrontLine[];
|
||||||
|
flights: Flight[];
|
||||||
|
map_center: LatLngLiteral;
|
||||||
|
}
|
||||||
17
client/src/api/gamestate.ts
Normal file
17
client/src/api/gamestate.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { AppDispatch } from "../app/store";
|
||||||
|
import { gameLoaded, gameUnloaded } from "./actions";
|
||||||
|
import backend from "./backend";
|
||||||
|
import Game from "./game";
|
||||||
|
|
||||||
|
export default function reloadGameState(dispatch: AppDispatch) {
|
||||||
|
backend
|
||||||
|
.get("/game")
|
||||||
|
.catch((error) => console.log(`Error fetching game state: ${error}`))
|
||||||
|
.then((response) => {
|
||||||
|
if (response == null || response.data == null) {
|
||||||
|
dispatch(gameUnloaded());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(gameLoaded(response.data as Game));
|
||||||
|
});
|
||||||
|
}
|
||||||
30
client/src/api/mapSlice.ts
Normal file
30
client/src/api/mapSlice.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { RootState } from "../app/store";
|
||||||
|
import { gameLoaded, gameUnloaded } from "./actions";
|
||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { LatLngLiteral } from "leaflet";
|
||||||
|
|
||||||
|
interface MapState {
|
||||||
|
center: LatLngLiteral;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: MapState = {
|
||||||
|
center: { lat: 0, lng: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapSlice = createSlice({
|
||||||
|
name: "map",
|
||||||
|
initialState: initialState,
|
||||||
|
reducers: {},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(gameLoaded, (state, action) => {
|
||||||
|
state.center = action.payload.map_center;
|
||||||
|
});
|
||||||
|
builder.addCase(gameUnloaded, (state) => {
|
||||||
|
state.center = { lat: 0, lng: 0 };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const selectMapCenter = (state: RootState) => state.map.center;
|
||||||
|
|
||||||
|
export default mapSlice.reducer;
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { RootState } from "../app/store";
|
import { RootState } from "../app/store";
|
||||||
|
import { gameLoaded, gameUnloaded } from "./actions";
|
||||||
import SupplyRoute from "./supplyroute";
|
import SupplyRoute from "./supplyroute";
|
||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
interface SupplyRoutesState {
|
interface SupplyRoutesState {
|
||||||
routes: SupplyRoute[];
|
routes: SupplyRoute[];
|
||||||
@ -13,15 +14,17 @@ const initialState: SupplyRoutesState = {
|
|||||||
export const supplyRoutesSlice = createSlice({
|
export const supplyRoutesSlice = createSlice({
|
||||||
name: "supplyRoutes",
|
name: "supplyRoutes",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {},
|
||||||
setSupplyRoutes: (state, action: PayloadAction<SupplyRoute[]>) => {
|
extraReducers: (builder) => {
|
||||||
state.routes = action.payload;
|
builder.addCase(gameLoaded, (state, action) => {
|
||||||
},
|
state.routes = action.payload.supply_routes;
|
||||||
|
});
|
||||||
|
builder.addCase(gameUnloaded, (state) => {
|
||||||
|
state.routes = [];
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setSupplyRoutes } = supplyRoutesSlice.actions;
|
|
||||||
|
|
||||||
export const selectSupplyRoutes = (state: RootState) => state.supplyRoutes;
|
export const selectSupplyRoutes = (state: RootState) => state.supplyRoutes;
|
||||||
|
|
||||||
export default supplyRoutesSlice.reducer;
|
export default supplyRoutesSlice.reducer;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { RootState } from "../app/store";
|
import { RootState } from "../app/store";
|
||||||
|
import { gameLoaded, gameUnloaded } from "./actions";
|
||||||
import { Tgo } from "./tgo";
|
import { Tgo } from "./tgo";
|
||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
@ -14,20 +15,28 @@ export const tgosSlice = createSlice({
|
|||||||
name: "tgos",
|
name: "tgos",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setTgos: (state, action: PayloadAction<Tgo[]>) => {
|
|
||||||
state.tgos = {};
|
|
||||||
for (const tgo of action.payload) {
|
|
||||||
state.tgos[tgo.id] = tgo;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateTgo: (state, action: PayloadAction<Tgo>) => {
|
updateTgo: (state, action: PayloadAction<Tgo>) => {
|
||||||
const tgo = action.payload;
|
const tgo = action.payload;
|
||||||
state.tgos[tgo.id] = tgo;
|
state.tgos[tgo.id] = tgo;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(gameLoaded, (state, action) => {
|
||||||
|
state.tgos = action.payload.tgos.reduce(
|
||||||
|
(acc: { [key: string]: Tgo }, curr) => {
|
||||||
|
acc[curr.id] = curr;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
builder.addCase(gameUnloaded, (state) => {
|
||||||
|
state.tgos = {};
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setTgos, updateTgo } = tgosSlice.actions;
|
export const { updateTgo } = tgosSlice.actions;
|
||||||
|
|
||||||
export const selectTgos = (state: RootState) => state.tgos;
|
export const selectTgos = (state: RootState) => state.tgos;
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import combatReducer from "../api/combatSlice";
|
|||||||
import controlPointsReducer from "../api/controlPointsSlice";
|
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 supplyRoutesReducer from "../api/supplyRoutesSlice";
|
import supplyRoutesReducer from "../api/supplyRoutesSlice";
|
||||||
import tgosReducer from "../api/tgosSlice";
|
import tgosReducer from "../api/tgosSlice";
|
||||||
import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
|
import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
|
||||||
@ -13,6 +14,7 @@ export const store = configureStore({
|
|||||||
controlPoints: controlPointsReducer,
|
controlPoints: controlPointsReducer,
|
||||||
flights: flightsReducer,
|
flights: flightsReducer,
|
||||||
frontLines: frontLinesReducer,
|
frontLines: frontLinesReducer,
|
||||||
|
map: mapReducer,
|
||||||
supplyRoutes: supplyRoutesReducer,
|
supplyRoutes: supplyRoutesReducer,
|
||||||
tgos: tgosReducer,
|
tgos: tgosReducer,
|
||||||
[apiSlice.reducerPath]: apiSlice.reducer,
|
[apiSlice.reducerPath]: apiSlice.reducer,
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { selectMapCenter } from "../../api/mapSlice";
|
||||||
|
import { useAppSelector } from "../../app/hooks";
|
||||||
import AircraftLayer from "../aircraftlayer";
|
import AircraftLayer from "../aircraftlayer";
|
||||||
import AirDefenseRangeLayer from "../airdefenserangelayer";
|
import AirDefenseRangeLayer from "../airdefenserangelayer";
|
||||||
import CombatLayer from "../combatlayer";
|
import CombatLayer from "../combatlayer";
|
||||||
@ -7,17 +9,23 @@ import FrontLinesLayer from "../frontlineslayer";
|
|||||||
import SupplyRoutesLayer from "../supplyrouteslayer";
|
import SupplyRoutesLayer from "../supplyrouteslayer";
|
||||||
import TgosLayer from "../tgoslayer/TgosLayer";
|
import TgosLayer from "../tgoslayer/TgosLayer";
|
||||||
import "./LiberationMap.css";
|
import "./LiberationMap.css";
|
||||||
import { LatLng } from "leaflet";
|
import { Map } from "leaflet";
|
||||||
|
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";
|
||||||
|
|
||||||
interface GameProps {
|
export default function LiberationMap() {
|
||||||
mapCenter: LatLng;
|
const map = useRef<Map>();
|
||||||
}
|
const mapCenter = useAppSelector(selectMapCenter);
|
||||||
|
useEffect(() => {
|
||||||
export default function LiberationMap(props: GameProps) {
|
map.current?.setView(mapCenter, 8, { animate: true, duration: 1 });
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<MapContainer zoom={8} center={props.mapCenter} zoomControl={false}>
|
<MapContainer
|
||||||
|
zoom={8}
|
||||||
|
zoomControl={false}
|
||||||
|
whenCreated={(mapInstance) => (map.current = mapInstance)}
|
||||||
|
>
|
||||||
<ScaleControl />
|
<ScaleControl />
|
||||||
<LayersControl collapsed={false}>
|
<LayersControl collapsed={false}>
|
||||||
<LayersControl.BaseLayer name="Imagery Clarity" checked>
|
<LayersControl.BaseLayer name="Imagery Clarity" checked>
|
||||||
|
|||||||
@ -1,14 +1,4 @@
|
|||||||
import backend from "../api/backend";
|
import reloadGameState from "../api/gamestate";
|
||||||
import { setControlPoints } from "../api/controlPointsSlice";
|
|
||||||
import { ControlPoint } from "../api/controlpoint";
|
|
||||||
import { Flight } from "../api/flight";
|
|
||||||
import { registerFlight } from "../api/flightsSlice";
|
|
||||||
import { setFrontLines } from "../api/frontLinesSlice";
|
|
||||||
import FrontLine from "../api/frontline";
|
|
||||||
import { setSupplyRoutes } from "../api/supplyRoutesSlice";
|
|
||||||
import SupplyRoute from "../api/supplyroute";
|
|
||||||
import Tgo from "../api/tgo";
|
|
||||||
import { setTgos } from "../api/tgosSlice";
|
|
||||||
import { useAppDispatch } from "../app/hooks";
|
import { useAppDispatch } from "../app/hooks";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
@ -19,48 +9,7 @@ import { useEffect } from "react";
|
|||||||
export const useInitialGameState = () => {
|
export const useInitialGameState = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
backend
|
reloadGameState(dispatch);
|
||||||
.get("/control-points")
|
|
||||||
.catch((error) => console.log(`Error fetching control points: ${error}`))
|
|
||||||
.then((response) => {
|
|
||||||
if (response != null) {
|
|
||||||
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
|
|
||||||
.get("/supply-routes")
|
|
||||||
.catch((error) => console.log(`Error fetching supply routes: ${error}`))
|
|
||||||
.then((response) => {
|
|
||||||
if (response != null) {
|
|
||||||
dispatch(setSupplyRoutes(response.data as SupplyRoute[]));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
backend
|
|
||||||
.get("/front-lines")
|
|
||||||
.catch((error) => console.log(`Error fetching front-lines: ${error}`))
|
|
||||||
.then((response) => {
|
|
||||||
if (response != null) {
|
|
||||||
dispatch(setFrontLines(response.data as FrontLine[]));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
backend
|
|
||||||
.get("/flights?with_waypoints=true")
|
|
||||||
.catch((error) => console.log(`Error fetching flights: ${error}`))
|
|
||||||
.then((response) => {
|
|
||||||
if (response != null) {
|
|
||||||
for (const flight of response.data) {
|
|
||||||
dispatch(registerFlight(flight as Flight));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from . import (
|
|||||||
eventstream,
|
eventstream,
|
||||||
flights,
|
flights,
|
||||||
frontlines,
|
frontlines,
|
||||||
|
game,
|
||||||
mapzones,
|
mapzones,
|
||||||
navmesh,
|
navmesh,
|
||||||
qt,
|
qt,
|
||||||
@ -27,6 +28,7 @@ app.include_router(debuggeometries.router)
|
|||||||
app.include_router(eventstream.router)
|
app.include_router(eventstream.router)
|
||||||
app.include_router(flights.router)
|
app.include_router(flights.router)
|
||||||
app.include_router(frontlines.router)
|
app.include_router(frontlines.router)
|
||||||
|
app.include_router(game.router)
|
||||||
app.include_router(mapzones.router)
|
app.include_router(mapzones.router)
|
||||||
app.include_router(navmesh.router)
|
app.include_router(navmesh.router)
|
||||||
app.include_router(qt.router)
|
app.include_router(qt.router)
|
||||||
|
|||||||
@ -1,9 +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 LeafletPoint
|
from game.server.leaflet import LeafletPoint
|
||||||
from game.theater import ControlPoint
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game import Game
|
||||||
|
from game.theater import ControlPoint
|
||||||
|
|
||||||
|
|
||||||
class ControlPointJs(BaseModel):
|
class ControlPointJs(BaseModel):
|
||||||
@ -29,3 +34,9 @@ class ControlPointJs(BaseModel):
|
|||||||
destination=destination,
|
destination=destination,
|
||||||
sidc=str(control_point.sidc()),
|
sidc=str(control_point.sidc()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def all_in_game(game: Game) -> list[ControlPointJs]:
|
||||||
|
return [
|
||||||
|
ControlPointJs.for_control_point(cp) for cp in game.theater.controlpoints
|
||||||
|
]
|
||||||
|
|||||||
@ -13,16 +13,15 @@ router: APIRouter = APIRouter(prefix="/control-points")
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
def list_control_points(game: Game = Depends(GameContext.get)) -> list[ControlPointJs]:
|
def list_control_points(
|
||||||
control_points = []
|
game: Game = Depends(GameContext.require),
|
||||||
for control_point in game.theater.controlpoints:
|
) -> list[ControlPointJs]:
|
||||||
control_points.append(ControlPointJs.for_control_point(control_point))
|
return ControlPointJs.all_in_game(game)
|
||||||
return control_points
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{cp_id}")
|
@router.get("/{cp_id}")
|
||||||
def get_control_point(
|
def get_control_point(
|
||||||
cp_id: int, game: Game = Depends(GameContext.get)
|
cp_id: int, game: Game = Depends(GameContext.require)
|
||||||
) -> ControlPointJs:
|
) -> ControlPointJs:
|
||||||
cp = game.theater.find_control_point_by_id(cp_id)
|
cp = game.theater.find_control_point_by_id(cp_id)
|
||||||
if cp is None:
|
if cp is None:
|
||||||
@ -35,7 +34,7 @@ def get_control_point(
|
|||||||
|
|
||||||
@router.get("/{cp_id}/destination-in-range")
|
@router.get("/{cp_id}/destination-in-range")
|
||||||
def destination_in_range(
|
def destination_in_range(
|
||||||
cp_id: int, lat: float, lng: float, game: Game = Depends(GameContext.get)
|
cp_id: int, lat: float, lng: float, game: Game = Depends(GameContext.require)
|
||||||
) -> bool:
|
) -> bool:
|
||||||
cp = game.theater.find_control_point_by_id(cp_id)
|
cp = game.theater.find_control_point_by_id(cp_id)
|
||||||
if cp is None:
|
if cp is None:
|
||||||
@ -50,7 +49,7 @@ def destination_in_range(
|
|||||||
|
|
||||||
@router.put("/{cp_id}/destination")
|
@router.put("/{cp_id}/destination")
|
||||||
def set_destination(
|
def set_destination(
|
||||||
cp_id: int, destination: LeafletPoint, game: Game = Depends(GameContext.get)
|
cp_id: int, destination: LeafletPoint, game: Game = Depends(GameContext.require)
|
||||||
) -> None:
|
) -> None:
|
||||||
cp = game.theater.find_control_point_by_id(cp_id)
|
cp = game.theater.find_control_point_by_id(cp_id)
|
||||||
if cp is None:
|
if cp is None:
|
||||||
@ -79,7 +78,7 @@ def set_destination(
|
|||||||
|
|
||||||
|
|
||||||
@router.put("/{cp_id}/cancel-travel")
|
@router.put("/{cp_id}/cancel-travel")
|
||||||
def cancel_travel(cp_id: int, game: Game = Depends(GameContext.get)) -> None:
|
def cancel_travel(cp_id: int, game: Game = Depends(GameContext.require)) -> None:
|
||||||
cp = game.theater.find_control_point_by_id(cp_id)
|
cp = game.theater.find_control_point_by_id(cp_id)
|
||||||
if cp is None:
|
if cp is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|||||||
@ -10,15 +10,19 @@ router: APIRouter = APIRouter(prefix="/debug/waypoint-geometries")
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/hold/{flight_id}")
|
@router.get("/hold/{flight_id}")
|
||||||
def hold_zones(flight_id: UUID, game: Game = Depends(GameContext.get)) -> HoldZonesJs:
|
def hold_zones(
|
||||||
|
flight_id: UUID, game: Game = Depends(GameContext.require)
|
||||||
|
) -> HoldZonesJs:
|
||||||
return HoldZonesJs.for_flight(game.db.flights.get(flight_id), game)
|
return HoldZonesJs.for_flight(game.db.flights.get(flight_id), game)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/ip/{flight_id}")
|
@router.get("/ip/{flight_id}")
|
||||||
def ip_zones(flight_id: UUID, game: Game = Depends(GameContext.get)) -> IpZonesJs:
|
def ip_zones(flight_id: UUID, game: Game = Depends(GameContext.require)) -> IpZonesJs:
|
||||||
return IpZonesJs.for_flight(game.db.flights.get(flight_id), game)
|
return IpZonesJs.for_flight(game.db.flights.get(flight_id), game)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/join/{flight_id}")
|
@router.get("/join/{flight_id}")
|
||||||
def join_zones(flight_id: UUID, game: Game = Depends(GameContext.get)) -> JoinZonesJs:
|
def join_zones(
|
||||||
|
flight_id: UUID, game: Game = Depends(GameContext.require)
|
||||||
|
) -> JoinZonesJs:
|
||||||
return JoinZonesJs.for_flight(game.db.flights.get(flight_id), game)
|
return JoinZonesJs.for_flight(game.db.flights.get(flight_id), game)
|
||||||
|
|||||||
@ -17,7 +17,11 @@ class GameContext:
|
|||||||
cls._game_model = game_model
|
cls._game_model = game_model
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls) -> Game:
|
def get(cls) -> Game | None:
|
||||||
|
return cls._game_model.game
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def require(cls) -> Game:
|
||||||
if cls._game_model.game is None:
|
if cls._game_model.game is None:
|
||||||
raise RuntimeError("GameContext has no Game set")
|
raise RuntimeError("GameContext has no Game set")
|
||||||
return cls._game_model.game
|
return cls._game_model.game
|
||||||
|
|||||||
@ -33,21 +33,38 @@ class GameUpdateEventsJs(BaseModel):
|
|||||||
deleted_front_lines: set[UUID]
|
deleted_front_lines: set[UUID]
|
||||||
updated_tgos: set[UUID]
|
updated_tgos: set[UUID]
|
||||||
updated_control_points: set[int]
|
updated_control_points: set[int]
|
||||||
|
reset_on_map_center: LeafletLatLon | None
|
||||||
|
game_unloaded: bool
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_events(cls, events: GameUpdateEvents, game: Game) -> GameUpdateEventsJs:
|
def from_events(
|
||||||
|
cls, events: GameUpdateEvents, game: Game | None
|
||||||
|
) -> GameUpdateEventsJs:
|
||||||
|
|
||||||
|
# We still need to be able to send update events when there is no game loaded
|
||||||
|
# because we need to send the unload event.
|
||||||
|
new_combats = []
|
||||||
|
updated_combats = []
|
||||||
|
if game is not None:
|
||||||
|
new_combats = [
|
||||||
|
FrozenCombatJs.for_combat(c, game.theater) for c in events.new_combats
|
||||||
|
]
|
||||||
|
updated_combats = [
|
||||||
|
FrozenCombatJs.for_combat(c, game.theater)
|
||||||
|
for c in events.updated_combats
|
||||||
|
]
|
||||||
|
|
||||||
|
recenter_map = None
|
||||||
|
if events.reset_on_map_center is not None:
|
||||||
|
recenter_map = events.reset_on_map_center.as_list()
|
||||||
|
|
||||||
return GameUpdateEventsJs(
|
return GameUpdateEventsJs(
|
||||||
updated_flight_positions={
|
updated_flight_positions={
|
||||||
f[0].id: f[1].latlng().as_list()
|
f[0].id: f[1].latlng().as_list()
|
||||||
for f in events.updated_flight_positions
|
for f in events.updated_flight_positions
|
||||||
},
|
},
|
||||||
new_combats=[
|
new_combats=new_combats,
|
||||||
FrozenCombatJs.for_combat(c, game.theater) for c in events.new_combats
|
updated_combats=updated_combats,
|
||||||
],
|
|
||||||
updated_combats=[
|
|
||||||
FrozenCombatJs.for_combat(c, game.theater)
|
|
||||||
for c in events.updated_combats
|
|
||||||
],
|
|
||||||
ended_combats=[c.id for c in events.ended_combats],
|
ended_combats=[c.id for c in events.ended_combats],
|
||||||
navmesh_updates=events.navmesh_updates,
|
navmesh_updates=events.navmesh_updates,
|
||||||
unculled_zones_updated=events.unculled_zones_updated,
|
unculled_zones_updated=events.unculled_zones_updated,
|
||||||
@ -66,4 +83,6 @@ class GameUpdateEventsJs(BaseModel):
|
|||||||
deleted_front_lines=events.deleted_front_lines,
|
deleted_front_lines=events.deleted_front_lines,
|
||||||
updated_tgos=events.updated_tgos,
|
updated_tgos=events.updated_tgos,
|
||||||
updated_control_points=events.updated_control_points,
|
updated_control_points=events.updated_control_points,
|
||||||
|
reset_on_map_center=recenter_map,
|
||||||
|
game_unloaded=events.game_unloaded,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,15 +1,19 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from game.ato import Flight
|
|
||||||
from game.ato.flightstate import InFlight
|
from game.ato.flightstate import InFlight
|
||||||
from game.server.leaflet import LeafletPoint
|
from game.server.leaflet import LeafletPoint
|
||||||
from game.server.waypoints.models import FlightWaypointJs
|
from game.server.waypoints.models import FlightWaypointJs
|
||||||
from game.server.waypoints.routes import waypoints_for_flight
|
from game.server.waypoints.routes import waypoints_for_flight
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game import Game
|
||||||
|
from game.ato import Flight
|
||||||
|
|
||||||
|
|
||||||
class FlightJs(BaseModel):
|
class FlightJs(BaseModel):
|
||||||
id: UUID
|
id: UUID
|
||||||
@ -37,3 +41,12 @@ class FlightJs(BaseModel):
|
|||||||
sidc=str(flight.sidc()),
|
sidc=str(flight.sidc()),
|
||||||
waypoints=waypoints,
|
waypoints=waypoints,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def all_in_game(game: Game, with_waypoints: bool) -> list[FlightJs]:
|
||||||
|
flights = []
|
||||||
|
for coalition in game.coalitions:
|
||||||
|
for package in coalition.ato.packages:
|
||||||
|
for flight in package.flights:
|
||||||
|
flights.append(FlightJs.for_flight(flight, with_waypoints))
|
||||||
|
return flights
|
||||||
|
|||||||
@ -14,19 +14,16 @@ router: APIRouter = APIRouter(prefix="/flights")
|
|||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
def list_flights(
|
def list_flights(
|
||||||
with_waypoints: bool = False, game: Game = Depends(GameContext.get)
|
with_waypoints: bool = False, game: Game = Depends(GameContext.require)
|
||||||
) -> list[FlightJs]:
|
) -> list[FlightJs]:
|
||||||
flights = []
|
return FlightJs.all_in_game(game, with_waypoints)
|
||||||
for coalition in game.coalitions:
|
|
||||||
for package in coalition.ato.packages:
|
|
||||||
for flight in package.flights:
|
|
||||||
flights.append(FlightJs.for_flight(flight, with_waypoints))
|
|
||||||
return flights
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{flight_id}")
|
@router.get("/{flight_id}")
|
||||||
def get_flight(
|
def get_flight(
|
||||||
flight_id: UUID, with_waypoints: bool = False, game: Game = Depends(GameContext.get)
|
flight_id: UUID,
|
||||||
|
with_waypoints: bool = False,
|
||||||
|
game: Game = Depends(GameContext.require),
|
||||||
) -> FlightJs:
|
) -> FlightJs:
|
||||||
flight = game.db.flights.get(flight_id)
|
flight = game.db.flights.get(flight_id)
|
||||||
return FlightJs.for_flight(flight, with_waypoints)
|
return FlightJs.for_flight(flight, with_waypoints)
|
||||||
@ -34,7 +31,7 @@ def get_flight(
|
|||||||
|
|
||||||
@router.get("/{flight_id}/commit-boundary")
|
@router.get("/{flight_id}/commit-boundary")
|
||||||
def commit_boundary(
|
def commit_boundary(
|
||||||
flight_id: UUID, game: Game = Depends(GameContext.get)
|
flight_id: UUID, game: Game = Depends(GameContext.require)
|
||||||
) -> LeafletPoly:
|
) -> LeafletPoly:
|
||||||
flight = game.db.flights.get(flight_id)
|
flight = game.db.flights.get(flight_id)
|
||||||
if not isinstance(flight.flight_plan, PatrollingFlightPlan):
|
if not isinstance(flight.flight_plan, PatrollingFlightPlan):
|
||||||
|
|||||||
@ -1,13 +1,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from game.server.leaflet import LeafletPoint
|
from game.server.leaflet import LeafletPoint
|
||||||
from game.theater import FrontLine
|
|
||||||
from game.utils import nautical_miles
|
from game.utils import nautical_miles
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game import Game
|
||||||
|
from game.theater import FrontLine
|
||||||
|
|
||||||
|
|
||||||
class FrontLineJs(BaseModel):
|
class FrontLineJs(BaseModel):
|
||||||
id: UUID
|
id: UUID
|
||||||
@ -22,3 +26,7 @@ class FrontLineJs(BaseModel):
|
|||||||
front_line.attack_heading.left.degrees, nautical_miles(2).meters
|
front_line.attack_heading.left.degrees, nautical_miles(2).meters
|
||||||
)
|
)
|
||||||
return FrontLineJs(id=front_line.id, extents=[a.latlng(), b.latlng()])
|
return FrontLineJs(id=front_line.id, extents=[a.latlng(), b.latlng()])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def all_in_game(game: Game) -> list[FrontLineJs]:
|
||||||
|
return [FrontLineJs.for_front_line(f) for f in game.theater.conflicts()]
|
||||||
|
|||||||
@ -10,12 +10,12 @@ router: APIRouter = APIRouter(prefix="/front-lines")
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
def list_front_lines(game: Game = Depends(GameContext.get)) -> list[FrontLineJs]:
|
def list_front_lines(game: Game = Depends(GameContext.require)) -> list[FrontLineJs]:
|
||||||
return [FrontLineJs.for_front_line(f) for f in game.theater.conflicts()]
|
return FrontLineJs.all_in_game(game)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{front_line_id}")
|
@router.get("/{front_line_id}")
|
||||||
def get_front_line(
|
def get_front_line(
|
||||||
front_line_id: UUID, game: Game = Depends(GameContext.get)
|
front_line_id: UUID, game: Game = Depends(GameContext.require)
|
||||||
) -> FrontLineJs:
|
) -> FrontLineJs:
|
||||||
return FrontLineJs.for_front_line(game.db.front_lines.get(front_line_id))
|
return FrontLineJs.for_front_line(game.db.front_lines.get(front_line_id))
|
||||||
|
|||||||
1
game/server/game/__init__.py
Normal file
1
game/server/game/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .routes import router
|
||||||
35
game/server/game/models.py
Normal file
35
game/server/game/models.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from game.server.controlpoints.models import ControlPointJs
|
||||||
|
from game.server.flights.models import FlightJs
|
||||||
|
from game.server.frontlines.models import FrontLineJs
|
||||||
|
from game.server.leaflet import LeafletPoint
|
||||||
|
from game.server.supplyroutes.models import SupplyRouteJs
|
||||||
|
from game.server.tgos.models import TgoJs
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game import Game
|
||||||
|
|
||||||
|
|
||||||
|
class GameJs(BaseModel):
|
||||||
|
control_points: list[ControlPointJs]
|
||||||
|
tgos: list[TgoJs]
|
||||||
|
supply_routes: list[SupplyRouteJs]
|
||||||
|
front_lines: list[FrontLineJs]
|
||||||
|
flights: list[FlightJs]
|
||||||
|
map_center: LeafletPoint
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_game(game: Game) -> GameJs:
|
||||||
|
return GameJs(
|
||||||
|
control_points=ControlPointJs.all_in_game(game),
|
||||||
|
tgos=TgoJs.all_in_game(game),
|
||||||
|
supply_routes=SupplyRouteJs.all_in_game(game),
|
||||||
|
front_lines=FrontLineJs.all_in_game(game),
|
||||||
|
flights=FlightJs.all_in_game(game, with_waypoints=True),
|
||||||
|
map_center=game.theater.terrain.map_view_default.position.latlng(),
|
||||||
|
)
|
||||||
14
game/server/game/routes.py
Normal file
14
game/server/game/routes.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
|
from game import Game
|
||||||
|
from game.server import GameContext
|
||||||
|
from .models import GameJs
|
||||||
|
|
||||||
|
router: APIRouter = APIRouter(prefix="/game")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
def game_state(game: Game | None = Depends(GameContext.get)) -> GameJs | None:
|
||||||
|
if game is None:
|
||||||
|
return None
|
||||||
|
return GameJs.from_game(game)
|
||||||
@ -9,7 +9,7 @@ router: APIRouter = APIRouter(prefix="/map-zones")
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/terrain")
|
@router.get("/terrain")
|
||||||
def get_terrain(game: Game = Depends(GameContext.get)) -> MapZonesJs:
|
def get_terrain(game: Game = Depends(GameContext.require)) -> MapZonesJs:
|
||||||
zones = game.theater.landmap
|
zones = game.theater.landmap
|
||||||
if zones is None:
|
if zones is None:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||||
@ -22,7 +22,9 @@ def get_terrain(game: Game = Depends(GameContext.get)) -> MapZonesJs:
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/unculled")
|
@router.get("/unculled")
|
||||||
def get_unculled_zones(game: Game = Depends(GameContext.get)) -> list[UnculledZoneJs]:
|
def get_unculled_zones(
|
||||||
|
game: Game = Depends(GameContext.require),
|
||||||
|
) -> list[UnculledZoneJs]:
|
||||||
return [
|
return [
|
||||||
UnculledZoneJs(
|
UnculledZoneJs(
|
||||||
position=zone.latlng(), radius=game.settings.perf_culling_distance * 1000
|
position=zone.latlng(), radius=game.settings.perf_culling_distance * 1000
|
||||||
@ -32,7 +34,9 @@ def get_unculled_zones(game: Game = Depends(GameContext.get)) -> list[UnculledZo
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/threats")
|
@router.get("/threats")
|
||||||
def get_threat_zones(game: Game = Depends(GameContext.get)) -> ThreatZoneContainerJs:
|
def get_threat_zones(
|
||||||
|
game: Game = Depends(GameContext.require),
|
||||||
|
) -> ThreatZoneContainerJs:
|
||||||
return ThreatZoneContainerJs(
|
return ThreatZoneContainerJs(
|
||||||
blue=ThreatZonesJs.from_zones(game.threat_zone_for(player=True), game.theater),
|
blue=ThreatZonesJs.from_zones(game.threat_zone_for(player=True), game.theater),
|
||||||
red=ThreatZonesJs.from_zones(game.threat_zone_for(player=False), game.theater),
|
red=ThreatZonesJs.from_zones(game.threat_zone_for(player=False), game.theater),
|
||||||
|
|||||||
@ -9,7 +9,9 @@ router: APIRouter = APIRouter(prefix="/navmesh")
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=list[NavMeshPolyJs])
|
@router.get("/", response_model=list[NavMeshPolyJs])
|
||||||
def get(for_player: bool, game: Game = Depends(GameContext.get)) -> list[NavMeshPolyJs]:
|
def get(
|
||||||
|
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 [
|
||||||
NavMeshPolyJs(
|
NavMeshPolyJs(
|
||||||
|
|||||||
@ -11,7 +11,7 @@ router: APIRouter = APIRouter(prefix="/qt")
|
|||||||
@router.post("/create-package/front-line/{front_line_id}")
|
@router.post("/create-package/front-line/{front_line_id}")
|
||||||
def new_front_line_package(
|
def new_front_line_package(
|
||||||
front_line_id: UUID,
|
front_line_id: UUID,
|
||||||
game: Game = Depends(GameContext.get),
|
game: Game = Depends(GameContext.require),
|
||||||
qt: QtCallbacks = Depends(QtContext.get),
|
qt: QtCallbacks = Depends(QtContext.get),
|
||||||
) -> None:
|
) -> None:
|
||||||
qt.create_new_package(game.db.front_lines.get(front_line_id))
|
qt.create_new_package(game.db.front_lines.get(front_line_id))
|
||||||
@ -20,7 +20,7 @@ def new_front_line_package(
|
|||||||
@router.post("/create-package/tgo/{tgo_id}")
|
@router.post("/create-package/tgo/{tgo_id}")
|
||||||
def new_tgo_package(
|
def new_tgo_package(
|
||||||
tgo_id: UUID,
|
tgo_id: UUID,
|
||||||
game: Game = Depends(GameContext.get),
|
game: Game = Depends(GameContext.require),
|
||||||
qt: QtCallbacks = Depends(QtContext.get),
|
qt: QtCallbacks = Depends(QtContext.get),
|
||||||
) -> None:
|
) -> None:
|
||||||
qt.create_new_package(game.db.tgos.get(tgo_id))
|
qt.create_new_package(game.db.tgos.get(tgo_id))
|
||||||
@ -29,7 +29,7 @@ def new_tgo_package(
|
|||||||
@router.post("/info/tgo/{tgo_id}")
|
@router.post("/info/tgo/{tgo_id}")
|
||||||
def show_tgo_info(
|
def show_tgo_info(
|
||||||
tgo_id: UUID,
|
tgo_id: UUID,
|
||||||
game: Game = Depends(GameContext.get),
|
game: Game = Depends(GameContext.require),
|
||||||
qt: QtCallbacks = Depends(QtContext.get),
|
qt: QtCallbacks = Depends(QtContext.get),
|
||||||
) -> None:
|
) -> None:
|
||||||
qt.show_tgo_info(game.db.tgos.get(tgo_id))
|
qt.show_tgo_info(game.db.tgos.get(tgo_id))
|
||||||
@ -38,7 +38,7 @@ def show_tgo_info(
|
|||||||
@router.post("/create-package/control-point/{cp_id}")
|
@router.post("/create-package/control-point/{cp_id}")
|
||||||
def new_cp_package(
|
def new_cp_package(
|
||||||
cp_id: int,
|
cp_id: int,
|
||||||
game: Game = Depends(GameContext.get),
|
game: Game = Depends(GameContext.require),
|
||||||
qt: QtCallbacks = Depends(QtContext.get),
|
qt: QtCallbacks = Depends(QtContext.get),
|
||||||
) -> None:
|
) -> None:
|
||||||
cp = game.theater.find_control_point_by_id(cp_id)
|
cp = game.theater.find_control_point_by_id(cp_id)
|
||||||
@ -53,7 +53,7 @@ def new_cp_package(
|
|||||||
@router.post("/info/control-point/{cp_id}")
|
@router.post("/info/control-point/{cp_id}")
|
||||||
def show_control_point_info(
|
def show_control_point_info(
|
||||||
cp_id: int,
|
cp_id: int,
|
||||||
game: Game = Depends(GameContext.get),
|
game: Game = Depends(GameContext.require),
|
||||||
qt: QtCallbacks = Depends(QtContext.get),
|
qt: QtCallbacks = Depends(QtContext.get),
|
||||||
) -> None:
|
) -> None:
|
||||||
cp = game.theater.find_control_point_by_id(cp_id)
|
cp = game.theater.find_control_point_by_id(cp_id)
|
||||||
|
|||||||
@ -81,3 +81,29 @@ class SupplyRouteJs(BaseModel):
|
|||||||
sea
|
sea
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def all_in_game(game: Game) -> list[SupplyRouteJs]:
|
||||||
|
seen = set()
|
||||||
|
routes = []
|
||||||
|
for control_point in game.theater.controlpoints:
|
||||||
|
seen.add(control_point)
|
||||||
|
for destination, route in control_point.convoy_routes.items():
|
||||||
|
if destination in seen:
|
||||||
|
continue
|
||||||
|
routes.append(
|
||||||
|
SupplyRouteJs.for_link(
|
||||||
|
game, control_point, destination, list(route), sea=False
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for destination, route in control_point.shipping_lanes.items():
|
||||||
|
if destination in seen:
|
||||||
|
continue
|
||||||
|
if not destination.is_friendly_to(control_point):
|
||||||
|
continue
|
||||||
|
routes.append(
|
||||||
|
SupplyRouteJs.for_link(
|
||||||
|
game, control_point, destination, list(route), sea=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return routes
|
||||||
|
|||||||
@ -8,27 +8,7 @@ router: APIRouter = APIRouter(prefix="/supply-routes")
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
def list_supply_routes(game: Game = Depends(GameContext.get)) -> list[SupplyRouteJs]:
|
def list_supply_routes(
|
||||||
seen = set()
|
game: Game = Depends(GameContext.require),
|
||||||
routes = []
|
) -> list[SupplyRouteJs]:
|
||||||
for control_point in game.theater.controlpoints:
|
return SupplyRouteJs.all_in_game(game)
|
||||||
seen.add(control_point)
|
|
||||||
for destination, route in control_point.convoy_routes.items():
|
|
||||||
if destination in seen:
|
|
||||||
continue
|
|
||||||
routes.append(
|
|
||||||
SupplyRouteJs.for_link(
|
|
||||||
game, control_point, destination, list(route), sea=False
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for destination, route in control_point.shipping_lanes.items():
|
|
||||||
if destination in seen:
|
|
||||||
continue
|
|
||||||
if not destination.is_friendly_to(control_point):
|
|
||||||
continue
|
|
||||||
routes.append(
|
|
||||||
SupplyRouteJs.for_link(
|
|
||||||
game, control_point, destination, list(route), sea=True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return routes
|
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from game.server.leaflet import LeafletPoint
|
from game.server.leaflet import LeafletPoint
|
||||||
from game.theater import TheaterGroundObject
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game import Game
|
||||||
|
from game.theater import TheaterGroundObject
|
||||||
|
|
||||||
|
|
||||||
class TgoJs(BaseModel):
|
class TgoJs(BaseModel):
|
||||||
@ -44,3 +48,12 @@ class TgoJs(BaseModel):
|
|||||||
dead=tgo.is_dead,
|
dead=tgo.is_dead,
|
||||||
sidc=str(tgo.sidc()),
|
sidc=str(tgo.sidc()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def all_in_game(game: Game) -> 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
|
||||||
|
|||||||
@ -10,15 +10,10 @@ router: APIRouter = APIRouter(prefix="/tgos")
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
def list_tgos(game: Game = Depends(GameContext.get)) -> list[TgoJs]:
|
def list_tgos(game: Game = Depends(GameContext.require)) -> list[TgoJs]:
|
||||||
tgos = []
|
return TgoJs.all_in_game(game)
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{tgo_id}")
|
@router.get("/{tgo_id}")
|
||||||
def get_tgo(tgo_id: UUID, game: Game = Depends(GameContext.get)) -> TgoJs:
|
def get_tgo(tgo_id: UUID, game: Game = Depends(GameContext.require)) -> TgoJs:
|
||||||
return TgoJs.for_tgo(game.db.tgos.get(tgo_id))
|
return TgoJs.for_tgo(game.db.tgos.get(tgo_id))
|
||||||
|
|||||||
@ -36,7 +36,7 @@ def waypoints_for_flight(flight: Flight) -> list[FlightWaypointJs]:
|
|||||||
|
|
||||||
@router.get("/{flight_id}", response_model=list[FlightWaypointJs])
|
@router.get("/{flight_id}", response_model=list[FlightWaypointJs])
|
||||||
def all_waypoints_for_flight(
|
def all_waypoints_for_flight(
|
||||||
flight_id: UUID, game: Game = Depends(GameContext.get)
|
flight_id: UUID, game: Game = Depends(GameContext.require)
|
||||||
) -> list[FlightWaypointJs]:
|
) -> list[FlightWaypointJs]:
|
||||||
return waypoints_for_flight(game.db.flights.get(flight_id))
|
return waypoints_for_flight(game.db.flights.get(flight_id))
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ def set_position(
|
|||||||
flight_id: UUID,
|
flight_id: UUID,
|
||||||
waypoint_idx: int,
|
waypoint_idx: int,
|
||||||
position: LeafletPoint,
|
position: LeafletPoint,
|
||||||
game: Game = Depends(GameContext.get),
|
game: Game = Depends(GameContext.require),
|
||||||
) -> None:
|
) -> None:
|
||||||
from game.server import EventStream
|
from game.server import EventStream
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,10 @@ from typing import TYPE_CHECKING
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from dcs import Point
|
from dcs import Point
|
||||||
|
from dcs.mapping import LatLng
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from game import Game
|
||||||
from game.ato import Flight, Package
|
from game.ato import Flight, Package
|
||||||
from game.sim.combat import FrozenCombat
|
from game.sim.combat import FrozenCombat
|
||||||
from game.theater import ControlPoint, FrontLine, TheaterGroundObject
|
from game.theater import ControlPoint, FrontLine, TheaterGroundObject
|
||||||
@ -32,6 +34,8 @@ class GameUpdateEvents:
|
|||||||
deleted_front_lines: set[UUID] = field(default_factory=set)
|
deleted_front_lines: set[UUID] = field(default_factory=set)
|
||||||
updated_tgos: set[UUID] = field(default_factory=set)
|
updated_tgos: set[UUID] = field(default_factory=set)
|
||||||
updated_control_points: set[int] = field(default_factory=set)
|
updated_control_points: set[int] = field(default_factory=set)
|
||||||
|
reset_on_map_center: LatLng | None = None
|
||||||
|
game_unloaded: bool = False
|
||||||
shutting_down: bool = False
|
shutting_down: bool = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -121,6 +125,17 @@ class GameUpdateEvents:
|
|||||||
self.updated_control_points.add(control_point.id)
|
self.updated_control_points.add(control_point.id)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def game_loaded(self, game: Game | None) -> GameUpdateEvents:
|
||||||
|
if game is None:
|
||||||
|
self.game_unloaded = True
|
||||||
|
self.reset_on_map_center = None
|
||||||
|
else:
|
||||||
|
self.reset_on_map_center = (
|
||||||
|
game.theater.terrain.map_view_default.position.latlng()
|
||||||
|
)
|
||||||
|
self.game_unloaded = False
|
||||||
|
return self
|
||||||
|
|
||||||
def shut_down(self) -> GameUpdateEvents:
|
def shut_down(self) -> GameUpdateEvents:
|
||||||
self.shutting_down = True
|
self.shutting_down = True
|
||||||
return self
|
return self
|
||||||
|
|||||||
@ -21,6 +21,7 @@ from game.factions import FACTIONS
|
|||||||
from game.profiling import logged_duration
|
from game.profiling import logged_duration
|
||||||
from game.server import EventStream, Server
|
from game.server import EventStream, Server
|
||||||
from game.settings import Settings
|
from game.settings import Settings
|
||||||
|
from game.sim import GameUpdateEvents
|
||||||
from game.theater.start_generator import GameGenerator, GeneratorSettings, ModSettings
|
from game.theater.start_generator import GameGenerator, GeneratorSettings, ModSettings
|
||||||
from pydcs_extensions import load_mods
|
from pydcs_extensions import load_mods
|
||||||
from qt_ui import (
|
from qt_ui import (
|
||||||
@ -58,6 +59,11 @@ def inject_custom_payloads(user_path: Path) -> None:
|
|||||||
PayloadDirectories.set_preferred(user_path / "MissionEditor" / "UnitPayloads")
|
PayloadDirectories.set_preferred(user_path / "MissionEditor" / "UnitPayloads")
|
||||||
|
|
||||||
|
|
||||||
|
def on_game_load(game: Game | None) -> None:
|
||||||
|
EventStream.drain()
|
||||||
|
EventStream.put_nowait(GameUpdateEvents().game_loaded(game))
|
||||||
|
|
||||||
|
|
||||||
def run_ui(game: Optional[Game], new_map: bool, dev: bool) -> None:
|
def run_ui(game: Optional[Game], new_map: bool, dev: bool) -> None:
|
||||||
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" # Potential fix for 4K screens
|
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" # Potential fix for 4K screens
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
@ -143,7 +149,7 @@ def run_ui(game: Optional[Game], new_map: bool, dev: bool) -> None:
|
|||||||
|
|
||||||
# Apply CSS (need works)
|
# Apply CSS (need works)
|
||||||
GameUpdateSignal()
|
GameUpdateSignal()
|
||||||
GameUpdateSignal.get_instance().game_loaded.connect(EventStream.drain)
|
GameUpdateSignal.get_instance().game_loaded.connect(on_game_load)
|
||||||
|
|
||||||
# Start window
|
# Start window
|
||||||
window = QLiberationWindow(game, new_map)
|
window = QLiberationWindow(game, new_map)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user