Draw frozen combat in the new UI.

https://github.com/dcs-liberation/dcs_liberation/issues/2039
This commit is contained in:
Dan Albert 2022-03-04 19:41:33 -08:00
parent 88cd9e19c5
commit 59f734dd07
9 changed files with 144 additions and 8 deletions

8
client/src/api/combat.ts Normal file
View File

@ -0,0 +1,8 @@
import { LatLng } from "leaflet";
export default interface Combat {
id: string;
flight_position: LatLng | null;
target_positions: LatLng[] | null;
footprint: LatLng[][] | null;
}

View File

@ -0,0 +1,42 @@
import { RootState } from "../app/store";
import Combat from "./combat";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
interface CombatState {
combat: { [key: string]: Combat };
}
const initialState: CombatState = {
combat: {},
};
export const combatSlice = createSlice({
name: "combat",
initialState,
reducers: {
setCombat: (state, action: PayloadAction<Combat[]>) => {
state.combat = {};
for (const combat of action.payload) {
state.combat[combat.id] = combat;
}
},
newCombat: (state, action: PayloadAction<Combat>) => {
const combat = action.payload;
state.combat[combat.id] = combat;
},
updateCombat: (state, action: PayloadAction<Combat>) => {
const combat = action.payload;
state.combat[combat.id] = combat;
},
endCombat: (state, action: PayloadAction<string>) => {
delete state.combat[action.payload];
},
},
});
export const { setCombat, newCombat, updateCombat, endCombat } =
combatSlice.actions;
export const selectCombat = (state: RootState) => state.combat;
export default combatSlice.reducer;

View File

@ -1,5 +1,7 @@
import { AppDispatch } from "../app/store"; import { AppDispatch } from "../app/store";
import backend from "./backend"; import backend from "./backend";
import Combat from "./combat";
import { endCombat, newCombat, updateCombat } from "./combatSlice";
import { updateControlPoint } from "./controlPointsSlice"; import { updateControlPoint } from "./controlPointsSlice";
import { ControlPoint } from "./controlpoint"; import { ControlPoint } from "./controlpoint";
import { Flight } from "./flight"; import { Flight } from "./flight";
@ -21,14 +23,10 @@ import Tgo from "./tgo";
import { updateTgo } from "./tgosSlice"; import { updateTgo } from "./tgosSlice";
import { LatLng } from "leaflet"; import { LatLng } from "leaflet";
// Placeholder. We don't use this yet. This is just here so we can flesh out the
// update events model.
interface FrozenCombat {}
interface GameUpdateEvents { interface GameUpdateEvents {
updated_flight_positions: { [id: string]: LatLng }; updated_flight_positions: { [id: string]: LatLng };
new_combats: FrozenCombat[]; new_combats: Combat[];
updated_combats: FrozenCombat[]; updated_combats: Combat[];
ended_combats: string[]; ended_combats: string[];
navmesh_updates: boolean[]; navmesh_updates: boolean[];
unculled_zones_updated: boolean; unculled_zones_updated: boolean;
@ -55,6 +53,18 @@ export const handleStreamedEvents = (
dispatch(updateFlightPosition([id, position])); dispatch(updateFlightPosition([id, position]));
} }
for (const combat of events.new_combats) {
dispatch(newCombat(combat));
}
for (const combat of events.updated_combats) {
dispatch(updateCombat(combat));
}
for (const id of events.ended_combats) {
dispatch(endCombat(id));
}
for (const flight of events.new_flights) { for (const flight of events.new_flights) {
dispatch(registerFlight(flight)); dispatch(registerFlight(flight));
} }

View File

@ -1,3 +1,4 @@
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";
@ -8,14 +9,15 @@ import logger from "redux-logger";
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
combat: combatReducer,
controlPoints: controlPointsReducer,
flights: flightsReducer, flights: flightsReducer,
frontLines: frontLinesReducer, frontLines: frontLinesReducer,
controlPoints: controlPointsReducer,
supplyRoutes: supplyRoutesReducer, supplyRoutes: supplyRoutesReducer,
tgos: tgosReducer, tgos: tgosReducer,
}, },
// The logger middleware must be last or it won't log actions. // The logger middleware must be last or it won't log actions.
//middleware: [logger], middleware: [logger],
}); });
export type AppDispatch = typeof store.dispatch; export type AppDispatch = typeof store.dispatch;

View File

@ -0,0 +1,52 @@
import CombatModel from "../../api/combat";
import { LatLng } from "leaflet";
import { Polygon, Polyline } from "react-leaflet";
interface CombatProps {
combat: CombatModel;
}
function CombatFootprint(props: CombatProps) {
if (!props.combat.footprint) {
return <></>;
}
return (
<Polygon
positions={props.combat.footprint}
color="#c85050"
interactive={false}
fillOpacity={0.2}
/>
);
}
function CombatLines(props: CombatProps) {
if (!props.combat.flight_position || !props.combat.target_positions) {
return <></>;
}
const flightPosition: LatLng = props.combat.flight_position;
return (
<>
{props.combat.target_positions.map((position) => {
return (
<Polyline
positions={[flightPosition, position]}
color="#c85050"
interactive={false}
/>
);
})}
</>
);
}
export default function Combat(props: CombatProps) {
return (
<>
<CombatFootprint {...props} />
<CombatLines {...props} />
</>
);
}

View File

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

View File

@ -0,0 +1,16 @@
import { selectCombat } from "../../api/combatSlice";
import { useAppSelector } from "../../app/hooks";
import Combat from "../combat/Combat";
import { LayerGroup } from "react-leaflet";
export default function CombatLayer() {
const combats = useAppSelector(selectCombat);
return (
<LayerGroup>
{Object.values(combats.combat).map((combat) => {
return <Combat key={combat.id} combat={combat} />;
})}
(
</LayerGroup>
);
}

View File

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

View File

@ -1,5 +1,6 @@
import AircraftLayer from "../aircraftlayer"; import AircraftLayer from "../aircraftlayer";
import AirDefenseRangeLayer from "../airdefenserangelayer"; import AirDefenseRangeLayer from "../airdefenserangelayer";
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";
@ -34,6 +35,9 @@ export default function LiberationMap(props: GameProps) {
<LayersControl.Overlay name="Aircraft" checked> <LayersControl.Overlay name="Aircraft" checked>
<AircraftLayer /> <AircraftLayer />
</LayersControl.Overlay> </LayersControl.Overlay>
<LayersControl.Overlay name="Active combat" checked>
<CombatLayer />
</LayersControl.Overlay>
<LayersControl.Overlay name="Air defenses" checked> <LayersControl.Overlay name="Air defenses" checked>
<TgosLayer categories={["aa"]} /> <TgosLayer categories={["aa"]} />
</LayersControl.Overlay> </LayersControl.Overlay>