diff --git a/client/src/api/frontLinesSlice.ts b/client/src/api/frontLinesSlice.ts new file mode 100644 index 00000000..a04dc1c8 --- /dev/null +++ b/client/src/api/frontLinesSlice.ts @@ -0,0 +1,28 @@ +import { PayloadAction, createSlice } from "@reduxjs/toolkit"; + +import FrontLine from "./frontline"; +import { RootState } from "../app/store"; + +interface FrontLinesState { + fronts: FrontLine[]; +} + +const initialState: FrontLinesState = { + fronts: [], +}; + +export const frontLinesSlice = createSlice({ + name: "frontLines", + initialState, + reducers: { + setFrontLines: (state, action: PayloadAction) => { + state.fronts = action.payload; + }, + }, +}); + +export const { setFrontLines } = frontLinesSlice.actions; + +export const selectFrontLines = (state: RootState) => state.frontLines; + +export default frontLinesSlice.reducer; diff --git a/client/src/api/frontline.ts b/client/src/api/frontline.ts new file mode 100644 index 00000000..2d1ac6f7 --- /dev/null +++ b/client/src/api/frontline.ts @@ -0,0 +1,7 @@ +import { LatLng } from "leaflet"; + +export interface FrontLine { + extents: LatLng[]; +} + +export default FrontLine; diff --git a/client/src/app/store.ts b/client/src/app/store.ts index 373d7020..4fe1f8ad 100644 --- a/client/src/app/store.ts +++ b/client/src/app/store.ts @@ -2,12 +2,14 @@ import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit"; import controlPointsReducer from "../api/controlPointsSlice"; import flightsReducer from "../api/flightsSlice"; +import frontLinesReducer from "../api/frontLinesSlice"; import supplyRoutesReducer from "../api/supplyRoutesSlice"; import tgosReducer from "../api/tgosSlice"; export const store = configureStore({ reducer: { flights: flightsReducer, + frontLines: frontLinesReducer, controlPoints: controlPointsReducer, supplyRoutes: supplyRoutesReducer, tgos: tgosReducer, diff --git a/client/src/components/frontline/FrontLine.tsx b/client/src/components/frontline/FrontLine.tsx new file mode 100644 index 00000000..72f2385b --- /dev/null +++ b/client/src/components/frontline/FrontLine.tsx @@ -0,0 +1,14 @@ +import { FrontLine as FrontLineModel } from "../../api/frontline"; +import { Polyline } from "react-leaflet"; + +interface FrontLineProps { + front: FrontLineModel; +} + +function FrontLine(props: FrontLineProps) { + return ( + + ); +} + +export default FrontLine; diff --git a/client/src/components/frontline/index.ts b/client/src/components/frontline/index.ts new file mode 100644 index 00000000..5ded594e --- /dev/null +++ b/client/src/components/frontline/index.ts @@ -0,0 +1 @@ +export { default } from "./FrontLine"; diff --git a/client/src/components/frontlineslayer/FrontLinesLayer.tsx b/client/src/components/frontlineslayer/FrontLinesLayer.tsx new file mode 100644 index 00000000..b071aa9a --- /dev/null +++ b/client/src/components/frontlineslayer/FrontLinesLayer.tsx @@ -0,0 +1,15 @@ +import FrontLine from "../frontline"; +import { LayerGroup } from "react-leaflet"; +import { selectFrontLines } from "../../api/frontLinesSlice"; +import { useAppSelector } from "../../app/hooks"; + +export default function SupplyRoutesLayer() { + const fronts = useAppSelector(selectFrontLines).fronts; + return ( + + {fronts.map((front, idx) => { + return ; + })} + + ); +} diff --git a/client/src/components/frontlineslayer/index.ts b/client/src/components/frontlineslayer/index.ts new file mode 100644 index 00000000..ddde2aa2 --- /dev/null +++ b/client/src/components/frontlineslayer/index.ts @@ -0,0 +1 @@ +export { default } from "./FrontLinesLayer"; diff --git a/client/src/components/liberationmap/LiberationMap.tsx b/client/src/components/liberationmap/LiberationMap.tsx index f886cbee..a8860b78 100644 --- a/client/src/components/liberationmap/LiberationMap.tsx +++ b/client/src/components/liberationmap/LiberationMap.tsx @@ -6,6 +6,7 @@ import AirDefenseRangeLayer from "../airdefenserangelayer"; import { BasemapLayer } from "react-esri-leaflet"; import ControlPointsLayer from "../controlpointslayer"; import FlightPlansLayer from "../flightplanslayer"; +import FrontLinesLayer from "../frontlineslayer"; import { LatLng } from "leaflet"; import SupplyRoutesLayer from "../supplyrouteslayer"; import { TgoType } from "../../api/tgo"; @@ -42,6 +43,9 @@ export default function LiberationMap(props: GameProps) { + + + diff --git a/client/src/hooks/useInitialGameState.tsx b/client/src/hooks/useInitialGameState.tsx index d1375c1c..cda2706b 100644 --- a/client/src/hooks/useInitialGameState.tsx +++ b/client/src/hooks/useInitialGameState.tsx @@ -1,10 +1,12 @@ import { ControlPoint } from "../api/controlpoint"; import { Flight } from "../api/flight"; +import FrontLine from "../api/frontline"; import SupplyRoute from "../api/supplyroute"; import Tgo from "../api/tgo"; import backend from "../api/backend"; import { registerFlight } from "../api/flightsSlice"; import { setControlPoints } from "../api/controlPointsSlice"; +import { setFrontLines } from "../api/frontLinesSlice"; import { setSupplyRoutes } from "../api/supplyRoutesSlice"; import { setTgos } from "../api/tgosSlice"; import { useAppDispatch } from "../app/hooks"; @@ -41,6 +43,14 @@ export const useInitialGameState = () => { 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}`)) diff --git a/game/server/app.py b/game/server/app.py index 2d74530f..549ad01d 100644 --- a/game/server/app.py +++ b/game/server/app.py @@ -6,6 +6,7 @@ from . import ( debuggeometries, eventstream, flights, + frontlines, mapzones, navmesh, supplyroutes, @@ -24,6 +25,7 @@ app.include_router(controlpoints.router) app.include_router(debuggeometries.router) app.include_router(eventstream.router) app.include_router(flights.router) +app.include_router(frontlines.router) app.include_router(mapzones.router) app.include_router(navmesh.router) app.include_router(supplyroutes.router) diff --git a/game/server/frontlines/__init__.py b/game/server/frontlines/__init__.py new file mode 100644 index 00000000..3a27ef1c --- /dev/null +++ b/game/server/frontlines/__init__.py @@ -0,0 +1 @@ +from .routes import router diff --git a/game/server/frontlines/models.py b/game/server/frontlines/models.py new file mode 100644 index 00000000..6a9d85f5 --- /dev/null +++ b/game/server/frontlines/models.py @@ -0,0 +1,9 @@ +from __future__ import annotations + +from pydantic import BaseModel + +from game.server.leaflet import LeafletPoint + + +class FrontLineJs(BaseModel): + extents: list[LeafletPoint] diff --git a/game/server/frontlines/routes.py b/game/server/frontlines/routes.py new file mode 100644 index 00000000..f0e70da5 --- /dev/null +++ b/game/server/frontlines/routes.py @@ -0,0 +1,22 @@ +from fastapi import APIRouter, Depends + +from game import Game +from game.utils import nautical_miles +from .models import FrontLineJs +from ..dependencies import GameContext + +router: APIRouter = APIRouter(prefix="/front-lines") + + +@router.get("/") +def list_front_lines(game: Game = Depends(GameContext.get)) -> list[FrontLineJs]: + front_lines = [] + for front_line in game.theater.conflicts(): + a = front_line.position.point_from_heading( + front_line.attack_heading.right.degrees, nautical_miles(2).meters + ) + b = front_line.position.point_from_heading( + front_line.attack_heading.left.degrees, nautical_miles(2).meters + ) + front_lines.append(FrontLineJs(extents=[a.latlng(), b.latlng()])) + return front_lines