diff --git a/frontend/react/src/constants/constants.ts b/frontend/react/src/constants/constants.ts
index 06f6f031..0ddd46e1 100644
--- a/frontend/react/src/constants/constants.ts
+++ b/frontend/react/src/constants/constants.ts
@@ -12,6 +12,7 @@ import {
faPlaneArrival,
faRoute,
faTrash,
+ faWifi,
faXmarksLines,
} from "@fortawesome/free-solid-svg-icons";
import { Unit } from "../unit/unit";
@@ -260,7 +261,14 @@ export const mapBounds = {
};
export const defaultMapMirrors = {};
-export const defaultMapLayers = {};
+export const defaultMapLayers = {
+ "AWACS": {
+ "urlTemplate": 'https://abcd.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png',
+ "minZoom": 1,
+ "maxZoom": 19,
+ "attribution": `© OpenStreetMap contributors © CARTO'`
+ },
+};
export enum OlympusState {
NOT_INITIALIZED = "Not initialized",
@@ -272,6 +280,7 @@ export enum OlympusState {
SPAWN_CONTEXT = "Spawn context",
DRAW = "Draw",
JTAC = "JTAC",
+ AWACS = "AWACS",
OPTIONS = "Options",
AUDIO = "Audio",
AIRBASE = "Airbase",
@@ -340,6 +349,9 @@ export const MAP_OPTIONS_DEFAULTS: MapOptions = {
cameraPluginEnabled: false,
cameraPluginMode: "map",
tabletMode: false,
+ showUnitBullseyes: false,
+ showUnitBRAA: false,
+ AWACSMode: false
};
export const MAP_HIDDEN_TYPES_DEFAULTS = {
@@ -459,7 +471,6 @@ export namespace ContextActions {
getApp().getUnitsManager().stop(units);
},
{
- executeImmediately: true,
type: ContextActionType.MOVE,
code: "Space",
}
@@ -506,7 +517,6 @@ export namespace ContextActions {
getApp().getUnitsManager().delete(false);
},
{
- executeImmediately: true,
type: ContextActionType.DELETE,
code: "Delete",
ctrlKey: false,
@@ -526,7 +536,6 @@ export namespace ContextActions {
UnitExplosionRequestEvent.dispatch(units);
},
{
- executeImmediately: true,
type: ContextActionType.DELETE,
code: "Delete",
ctrlKey: false,
@@ -544,7 +553,7 @@ export namespace ContextActions {
(units: Unit[]) => {
getApp().getMap().centerOnUnit(units[0]);
},
- { executeImmediately: true, type: ContextActionType.OTHER, code: "KeyM", ctrlKey: false, shiftKey: false, altKey: false }
+ { type: ContextActionType.OTHER, code: "KeyM", ctrlKey: false, shiftKey: false, altKey: false }
);
export const REFUEL = new ContextAction(
@@ -556,7 +565,7 @@ export namespace ContextActions {
(units: Unit[]) => {
getApp().getUnitsManager().refuel(units);
},
- { executeImmediately: true, type: ContextActionType.ADMIN, code: "KeyR", ctrlKey: false, shiftKey: false, altKey: false }
+ { type: ContextActionType.ADMIN, code: "KeyR", ctrlKey: false, shiftKey: false, altKey: false }
);
export const FOLLOW = new ContextAction(
@@ -643,7 +652,7 @@ export namespace ContextActions {
(units: Unit[], _1, _2) => {
getApp().getUnitsManager().createGroup(units);
},
- { executeImmediately: true, type: ContextActionType.OTHER, code: "KeyG", ctrlKey: false, shiftKey: false, altKey: false }
+ { type: ContextActionType.OTHER, code: "KeyG", ctrlKey: false, shiftKey: false, altKey: false }
);
export const ATTACK = new ContextAction(
@@ -687,4 +696,16 @@ export namespace ContextActions {
},
{ type: ContextActionType.ADMIN, code: "KeyX", ctrlKey: false, shiftKey: false }
);
+
+ export const SET_AWACS_REFERENCE = new ContextAction(
+ "set-awacs-reference",
+ "Set AWACS reference",
+ "Set unit as AWACS reference",
+ faWifi,
+ ContextActionTarget.NONE,
+ (units: Unit[], _1, _2) => {
+ getApp().getUnitsManager().setAWACSReference(units[0].ID);
+ },
+ { type: ContextActionType.ADMIN, code: "KeyU", ctrlKey: false, shiftKey: false, altKey: false }
+ );
}
diff --git a/frontend/react/src/events.ts b/frontend/react/src/events.ts
index de9d0309..eab00388 100644
--- a/frontend/react/src/events.ts
+++ b/frontend/react/src/events.ts
@@ -151,6 +151,19 @@ export class ModalEvent {
}
/************** Map events ***************/
+export class MouseMovedEvent {
+ static on(callback: (latlng: LatLng, elevation: number) => void) {
+ document.addEventListener(this.name, (ev: CustomEventInit) => {
+ callback(ev.detail.latlng, ev.detail.elevation);
+ });
+ }
+
+ static dispatch(latlng: LatLng, elevation?: number) {
+ document.dispatchEvent(new CustomEvent(this.name, { detail: { latlng, elevation } }));
+ // Logging disabled since periodic
+ }
+}
+
export class HiddenTypesChangedEvent {
static on(callback: (hiddenTypes: MapHiddenTypes) => void) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
@@ -346,13 +359,13 @@ export class SpawnContextMenuRequestEvent {
}
export class HotgroupsChangedEvent {
- static on(callback: (hotgroups: { [key: number]: number }) => void) {
+ static on(callback: (hotgroups: { [key: number]: Unit[] }) => void) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(ev.detail.hotgroups);
});
}
- static dispatch(hotgroups: { [key: number]: number }) {
+ static dispatch(hotgroups: { [key: number]: Unit[] }) {
document.dispatchEvent(new CustomEvent(this.name, { detail: { hotgroups } }));
console.log(`Event ${this.name} dispatched`);
}
@@ -371,15 +384,15 @@ export class StarredSpawnsChangedEvent {
}
}
-export class MouseMovedEvent {
- static on(callback: (latlng: LatLng, elevation: number) => void) {
+export class AWACSReferenceChangedEvent {
+ static on(callback: (unit: Unit | null) => void) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
- callback(ev.detail.latlng, ev.detail.elevation);
+ callback(ev.detail);
});
}
- static dispatch(latlng: LatLng, elevation?: number) {
- document.dispatchEvent(new CustomEvent(this.name, { detail: { latlng, elevation } }));
+ static dispatch(unit: Unit | null) {
+ document.dispatchEvent(new CustomEvent(this.name, { detail: unit }));
// Logging disabled since periodic
}
}
@@ -461,7 +474,7 @@ export class BullseyesDataChanged {
});
}
- static dispatch(bullseyes: { [name: string]: Bullseye } ) {
+ static dispatch(bullseyes: { [name: string]: Bullseye }) {
document.dispatchEvent(new CustomEvent(this.name, { detail: { bullseyes } }));
// Logging disabled since periodic
}
diff --git a/frontend/react/src/interfaces.ts b/frontend/react/src/interfaces.ts
index 34758ceb..156122e2 100644
--- a/frontend/react/src/interfaces.ts
+++ b/frontend/react/src/interfaces.ts
@@ -130,6 +130,8 @@ export interface ObjectIconOptions {
showSummary: boolean;
showCallsign: boolean;
rotateToHeading: boolean;
+ showBullseyes: boolean;
+ showBRAA: boolean;
}
export interface GeneralSettings {
diff --git a/frontend/react/src/map/map.ts b/frontend/react/src/map/map.ts
index b9384789..1e9b56b0 100644
--- a/frontend/react/src/map/map.ts
+++ b/frontend/react/src/map/map.ts
@@ -207,7 +207,7 @@ export class Map extends L.Map {
/* Custom touch events for touchscreen support */
L.DomEvent.on(this.getContainer(), "touchstart", this.#onMouseDown, this);
L.DomEvent.on(this.getContainer(), "touchend", this.#onMouseUp, this);
- L.DomEvent.on(this.getContainer(), 'wheel', this.#onMouseWheel, this);
+ L.DomEvent.on(this.getContainer(), "wheel", this.#onMouseWheel, this);
/* Event listeners */
AppStateChangedEvent.on((state, subState) => this.#onStateChanged(state, subState));
@@ -227,6 +227,8 @@ export class Map extends L.Map {
MapOptionsChangedEvent.on((options: MapOptions) => {
this.getContainer().toggleAttribute("data-hide-labels", !options.showUnitLabels);
+ this.getContainer().toggleAttribute("data-hide-bullseyes", !options.showUnitBullseyes);
+ this.getContainer().toggleAttribute("data-hide-BRAA", !options.showUnitBRAA);
this.#cameraControlPort = options.cameraPluginPort;
this.#cameraZoomRatio = 50 / (20 + options.cameraPluginRatio);
this.#slaveDCSCamera = options.cameraPluginEnabled;
@@ -400,7 +402,7 @@ export class Map extends L.Map {
const contextActionSet = this.getContextActionSet();
if (this.getContextAction() === null || contextAction !== this.getContextAction()) {
if (getApp().getState() === OlympusState.UNIT_CONTROL && contextActionSet && contextAction.getId() in contextActionSet.getContextActions()) {
- if (contextAction.getOptions().executeImmediately) contextAction.executeCallback(null, null);
+ if (contextAction.getTarget() === ContextActionTarget.NONE) contextAction.executeCallback(null, null);
else this.setContextAction(contextAction);
}
} else {
diff --git a/frontend/react/src/map/markers/stylesheets/units.css b/frontend/react/src/map/markers/stylesheets/units.css
index 32de33e9..94a65ba4 100644
--- a/frontend/react/src/map/markers/stylesheets/units.css
+++ b/frontend/react/src/map/markers/stylesheets/units.css
@@ -100,6 +100,7 @@
font-weight: var(--unit-font-weight);
line-height: normal;
position: absolute;
+ font-size: 12px;
}
[data-object|="unit-groundunit"] .unit-short-label {
@@ -398,3 +399,53 @@
.ol-temporary-marker {
opacity: 0.5;
}
+
+/*** Unit summary ***/
+[data-object|="unit"] .unit-tactical {
+ color: white;
+ column-gap: 6px;
+ display: flex;
+ flex-wrap: wrap;
+ font-size: 11px;
+ font-weight: bold;
+ justify-content: left;
+ line-height: 12px;
+ pointer-events: none;
+ position: absolute;
+ row-gap: 1px;
+
+ left: 100%;
+ width: fit-content;
+}
+
+[data-hide-bullseyes] [data-object|="unit"] .unit-bullseyes {
+ display: none;
+}
+
+[data-hide-BRAA] [data-object|="unit"] .unit-braa {
+ display: none;
+}
+
+[data-object|="unit"] .unit-blue-bullseye {
+ text-shadow:
+ -1px -1px 0 #00F,
+ 1px -1px 0 #00F,
+ -1px 1px 0 #00F,
+ 1px 1px 0 #00F;
+}
+
+[data-object|="unit"] .unit-red-bullseye {
+ text-shadow:
+ -1px -1px 0 #F00,
+ 1px -1px 0 #F00,
+ -1px 1px 0 #F00,
+ 1px 1px 0 #F00;
+}
+
+[data-object|="unit"] .unit-braa {
+ text-shadow:
+ -1px -1px 0 #000,
+ 1px -1px 0 #000,
+ -1px 1px 0 #000,
+ 1px 1px 0 #000;
+}
diff --git a/frontend/react/src/map/stylesheets/map.css b/frontend/react/src/map/stylesheets/map.css
index 5758cadc..d6bf3be6 100644
--- a/frontend/react/src/map/stylesheets/map.css
+++ b/frontend/react/src/map/stylesheets/map.css
@@ -180,4 +180,4 @@
path.leaflet-interactive:focus {
outline: none;
-}
\ No newline at end of file
+}
diff --git a/frontend/react/src/other/utils.ts b/frontend/react/src/other/utils.ts
index 02675a46..307c9d4f 100644
--- a/frontend/react/src/other/utils.ts
+++ b/frontend/react/src/other/utils.ts
@@ -393,3 +393,7 @@ export function wait(time) {
setTimeout(resolve, time);
});
}
+
+export function computeBearingRangeString(latlng1, latlng2) {
+ return `${bearing(latlng1.lat, latlng1.lng, latlng2.lat, latlng2.lng).toFixed()}/${(latlng1.distanceTo(latlng2) / 1852).toFixed(0)}`;
+}
diff --git a/frontend/react/src/types/types.ts b/frontend/react/src/types/types.ts
index 522c6235..45eb78ed 100644
--- a/frontend/react/src/types/types.ts
+++ b/frontend/react/src/types/types.ts
@@ -26,6 +26,9 @@ export type MapOptions = {
cameraPluginEnabled: boolean;
cameraPluginMode: string;
tabletMode: boolean;
+ showUnitBullseyes: false;
+ showUnitBRAA: false;
+ AWACSMode: false;
};
export type MapHiddenTypes = {
diff --git a/frontend/react/src/ui/contextmenus/mapcontextmenu.tsx b/frontend/react/src/ui/contextmenus/mapcontextmenu.tsx
index dcb215b0..1934063c 100644
--- a/frontend/react/src/ui/contextmenus/mapcontextmenu.tsx
+++ b/frontend/react/src/ui/contextmenus/mapcontextmenu.tsx
@@ -10,6 +10,7 @@ import {
ContextActionChangedEvent,
ContextActionSetChangedEvent,
MapContextMenuRequestEvent,
+ SelectedUnitsChangedEvent,
SelectionClearedEvent,
UnitContextMenuRequestEvent,
} from "../../events";
@@ -24,6 +25,7 @@ export function MapContextMenu(props: {}) {
const [yPosition, setYPosition] = useState(0);
const [latLng, setLatLng] = useState(null as null | LatLng);
const [unit, setUnit] = useState(null as null | Unit);
+ const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
var contentRef = useRef(null);
@@ -45,6 +47,7 @@ export function MapContextMenu(props: {}) {
setXPosition(getApp().getMap().getContainer().offsetLeft + containerPoint.x);
setYPosition(getApp().getMap().getContainer().offsetTop + containerPoint.y);
});
+ SelectedUnitsChangedEvent.on((selectedUnits) => setSelectedUnits([...selectedUnits]));
}, []);
useEffect(() => {
@@ -70,7 +73,13 @@ export function MapContextMenu(props: {}) {
let reorderedActions: ContextAction[] = contextActionSet
? Object.values(
- contextActionSet.getContextActions(appSubState === UnitControlSubState.MAP_CONTEXT_MENU ? ContextActionTarget.POINT : ContextActionTarget.UNIT)
+ contextActionSet.getContextActions(
+ selectedUnits.length === 1 && unit === selectedUnits[0]
+ ? ContextActionTarget.NONE
+ : appSubState === UnitControlSubState.MAP_CONTEXT_MENU
+ ? ContextActionTarget.POINT
+ : ContextActionTarget.UNIT
+ )
).sort((a: ContextAction, b: ContextAction) => (a.getOptions().type < b.getOptions().type ? -1 : 1))
: [];
@@ -104,7 +113,7 @@ export function MapContextMenu(props: {}) {
${colorString}
`}
onClick={() => {
- if (contextActionIt.getOptions().executeImmediately) {
+ if (contextActionIt.getTarget() === ContextActionTarget.NONE) {
contextActionIt.executeCallback(null, null);
} else {
if (appSubState === UnitControlSubState.MAP_CONTEXT_MENU) {
@@ -113,10 +122,13 @@ export function MapContextMenu(props: {}) {
contextActionIt.executeCallback(unit, null);
}
window.setTimeout(() => {
- if (getApp().getSubState() === UnitControlSubState.MAP_CONTEXT_MENU || getApp().getSubState() === UnitControlSubState.UNIT_CONTEXT_MENU) {
- getApp().setState(OlympusState.UNIT_CONTROL)
+ if (
+ getApp().getSubState() === UnitControlSubState.MAP_CONTEXT_MENU ||
+ getApp().getSubState() === UnitControlSubState.UNIT_CONTEXT_MENU
+ ) {
+ getApp().setState(OlympusState.UNIT_CONTROL);
}
- }, 200)
+ }, 200);
}
}}
>
diff --git a/frontend/react/src/ui/panels/awacsmenu.tsx b/frontend/react/src/ui/panels/awacsmenu.tsx
new file mode 100644
index 00000000..8d9fcbd4
--- /dev/null
+++ b/frontend/react/src/ui/panels/awacsmenu.tsx
@@ -0,0 +1,108 @@
+import React, { useEffect, useState } from "react";
+import { Menu } from "./components/menu";
+import { OlToggle } from "../components/oltoggle";
+import { MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
+import { AWACSReferenceChangedEvent as AWACSReferenceUnitChangedEvent, HotgroupsChangedEvent, MapOptionsChangedEvent } from "../../events";
+import { getApp } from "../../olympusapp";
+import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
+import { Coalition } from "../../types/types";
+import { FaQuestionCircle } from "react-icons/fa";
+import { Unit } from "../../unit/unit";
+
+export function AWACSMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
+ const [callsign, setCallsign] = useState("Magic");
+ const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
+ const [coalition, setCoalition] = useState("blue" as Coalition);
+ const [hotgroups, setHotgroups] = useState({} as { [key: number]: Unit[] });
+ const [referenceUnit, setReferenceUnit] = useState(null as Unit | null);
+
+ useEffect(() => {
+ MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
+ HotgroupsChangedEvent.on((hotgroups) => setHotgroups({ ...hotgroups }));
+ AWACSReferenceUnitChangedEvent.on((unit) => setReferenceUnit(unit));
+ }, []);
+
+ const enemyGroups = Object.values(hotgroups).filter((hotgroup) => {
+ return hotgroup.every((unit) => unit.getCoalition() !== coalition)
+ })
+
+ return (
+
+ );
+}
diff --git a/frontend/react/src/ui/panels/coordinatespanel.tsx b/frontend/react/src/ui/panels/coordinatespanel.tsx
index b74b1d60..09aa018f 100644
--- a/frontend/react/src/ui/panels/coordinatespanel.tsx
+++ b/frontend/react/src/ui/panels/coordinatespanel.tsx
@@ -28,9 +28,9 @@ export function CoordinatesPanel(props: {}) {
return (
diff --git a/frontend/react/src/ui/panels/header.tsx b/frontend/react/src/ui/panels/header.tsx
index 4d0f980c..046529d7 100644
--- a/frontend/react/src/ui/panels/header.tsx
+++ b/frontend/react/src/ui/panels/header.tsx
@@ -35,7 +35,8 @@ export function Header() {
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
MapSourceChangedEvent.on((source) => setMapSource(source));
ConfigLoadedEvent.on((config: OlympusConfig) => {
- var sources = Object.keys(config.frontend.mapMirrors).concat(Object.keys(config.frontend.mapLayers));
+
+ var sources = Object.keys(config.frontend.mapMirrors).concat(Object.keys(config.frontend.mapLayers)).concat(getApp().getMap().getLayers());
setMapSources(sources);
});
CommandModeOptionsChangedEvent.on((commandModeOptions) => {
diff --git a/frontend/react/src/ui/panels/hotgroupsbar.tsx b/frontend/react/src/ui/panels/hotgroupsbar.tsx
index 336bed7e..c892ccf5 100644
--- a/frontend/react/src/ui/panels/hotgroupsbar.tsx
+++ b/frontend/react/src/ui/panels/hotgroupsbar.tsx
@@ -1,15 +1,13 @@
import React, { useEffect, useState } from "react";
-import { AppStateChangedEvent, ContextActionChangedEvent, HotgroupsChangedEvent, InfoPopupEvent } from "../../events";
+import { AppStateChangedEvent, HotgroupsChangedEvent } from "../../events";
import { OlympusState } from "../../constants/constants";
-import { ContextAction } from "../../unit/contextaction";
import { OlStateButton } from "../components/olstatebutton";
-import { faUserGroup } from "@fortawesome/free-solid-svg-icons";
import { getApp } from "../../olympusapp";
+import { Unit } from "../../unit/unit";
export function HotGroupBar(props: {}) {
- const [hotgroups, setHotgroups] = useState({} as { [key: number]: number });
+ const [hotgroups, setHotgroups] = useState({} as { [key: number]: Unit[] });
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
- const [menuHidden, setMenuHidden] = useState(false);
useEffect(() => {
AppStateChangedEvent.on((state, subState) => setAppState(state));
@@ -18,14 +16,11 @@ export function HotGroupBar(props: {}) {
return (
- {Object.entries(hotgroups).map(([hotgroup, counter]) => {
+ {Object.entries(hotgroups).map(([hotgroup, units]) => {
return (
-
{getApp().getUnitsManager().selectUnitsByHotgroup(parseInt(hotgroup))}} tooltip="">
+ {getApp().getUnitsManager().selectUnitsByHotgroup(parseInt(hotgroup))}} tooltip="Select units of this hotgroup" className={`
+ min-h-12 min-w-12
+ `}>
- {counter}
+ {units.length}
diff --git a/frontend/react/src/ui/panels/minimappanel.tsx b/frontend/react/src/ui/panels/minimappanel.tsx
index a86c6a1c..cb694c43 100644
--- a/frontend/react/src/ui/panels/minimappanel.tsx
+++ b/frontend/react/src/ui/panels/minimappanel.tsx
@@ -58,7 +58,7 @@ export function MiniMapPanel(props: {}) {
${mapOptions.showMinimap ? `bottom-[188px]` : `bottom-[20px]`}
flex w-[288px] items-center justify-between
${mapOptions.showMinimap ? `rounded-t-lg` : `rounded-lg`}
- bg-gray-200 p-3 text-sm backdrop-blur-lg backdrop-grayscale
+ h-12 bg-gray-200 px-3 text-sm backdrop-blur-lg backdrop-grayscale
dark:bg-olympus-800/90 dark:text-gray-200
`}
>
diff --git a/frontend/react/src/ui/panels/radiossummarypanel.tsx b/frontend/react/src/ui/panels/radiossummarypanel.tsx
index 5c846c37..34ff7991 100644
--- a/frontend/react/src/ui/panels/radiossummarypanel.tsx
+++ b/frontend/react/src/ui/panels/radiossummarypanel.tsx
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import { AudioSinksChangedEvent } from "../../events";
import { AudioSink } from "../../audio/audiosink";
import { RadioSink } from "../../audio/radiosink";
-import { FaJetFighter, FaRadio } from "react-icons/fa6";
+import { FaJetFighter, FaRadio, FaVolumeHigh } from "react-icons/fa6";
import { OlStateButton } from "../components/olstatebutton";
import { UnitSink } from "../../audio/unitsink";
@@ -19,13 +19,12 @@ export function RadiosSummaryPanel(props: {}) {
-
+
{audioSinks.filter((audioSinks) => audioSinks instanceof RadioSink).length > 0 &&
audioSinks
.filter((audioSinks) => audioSinks instanceof RadioSink)
@@ -43,15 +42,14 @@ export function RadiosSummaryPanel(props: {}) {
}}
tooltip="Click to talk, lights up when receiving"
buttonColor={radioSink.getReceiving() ? "white" : null}
+ className="min-h-12 min-w-12"
>
-
{idx + 1}
+
{idx + 1}
);
})}
-
- {audioSinks.filter((audioSinks) => audioSinks instanceof UnitSink).length > 0 &&
}
{audioSinks.filter((audioSinks) => audioSinks instanceof UnitSink).length > 0 &&
audioSinks
.filter((audioSinks) => audioSinks instanceof UnitSink)
@@ -68,8 +66,11 @@ export function RadiosSummaryPanel(props: {}) {
unitSink.setPtt(false);
}}
tooltip="Click to talk"
+ className="min-h-12 min-w-12"
>
-
{idx + 1}
+
{idx + 1}
);
})}
diff --git a/frontend/react/src/ui/panels/sidebar.tsx b/frontend/react/src/ui/panels/sidebar.tsx
index 60390e37..0e87c1a0 100644
--- a/frontend/react/src/ui/panels/sidebar.tsx
+++ b/frontend/react/src/ui/panels/sidebar.tsx
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";
import { OlStateButton } from "../components/olstatebutton";
-import { faGamepad, faPencil, faEllipsisV, faCog, faQuestionCircle, faPlusSquare, faVolumeHigh, faJ, faCrown } from "@fortawesome/free-solid-svg-icons";
+import { faGamepad, faPencil, faEllipsisV, faCog, faQuestionCircle, faPlusSquare, faVolumeHigh, faJ, faCrown, faA } from "@fortawesome/free-solid-svg-icons";
import { getApp } from "../../olympusapp";
import { NO_SUBSTATE, OlympusState, OlympusSubState, SpawnSubState } from "../../constants/constants";
import { AppStateChangedEvent } from "../../events";
@@ -65,13 +65,21 @@ export function SideBar() {
icon={faVolumeHigh}
tooltip="Hide/show audio menu"
>
-
{
getApp().setState(appState !== OlympusState.JTAC ? OlympusState.JTAC : OlympusState.IDLE);
}}
checked={appState === OlympusState.JTAC}
icon={faJ}
tooltip="Hide/show JTAC menu"
+ >{*/}
+
{
+ getApp().setState(appState !== OlympusState.AWACS ? OlympusState.AWACS : OlympusState.IDLE);
+ }}
+ checked={appState === OlympusState.AWACS}
+ icon={faA}
+ tooltip="Hide/show AWACS menu"
>
{
diff --git a/frontend/react/src/ui/panels/unitcontrolbar.tsx b/frontend/react/src/ui/panels/unitcontrolbar.tsx
index edbb374c..43bd83fa 100644
--- a/frontend/react/src/ui/panels/unitcontrolbar.tsx
+++ b/frontend/react/src/ui/panels/unitcontrolbar.tsx
@@ -3,9 +3,8 @@ import { ContextActionSet } from "../../unit/contextactionset";
import { OlStateButton } from "../components/olstatebutton";
import { getApp } from "../../olympusapp";
import { ContextAction } from "../../unit/contextaction";
-import { CONTEXT_ACTION_COLORS, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
-import { FaInfoCircle } from "react-icons/fa";
-import { FaChevronDown, FaChevronLeft, FaChevronRight, FaChevronUp } from "react-icons/fa6";
+import { CONTEXT_ACTION_COLORS, ContextActionTarget, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
+import { FaChevronDown,FaChevronUp } from "react-icons/fa6";
import { OlympusState } from "../../constants/constants";
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent } from "../../events";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -83,7 +82,7 @@ export function UnitControlBar(props: {}) {
tooltip={contextActionIt.getLabel()}
buttonColor={CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type ?? 0]}
onClick={() => {
- if (contextActionIt.getOptions().executeImmediately) {
+ if (contextActionIt.getTarget() === ContextActionTarget.NONE) {
contextActionIt.executeCallback(null, null);
} else {
contextActionIt !== contextAction
diff --git a/frontend/react/src/ui/panels/unitcontrolmenu.tsx b/frontend/react/src/ui/panels/unitcontrolmenu.tsx
index e6531466..905b83da 100644
--- a/frontend/react/src/ui/panels/unitcontrolmenu.tsx
+++ b/frontend/react/src/ui/panels/unitcontrolmenu.tsx
@@ -12,7 +12,6 @@ import {
emissionsCountermeasures,
maxAltitudeValues,
maxSpeedValues,
- minAltitudeValues,
reactionsToThreat,
speedIncrements,
} from "../../constants/constants";
diff --git a/frontend/react/src/ui/ui.tsx b/frontend/react/src/ui/ui.tsx
index 392b8b13..22950104 100644
--- a/frontend/react/src/ui/ui.tsx
+++ b/frontend/react/src/ui/ui.tsx
@@ -31,6 +31,7 @@ import { HotGroupBar } from "./panels/hotgroupsbar";
import { SpawnContextMenu } from "./contextmenus/spawncontextmenu";
import { CoordinatesPanel } from "./panels/coordinatespanel";
import { RadiosSummaryPanel } from "./panels/radiossummarypanel";
+import { AWACSMenu } from "./panels/awacsmenu";
export type OlympusUIState = {
mainMenuVisible: boolean;
@@ -93,7 +94,8 @@ export function UI() {
open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.UNIT_EXPLOSION_MENU}
onClose={() => getApp().setState(OlympusState.IDLE)}
/>
- getApp().setState(OlympusState.IDLE)} />
+ {/*} getApp().setState(OlympusState.IDLE)} />{*/}
+ getApp().setState(OlympusState.IDLE)} />
diff --git a/frontend/react/src/unit/contextaction.ts b/frontend/react/src/unit/contextaction.ts
index 56742ce5..b4241cb5 100644
--- a/frontend/react/src/unit/contextaction.ts
+++ b/frontend/react/src/unit/contextaction.ts
@@ -4,7 +4,6 @@ import { LatLng } from "leaflet";
import { ContextActionTarget, ContextActionType } from "../constants/constants";
export interface ContextActionOptions {
- executeImmediately?: boolean;
type: ContextActionType;
code: string | null;
shiftKey?: boolean;
@@ -32,7 +31,6 @@ export class ContextAction {
this.#callback = callback;
this.#icon = icon;
this.#options = {
- executeImmediately: false,
...options,
};
}
diff --git a/frontend/react/src/unit/contextactionset.ts b/frontend/react/src/unit/contextactionset.ts
index 21fe4c4c..cb0f30cd 100644
--- a/frontend/react/src/unit/contextactionset.ts
+++ b/frontend/react/src/unit/contextactionset.ts
@@ -21,7 +21,7 @@ export class ContextActionSet {
}
getContextActions(targetFilter?: ContextActionTarget) {
- if (targetFilter) {
+ if (targetFilter !== undefined) {
var filteredContextActionSet = new ContextActionSet();
Object.keys(this.#contextActions).forEach((key) => {
if (this.#contextActions[key].getTarget() === targetFilter) filteredContextActionSet[key] = this.#contextActions[key];
diff --git a/frontend/react/src/unit/unit.ts b/frontend/react/src/unit/unit.ts
index 63ee7055..4e4bf28b 100644
--- a/frontend/react/src/unit/unit.ts
+++ b/frontend/react/src/unit/unit.ts
@@ -969,6 +969,27 @@ export abstract class Unit extends CustomMarker {
el.appendChild(summary);
}
+ var tactical = document.createElement("div");
+ tactical.classList.add("unit-tactical");
+ if (iconOptions.showBullseyes) {
+ var bullseyes = document.createElement("div");
+ bullseyes.classList.add("unit-bullseyes");
+ var blueBullseye = document.createElement("div");
+ blueBullseye.classList.add("unit-blue-bullseye");
+ var redBullseye = document.createElement("div");
+ redBullseye.classList.add("unit-red-bullseye");
+ bullseyes.appendChild(blueBullseye);
+ bullseyes.appendChild(redBullseye);
+ tactical.appendChild(bullseyes);
+ }
+
+ if (iconOptions.showBRAA) {
+ var BRAA = document.createElement("div");
+ BRAA.classList.add("unit-braa");
+ tactical.appendChild(BRAA);
+ }
+ el.appendChild(tactical)
+
this.getElement()?.appendChild(el);
}
@@ -1490,6 +1511,21 @@ export abstract class Unit extends CustomMarker {
const hotgroupEl = element.querySelector(".unit-hotgroup-id") as HTMLElement;
if (hotgroupEl) hotgroupEl.innerText = String(this.#hotgroup);
}
+
+ /* Set bullseyes positions */
+ const bullseyes = getApp().getMissionManager().getBullseyes();
+ const blueBullseye = `${bearing(bullseyes[2].getLatLng().lat, bullseyes[2].getLatLng().lng, this.getLatLng().lat, this.getLatLng().lng).toFixed()}/${(bullseyes[2].getLatLng().distanceTo(this.getLatLng()) / 1852).toFixed(0)}`
+ const redBullseye = `${bearing(bullseyes[1].getLatLng().lat, bullseyes[1].getLatLng().lng, this.getLatLng().lat, this.getLatLng().lng).toFixed()}/${(bullseyes[1].getLatLng().distanceTo(this.getLatLng()) / 1852).toFixed(0)}`
+ if (element.querySelector(".unit-blue-bullseye")) (element.querySelector(".unit-blue-bullseye")).innerText = `${blueBullseye}`;
+ if (element.querySelector(".unit-red-bullseye")) (element.querySelector(".unit-red-bullseye")).innerText = `${redBullseye}`;
+
+ /* Set BRAA */
+ const reference = getApp().getUnitsManager().getAWACSReference();
+ if (reference && reference !== this) {
+ const BRAA = `${bearing(reference.getLatLng().lat, reference.getLatLng().lng, this.getLatLng().lat, this.getLatLng().lng).toFixed()}/${(reference.getLatLng().distanceTo(this.getLatLng()) / 1852).toFixed(0)}`
+ if (element.querySelector(".unit-braa")) (element.querySelector(".unit-braa")).innerText = `${BRAA}`;
+ }
+ else if (element.querySelector(".unit-braa")) (element.querySelector(".unit-braa")).innerText = ``;
}
/* Set vertical offset for altitude stacking */
@@ -1770,7 +1806,9 @@ export abstract class AirUnit extends Unit {
showSummary: belongsToCommandedCoalition || this.getDetectionMethods().some((value) => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value)),
showCallsign: belongsToCommandedCoalition,
rotateToHeading: false,
- };
+ showBullseyes: true,
+ showBRAA: true,
+ } as ObjectIconOptions;
}
appendContextActions(contextActionSet: ContextActionSet) {
@@ -1783,6 +1821,7 @@ export abstract class AirUnit extends Unit {
/* Context actions that require a target unit */
contextActionSet.addContextAction(this, ContextActions.ATTACK);
contextActionSet.addContextAction(this, ContextActions.FOLLOW);
+ contextActionSet.addContextAction(this, ContextActions.SET_AWACS_REFERENCE)
if (this.canTargetPoint()) {
/* Context actions that require a target position */
@@ -1858,7 +1897,9 @@ export class GroundUnit extends Unit {
showSummary: false,
showCallsign: belongsToCommandedCoalition,
rotateToHeading: false,
- };
+ showBullseyes: false,
+ showBRAA: false,
+ } as ObjectIconOptions;
}
appendContextActions(contextActionSet: ContextActionSet) {
@@ -1927,7 +1968,9 @@ export class NavyUnit extends Unit {
showSummary: false,
showCallsign: belongsToCommandedCoalition,
rotateToHeading: false,
- };
+ showBullseyes: false,
+ showBRAA: false,
+ } as ObjectIconOptions;
}
appendContextActions(contextActionSet: ContextActionSet) {
diff --git a/frontend/react/src/unit/unitsmanager.ts b/frontend/react/src/unit/unitsmanager.ts
index c443fd0a..700189a6 100644
--- a/frontend/react/src/unit/unitsmanager.ts
+++ b/frontend/react/src/unit/unitsmanager.ts
@@ -23,6 +23,7 @@ import { UnitDataFileImport } from "./importexport/unitdatafileimport";
import { CoalitionCircle } from "../map/coalitionarea/coalitioncircle";
import { ContextActionSet } from "./contextactionset";
import {
+ AWACSReferenceChangedEvent,
CommandModeOptionsChangedEvent,
ContactsUpdatedEvent,
HotgroupsChangedEvent,
@@ -49,6 +50,7 @@ export class UnitsManager {
#unitDataImport!: UnitDataFileImport;
#unitDatabase: UnitDatabase;
#protectionCallback: (units: Unit[]) => void = (units) => {};
+ #AWACSReference: Unit | null = null;
constructor() {
this.#unitDatabase = new UnitDatabase();
@@ -1125,15 +1127,15 @@ export class UnitsManager {
units.forEach((unit: Unit) => unit.setHotgroup(hotgroup));
this.#showActionMessage(units, `added to hotgroup ${hotgroup}`);
- let hotgroups: { [key: number]: number } = {};
+ let hotgroups: { [key: number]: Unit[] } = {};
for (let ID in this.#units) {
const unit = this.#units[ID];
if (unit.getAlive() && !unit.getHuman()) {
const hotgroup = unit.getHotgroup();
if (hotgroup) {
if (!(hotgroup in hotgroups)) {
- hotgroups[hotgroup] = 1;
- } else hotgroups[hotgroup] += 1;
+ hotgroups[hotgroup] = [unit];
+ } else hotgroups[hotgroup].push(unit);
}
}
}
@@ -1544,6 +1546,15 @@ export class UnitsManager {
this.#protectionCallback(this.getSelectedUnits());
}
+ setAWACSReference(ID) {
+ this.#AWACSReference = this.#units[ID] ?? null;
+ AWACSReferenceChangedEvent.dispatch(this.#AWACSReference)
+ }
+
+ getAWACSReference() {
+ return this.#AWACSReference;
+ }
+
/***********************************************/
#onUnitSelection(unit: Unit) {
if (this.getSelectedUnits().length > 0) {