From 2a00bab1496325bfb700d168a68d1e744386e7e6 Mon Sep 17 00:00:00 2001 From: MarcoJayUsai Date: Fri, 21 Mar 2025 12:33:15 +0100 Subject: [PATCH] feat(alarm state): added unit control panel buttons (WIP) --- frontend/react/src/constants/constants.ts | 7 +- frontend/react/src/interfaces.ts | 7 ++ .../src/map/markers/stylesheets/units.css | 4 +- frontend/react/src/other/utils.ts | 12 +-- .../react/src/ui/panels/unitcontrolmenu.tsx | 81 ++++++++++++++++++- frontend/react/src/unit/unit.ts | 34 +++++--- 6 files changed, 121 insertions(+), 24 deletions(-) diff --git a/frontend/react/src/constants/constants.ts b/frontend/react/src/constants/constants.ts index 2174cc44..3e74f4cd 100644 --- a/frontend/react/src/constants/constants.ts +++ b/frontend/react/src/constants/constants.ts @@ -96,13 +96,8 @@ export const states: string[] = [ UnitState.LAND_AT_POINT, ]; -export enum RADAR_STATES { - RED = 'red', - GREEN = 'green', - AUTO = 'auto' -} - export const ROEs: string[] = ["free", "designated", "", "return", "hold"]; +export const alarmStates: string[] = ["green", "auto", "red"]; export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"]; export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"]; diff --git a/frontend/react/src/interfaces.ts b/frontend/react/src/interfaces.ts index d8f892de..1c0d3726 100644 --- a/frontend/react/src/interfaces.ts +++ b/frontend/react/src/interfaces.ts @@ -213,6 +213,7 @@ export interface UnitData { markerCategory: string; ID: number; alive: boolean; + alarmState: AlarmState | undefined; human: boolean; controlled: boolean; coalition: string; @@ -395,4 +396,10 @@ export interface Drawing { hiddenOnPlanner?: boolean; file?: string; scale?: number; +} + +export enum AlarmState { + AUTO = 'auto', + GREEN = 'green', + RED = 'red' } \ No newline at end of file diff --git a/frontend/react/src/map/markers/stylesheets/units.css b/frontend/react/src/map/markers/stylesheets/units.css index da063531..9583e1f4 100644 --- a/frontend/react/src/map/markers/stylesheets/units.css +++ b/frontend/react/src/map/markers/stylesheets/units.css @@ -631,12 +631,12 @@ left: 35px; bottom: 8px; } -.unit[data-radar-state="GREEN"] .unit-radar-state { +.unit[data-radar-state="green"] .unit-radar-state { border: 1px solid white; background: rgb(0, 226, 0); } -.unit[data-radar-state="RED"] .unit-radar-state { +.unit[data-radar-state="red"] .unit-radar-state { border: 1px solid white; background: red; } diff --git a/frontend/react/src/other/utils.ts b/frontend/react/src/other/utils.ts index b4506c10..68f5f004 100644 --- a/frontend/react/src/other/utils.ts +++ b/frontend/react/src/other/utils.ts @@ -547,7 +547,7 @@ export function roundToNearestFive(number) { return Math.round(number / 5) * 5; } -export function toDCSFormationOffset(offset: {x: number, y: number, z: number}) { +export function toDCSFormationOffset(offset: { x: number, y: number, z: number }) { // X: front-rear, positive front // Y: top-bottom, positive top // Z: left-right, positive right @@ -555,7 +555,7 @@ export function toDCSFormationOffset(offset: {x: number, y: number, z: number}) return { x: -offset.y, y: offset.z, z: offset.x }; } -export function fromDCSFormationOffset(offset: {x: number, y: number, z: number}) { +export function fromDCSFormationOffset(offset: { x: number, y: number, z: number }) { return { x: offset.z, y: -offset.x, z: offset.y }; } @@ -649,10 +649,10 @@ export function normalizeAngle(angle: number): number { } export function decimalToRGBA(decimal: number): string { - const r = (decimal >>> 24) & 0xff; - const g = (decimal >>> 16) & 0xff; - const b = (decimal >>> 8) & 0xff; - const a = (decimal & 0xff) / 255; + const r = (decimal >>> 24) & 0xff; + const g = (decimal >>> 16) & 0xff; + const b = (decimal >>> 8) & 0xff; + const a = (decimal & 0xff) / 255; return `rgba(${r}, ${g}, ${b}, ${a.toFixed(2)})`; } diff --git a/frontend/react/src/ui/panels/unitcontrolmenu.tsx b/frontend/react/src/ui/panels/unitcontrolmenu.tsx index 56d810ba..369c09cf 100644 --- a/frontend/react/src/ui/panels/unitcontrolmenu.tsx +++ b/frontend/react/src/ui/panels/unitcontrolmenu.tsx @@ -8,6 +8,7 @@ import { OlButtonGroup, OlButtonGroupItem } from "../components/olbuttongroup"; import { OlCheckbox } from "../components/olcheckbox"; import { ROEs, + alarmStates, altitudeIncrements, emissionsCountermeasures, maxAltitudeValues, @@ -53,7 +54,7 @@ import { OlSearchBar } from "../components/olsearchbar"; import { OlDropdown, OlDropdownItem } from "../components/oldropdown"; import { FaRadio, FaVolumeHigh } from "react-icons/fa6"; import { OlNumberInput } from "../components/olnumberinput"; -import { GeneralSettings, Radio, TACAN } from "../../interfaces"; +import { AlarmState, GeneralSettings, Radio, TACAN } from "../../interfaces"; import { OlStringInput } from "../components/olstringinput"; import { OlFrequencyInput } from "../components/olfrequencyinput"; import { UnitSink } from "../../audio/unitsink"; @@ -85,6 +86,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) { radio: undefined as undefined | Radio, TACAN: undefined as undefined | TACAN, generalSettings: undefined as undefined | GeneralSettings, + alarmState: undefined as undefined | AlarmState }; } @@ -165,6 +167,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) { onOff: (unit: Unit) => unit.getOnOff(), radio: (unit: Unit) => unit.getRadio(), TACAN: (unit: Unit) => unit.getTACAN(), + alarmState: (unit: Unit) => unit.getAlarmState(), generalSettings: (unit: Unit) => unit.getGeneralSettings(), isAudioSink: (unit: Unit) => { return ( @@ -818,6 +821,82 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) { )} {/* ============== Rules of Engagement END ============== */} + + {/* ============== Alarm state selector START ============== */} + {selectedUnitsData.alarmState && +
+ + Alarm State + + ( + +
Sets the alarm state of the unit, in order:
+
+
+ {" "} + Green: The unit will not engage with its sensors in any circumstances. The unit will be able to move. +
+
+ {" "} + {" "} +
+ {" "} + Auto: The unit will use its sensors to engage based on its ROE. +
+
+
+ {" "} + Red: The unit will be actively searching for target with its sensors. For some units, this will deploy + the radar and make the unit not able to move. +
+
+
+ } + /> + )} + tooltipRelativeToParent={true} + > + {[olButtonsRoeHold, olButtonsRoeReturn, olButtonsRoeDesignated].map((icon, idx) => { + return ( + { + // TODO: set alarm state + // getApp() + // .getUnitsManager() + // .setROE(ROEs[convertROE(idx)], null, () => + // setForcedUnitsData({ + // ...forcedUnitsData, + // ROE: ROEs[convertROE(idx)], + // }) + // ); + }} + active={selectedUnitsData.alarmState === alarmStates[idx]} + icon={icon} + /> + ); + })} + + + } + {/* ============== Alarm state selector END ============== */} + + {selectedCategories.every((category) => { return ["Aircraft", "Helicopter"].includes(category); }) && ( diff --git a/frontend/react/src/unit/unit.ts b/frontend/react/src/unit/unit.ts index 3e809105..399b926c 100644 --- a/frontend/react/src/unit/unit.ts +++ b/frontend/react/src/unit/unit.ts @@ -51,7 +51,7 @@ import { } from "../constants/constants"; import { DataExtractor } from "../server/dataextractor"; import { Weapon } from "../weapon/weapon"; -import { Ammo, Contact, GeneralSettings, LoadoutBlueprint, ObjectIconOptions, Offset, Radio, TACAN, UnitBlueprint, UnitData } from "../interfaces"; +import { AlarmState, Ammo, Contact, GeneralSettings, LoadoutBlueprint, ObjectIconOptions, Offset, Radio, TACAN, UnitBlueprint, UnitData } from "../interfaces"; import { RangeCircle } from "../map/rangecircle"; import { Group } from "./group"; import { ContextActionSet } from "./contextactionset"; @@ -83,7 +83,7 @@ export abstract class Unit extends CustomMarker { /* Data controlled directly by the backend. No setters are provided to avoid misalignments */ #alive: boolean = false; - #radarState: string = ''; + #alarmState: AlarmState | undefined = undefined; #human: boolean = false; #controlled: boolean = false; #coalition: string = "neutral"; @@ -333,6 +333,9 @@ export abstract class Unit extends CustomMarker { getRaceTrackBearing() { return this.#racetrackBearing; } + getAlarmState() { + return this.#alarmState; + } static getConstructor(type: string) { if (type === "GroundUnit") return GroundUnit; @@ -490,7 +493,19 @@ export abstract class Unit extends CustomMarker { updateMarker = true; break; case DataIndexes.radarState: - this.setRadarState(dataExtractor.extractString()); + let stringAlarmState = dataExtractor.extractString(); + switch (stringAlarmState) { + case 'RED': + this.setRadarState(AlarmState.RED); + break; + case 'GREEN': + this.setRadarState(AlarmState.GREEN); + break; + case '': + this.setRadarState(AlarmState.AUTO); + default: + break; + } updateMarker = true; break; case DataIndexes.human: @@ -755,6 +770,7 @@ export abstract class Unit extends CustomMarker { racetrackLength: this.#racetrackLength, racetrackAnchor: this.#racetrackAnchor, racetrackBearing: this.#racetrackBearing, + alarmState: this.#alarmState }; } @@ -769,11 +785,11 @@ export abstract class Unit extends CustomMarker { } } - setRadarState(newRadarState: string) { - if (newRadarState != this.#radarState) { - this.#radarState = newRadarState; + setRadarState(newRadarState: AlarmState) { + if (newRadarState != this.#alarmState) { + this.#alarmState = newRadarState; // TODO: check if an event is needed -- surely yes to update the UI - console.log('----radar state updated: ', this.#radarState); + console.log('----radar state updated: ', this.#alarmState); this.#updateMarker(); } } @@ -1053,7 +1069,7 @@ export abstract class Unit extends CustomMarker { } /* Radar state indicator */ - if (this.#radarState !== '') { + if (this.#alarmState) { var radarStateIcon = document.createElement("div"); radarStateIcon.classList.add("unit-radar-state"); el.append(radarStateIcon); @@ -1587,7 +1603,7 @@ export abstract class Unit extends CustomMarker { element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive); /* Set RED/GREEN state*/ - if (this.#radarState !== '') element.querySelector(".unit")?.setAttribute("data-radar-state", this.#radarState); + if (this.#alarmState) element.querySelector(".unit")?.setAttribute("data-radar-state", this.#alarmState); /* Set current unit state */ if (this.#human) {