From 1f51d6912671437b4dcb1b9139a15c0339e968bf Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Tue, 11 Apr 2023 15:20:17 +0200 Subject: [PATCH 01/11] Added basic tanker and AWACS enroute tasks Tanker not working propertly yet, frequency setting still needed --- client/src/@types/unit.d.ts | 2 ++ client/src/server/server.ts | 18 ++++++++++++ client/src/units/unit.ts | 50 +++++++++++++++++++++++++++++--- client/src/units/unitsmanager.ts | 27 +++++++++++++++++ scripts/OlympusCommand.lua | 21 ++++++++++++-- src/core/include/unit.h | 6 ++++ src/core/src/airunit.cpp | 40 +++++++++++++++++++++++-- src/core/src/scheduler.cpp | 20 +++++++++++++ 8 files changed, 176 insertions(+), 8 deletions(-) diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index b14a00b3..ad14a6ec 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -41,6 +41,8 @@ interface TaskData { activePath: any; targetSpeed: number; targetAltitude: number; + isTanker: boolean; + isAWACS: boolean; } interface OptionsData { diff --git a/client/src/server/server.ts b/client/src/server/server.ts index f45989fe..4e4c4e02 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -176,3 +176,21 @@ export function setReactionToThreat(ID: number, reactionToThreat: string) { var data = {"setReactionToThreat": command} POST(data, () => { }); } + +export function refuel(ID: number) { + var command = { "ID": ID }; + var data = { "refuel": command } + POST(data, () => { }); +} + +export function setTanker(ID: number, state: boolean) { + var command = { "ID": ID, "state": state }; + var data = { "setIsTanker": command } + POST(data, () => { }); +} + +export function setAWACS(ID: number, state: boolean) { + var command = { "ID": ID, "state": state }; + var data = { "setIsAWACS": command } + POST(data, () => { }); +} diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 616d098c..41efeeb5 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -1,7 +1,7 @@ import { Marker, LatLng, Polyline, Icon, DivIcon } from 'leaflet'; import { getMap, getUnitsManager } from '..'; import { rad2deg } from '../other/utils'; -import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed } from '../server/server'; +import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setTanker, setAWACS } from '../server/server'; import { aircraftDatabase } from './aircraftdatabase'; import { groundUnitsDatabase } from './groundunitsdatabase'; @@ -50,6 +50,8 @@ export class Unit extends Marker { activePath: {}, targetSpeed: 0, targetAltitude: 0, + isTanker: false, + isAWACS: false, }, optionsData: { ROE: "", @@ -363,6 +365,18 @@ export class Unit extends Marker { deleteUnit(this.ID); } + refuel() { + refuel(this.ID); + } + + toggleTanker() { + setTanker(this.ID, !this.getTaskData().isTanker); + } + + toggleAWACS() { + setAWACS(this.ID, !this.getTaskData().isAWACS); + } + #onClick(e: any) { this.#timer = setTimeout(() => { if (!this.#preventClick) { @@ -383,10 +397,32 @@ export class Unit extends Marker { } #onContextMenu(e: any) { - var options = [ - 'Attack' - ] + var options: string[] = []; if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().includes(this))) + { + options = [ + 'Attack' + ] + } + else if (getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) + { + if (this.getBaseData().category == "Aircraft") + { + options.push("Refuel"); // TODO Add some way of knowing which aircraft can AAR + + if (getUnitsManager().getSelectedUnits().length == 1){ + var roles = aircraftDatabase.getByName(this.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles}) + if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker")){ + options.push(this.getTaskData().isTanker? "Stop tanker": "Start tanker"); + } + if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS")){ + options.push(this.getTaskData().isAWACS? "Stop AWACS": "Start AWACS"); + } + } + } + } + + if (options.length > 0) { getMap().showUnitContextMenu(e); getMap().getUnitContextMenu().setOptions(options, (option: string) => { @@ -399,6 +435,12 @@ export class Unit extends Marker { #executeAction(action: string) { if (action === "Attack") getUnitsManager().selectedUnitsAttackUnit(this.ID); + if (action === "Refuel") + getUnitsManager().selectedUnitsRefuel(); + if (action === "Start tanker" || action === "Stop tanker") + getUnitsManager().selectedUnitsToggleTanker(); + if (action === "Start AWACS" || action === "Stop AWACS") + getUnitsManager().selectedUnitsToggleAWACS(); } #updateMarker() { diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index b8badd5e..754705b6 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -325,6 +325,33 @@ export class UnitsManager { } } + selectedUnitsRefuel() + { + var selectedUnits = this.getSelectedUnits(); + for (let idx in selectedUnits) + { + selectedUnits[idx].refuel(); + } + } + + selectedUnitsToggleTanker() + { + var selectedUnits = this.getSelectedUnits(); + for (let idx in selectedUnits) + { + selectedUnits[idx].toggleTanker(); + } + } + + selectedUnitsToggleAWACS() + { + var selectedUnits = this.getSelectedUnits(); + for (let idx in selectedUnits) + { + selectedUnits[idx].toggleAWACS(); + } + } + copyUnits() { this.#copiedUnits = this.getSelectedUnits(); diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 429b6bb2..1563ebfb 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,6 +1,6 @@ local version = "v0.1.1-alpha" -local debug = false +local debug = true Olympus.unitCounter = 1 Olympus.payloadRegistry = {} @@ -79,6 +79,18 @@ function Olympus.buildEnrouteTask(options) } } end + -- Start being an active tanker + elseif options['id'] == 'Tanker' then + task = { + id = 'Tanker', + params = {}, + } + -- Start being an active AWACS + elseif options['id'] == 'AWACS' then + task = { + id = 'AWACS', + params = {}, + } end return task end @@ -86,7 +98,6 @@ end -- Builds a valid task depending on the provided options function Olympus.buildTask(options) local task = nil - -- Engage specific target by ID. Checks if target exists. if options['id'] == 'FollowUnit' and options['leaderID'] and options['offset'] then local leader = Olympus.getUnitByID(options['leaderID']) if leader and leader:isExist() then @@ -100,6 +111,11 @@ function Olympus.buildTask(options) } } end + elseif options['id'] == 'Refuel' then + task = { + id = 'Refueling', + params = {} + } end return task end @@ -356,6 +372,7 @@ function Olympus.setTask(ID, taskOptions) local unit = Olympus.getUnitByID(ID) if unit then local task = Olympus.buildTask(taskOptions); + Olympus.debug("Olympus.setTask " .. Olympus.serializeTable(task), 20) if task then unit:getGroup():getController():setTask(task) Olympus.debug("Olympus.setTask completed successfully", 2) diff --git a/src/core/include/unit.h b/src/core/include/unit.h index e5e27ef0..5dc3913f 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -103,12 +103,16 @@ public: void pushActivePathBack(Coords newActivePathBack); void popActivePathFront(); void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));} + void setIsTanker(bool newIsTanker) { isTanker = newIsTanker; addMeasure(L"isTanker", json::value(newIsTanker));} + void setIsAWACS(bool newIsAWACS) { isAWACS = newIsAWACS; addMeasure(L"isAWACS", json::value(newIsAWACS));} wstring getCurrentTask() { return currentTask; } virtual double getTargetSpeed() { return targetSpeed; }; virtual double getTargetAltitude() { return targetAltitude; }; Coords getActiveDestination() { return activeDestination; } list getActivePath() { return activePath; } int getTargetID() { return targetID; } + bool getIsTanker() { return isTanker; } + bool getIsAWACS() { return isAWACS; } /********** Options data **********/ void setROE(wstring newROE); @@ -168,6 +172,8 @@ protected: list activePath; Coords activeDestination = Coords(0); int targetID = NULL; + bool isTanker = false; + bool isAWACS = false; /********** Options data **********/ wstring ROE = L""; diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index 08922240..00214acc 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -22,6 +22,7 @@ void AirUnit::setState(int newState) { if (state != newState) { + /* Perform any action required when LEAVING a certain state */ switch (state) { case State::IDLE: { break; @@ -44,10 +45,14 @@ void AirUnit::setState(int newState) case State::LAND: { break; } + case State::REFUEL: { + break; + } default: break; } + /* Perform any action required when ENTERING a certain state */ switch (newState) { case State::IDLE: { resetActiveDestination(); @@ -79,6 +84,11 @@ void AirUnit::setState(int newState) resetActiveDestination(); break; } + case State::REFUEL: { + clearActivePath(); + resetActiveDestination(); + break; + } default: break; } @@ -225,8 +235,23 @@ void AirUnit::AIloop() break; } case State::REACH_DESTINATION: { - wstring enrouteTask = L"nil"; - currentTask = L"Reaching destination"; + wstring enrouteTask = L""; + + if (isTanker) + { + enrouteTask = L"{" "id = 'Tanker' }"; + currentTask = L"Tanker"; + } + else if (isAWACS) + { + enrouteTask = L"{" "id = 'AWACS' }"; + currentTask = L"AWACS"; + } + else + { + enrouteTask = L"nil"; + currentTask = L"Reaching destination"; + } if (activeDestination == NULL || !hasTask) { @@ -338,6 +363,17 @@ void AirUnit::AIloop() } break; } + case State::REFUEL: { + if (!hasTask) { + std::wostringstream taskSS; + taskSS << "{" + << "id = 'Refuel'" + << "}"; + Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); + scheduler->appendCommand(command); + hasTask = true; + } + } default: break; } diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index 8e80f9bf..db4e83f1 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -250,6 +250,26 @@ void Scheduler::handleRequest(wstring key, json::value value) int ID = value[L"ID"].as_integer(); unitsManager->deleteUnit(ID); } + else if (key.compare(L"refuel") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + unit->setState(State::REFUEL); + } + else if (key.compare(L"setIsTanker") == 0) + { + int ID = value[L"ID"].as_integer(); + bool state = value[L"state"].as_bool(); + Unit* unit = unitsManager->getUnit(ID); + unit->setIsTanker(state); + } + else if (key.compare(L"setIsAWACS") == 0) + { + int ID = value[L"ID"].as_integer(); + bool state = value[L"state"].as_bool(); + Unit* unit = unitsManager->getUnit(ID); + unit->setIsAWACS(state); + } else { log(L"Unknown command: " + key); From 316261e01e6b725f760f40adb8317d8953df5907 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Tue, 11 Apr 2023 20:30:59 +0200 Subject: [PATCH 02/11] Started to implement advanced settings --- client/demo.js | 5 +- client/public/stylesheets/layout.css | 17 ++++-- client/src/@types/unit.d.ts | 6 ++ client/src/panels/unitcontrolpanel.ts | 88 ++++++++++++++++----------- client/src/units/unit.ts | 20 ++---- client/views/dialogs.ejs | 37 ++++++----- client/views/unitcontrolpanel.ejs | 7 ++- src/core/include/unit.h | 20 +++++- src/core/src/scheduler.cpp | 21 ++++--- 9 files changed, 137 insertions(+), 84 deletions(-) diff --git a/client/demo.js b/client/demo.js index d00a6e8f..be18be2f 100644 --- a/client/demo.js +++ b/client/demo.js @@ -3,7 +3,7 @@ const DEMO_UNIT_DATA = { ["1"]:{ baseData: { AI: true, - name: "F-5E", + name: "KC-135", unitName: "Olympus 1-1", groupName: "Group 1", alive: true, @@ -49,7 +49,8 @@ const DEMO_UNIT_DATA = { currentTask: "Holding", activePath: undefined, targetSpeed: 400, - targetAltitude: 3000 + targetAltitude: 3000, + isTanker: true }, optionsData: { ROE: "Designated", diff --git a/client/public/stylesheets/layout.css b/client/public/stylesheets/layout.css index ed682be4..afa76477 100644 --- a/client/public/stylesheets/layout.css +++ b/client/public/stylesheets/layout.css @@ -138,16 +138,16 @@ dl.ol-data-grid dd { } .ol-dialog-footer { - border-top:1px solid var( --background-grey ); - padding-top:15px; + border-top: 1px solid var( --background-grey ); + padding-top: 15px; + display: flex; + row-gap: 10px; } .ol-dialog.scrollable .ol-dialog-content { overflow-y: auto; } - - .ol-checkbox label { align-items: center; cursor: pointer; @@ -203,6 +203,15 @@ dl.ol-data-grid dd { content: "\d7"; } +.ol-button-apply { + background: transparent; + border: 1px solid white; +} + +.ol-button-apply::before { + content: "\2713"; +} + .ol-button-settings { background-color: var( --background-slate-blue ); } diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index ad14a6ec..fca60a3b 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -43,6 +43,12 @@ interface TaskData { targetAltitude: number; isTanker: boolean; isAWACS: boolean; + radioOn: boolean; + TACANOn: boolean; + radioFrequency: number; + TACANChannel: number; + TACANXY: string; + TACANCallsign: string; } interface OptionsData { diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 91550e75..f3c28d63 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -7,11 +7,8 @@ import { Aircraft, GroundUnit, Unit } from "../units/unit"; import { UnitDatabase } from "../units/unitdatabase"; import { Panel } from "./panel"; -// const ROEs: string[] = ["Free", "Designated free", "Designated", "Return", "Hold"]; // Full list -// const reactionsToThreat: string[] = ["None", "Passive", "Evade", "Escape", "Abort"]; // Full list - -const ROEs: string[] = [ "Hold", "Return", "Designated", "Free" ]; -const reactionsToThreat: string[] = [ "None", "Passive", "Evade" ]; +const ROEs: string[] = ["Hold", "Return", "Designated", "Free"]; +const reactionsToThreat: string[] = ["None", "Passive", "Evade"]; const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 }; const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 }; @@ -23,9 +20,10 @@ const altitudeIncrements: { [key: string]: number } = { Aircraft: 2500, Helicopt export class UnitControlPanel extends Panel { #altitudeSlider: Slider; #airspeedSlider: Slider; - #expectedAltitude:number = -1; - #expectedSpeed: number = -1; + #expectedAltitude: number = -1; + #expectedSpeed: number = -1; #optionButtons: { [key: string]: HTMLButtonElement[] } = {} + #advancedSettingsDialog: HTMLElement; constructor(ID: string) { super(ID); @@ -35,7 +33,7 @@ export class UnitControlPanel extends Panel { this.#expectedAltitude = value; getUnitsManager().selectedUnitsSetAltitude(value * 0.3048) }); - + this.#airspeedSlider = new Slider("airspeed-slider", 0, 100, "kts", (value: number) => { this.#expectedSpeed = value; getUnitsManager().selectedUnitsSetSpeed(value / 1.94384) @@ -61,6 +59,8 @@ export class UnitControlPanel extends Panel { this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]); this.getElement().querySelector("#reaction-to-threat-buttons-container")?.append(...this.#optionButtons["reactionToThreat"]); + this.#advancedSettingsDialog = document.querySelector("#advanced-settings-dialog"); + document.addEventListener("unitUpdated", (e: CustomEvent) => { if (e.detail.getSelected()) this.update() }); document.addEventListener("unitsSelection", (e: CustomEvent) => { this.show(); this.update() }); document.addEventListener("clearSelection", () => { this.hide() }); @@ -72,39 +72,32 @@ export class UnitControlPanel extends Panel { // Do this after panel is hidden (make sure there's a reset) protected onHide() { this.#expectedAltitude = -1; - this.#expectedSpeed = -1; + this.#expectedSpeed = -1; } // Update function will only be allowed to update the sliders once it's matched the expected value for the first time (due to lag of Ajax request) - #updateCanSetAltitudeSlider( altitude:number ) { - - if ( this.#expectedAltitude < 0 || altitude === this.#expectedAltitude ) { + #updateCanSetAltitudeSlider(altitude: number) { + if (this.#expectedAltitude < 0 || altitude === this.#expectedAltitude) { this.#expectedAltitude = -1; return true; } - return false; - } - - #updateCanSetSpeedSlider( altitude:number ) { - - if ( this.#expectedSpeed < 0 || altitude === this.#expectedSpeed ) { + #updateCanSetSpeedSlider(altitude: number) { + if (this.#expectedSpeed < 0 || altitude === this.#expectedSpeed) { this.#expectedSpeed = -1; return true; } - return false; - } - update() { var units = getUnitsManager().getSelectedUnits(); if (this.getElement() != null && units.length > 0) { this.#showFlightControlSliders(units); + this.#updateAdvancedSettingsDialog(units); this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => { let database: UnitDatabase | null; @@ -115,7 +108,7 @@ export class UnitControlPanel extends Panel { else database = null; // TODO add databases for other unit types - console.log( unit.getBaseData() ); + console.log(unit.getBaseData()); var button = document.createElement("button"); var callsign = unit.getBaseData().unitName || ""; @@ -140,16 +133,14 @@ export class UnitControlPanel extends Panel { } } - - #showFlightControlSliders(units: Unit[]) - { - if (getUnitsManager().getSelectedUnitsType() !== undefined) - this.#airspeedSlider.show() + #showFlightControlSliders(units: Unit[]) { + if (getUnitsManager().getSelectedUnitsType() !== undefined) + this.#airspeedSlider.show() else this.#airspeedSlider.hide(); - - if (getUnitsManager().getSelectedUnitsType() === "Aircraft" || getUnitsManager().getSelectedUnitsType() === "Helicopter") - this.#altitudeSlider.show() + + if (getUnitsManager().getSelectedUnitsType() === "Aircraft" || getUnitsManager().getSelectedUnitsType() === "Helicopter") + this.#altitudeSlider.show() else this.#altitudeSlider.hide(); @@ -173,8 +164,8 @@ export class UnitControlPanel extends Panel { targetSpeed *= 1.94384; - if ( this.#updateCanSetSpeedSlider( targetSpeed ) ) { - this.#airspeedSlider.setValue( targetSpeed ); + if (this.#updateCanSetSpeedSlider(targetSpeed)) { + this.#airspeedSlider.setValue(targetSpeed); } } @@ -183,8 +174,8 @@ export class UnitControlPanel extends Panel { if (targetAltitude != undefined) { targetAltitude /= 0.3048; - if ( this.#updateCanSetAltitudeSlider( targetAltitude ) ) { - this.#altitudeSlider.setValue( targetAltitude ); + if (this.#updateCanSetAltitudeSlider(targetAltitude)) { + this.#altitudeSlider.setValue(targetAltitude); } } } @@ -193,4 +184,33 @@ export class UnitControlPanel extends Panel { this.#altitudeSlider.setActive(false); } } + + #updateAdvancedSettingsDialog(units: Unit[]) + { + this.getElement().querySelector("#advanced-settings-div")?.classList.toggle("hide", units.length != 1); + + if (units.length == 1) + { + const unit = units[0]; + (this.#advancedSettingsDialog.querySelector("#unit-name")).innerText = unit.getBaseData().unitName; + + if (getUnitsManager().getSelectedUnits().length == 1){ + var roles = aircraftDatabase.getByName(unit.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles}) + if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker")){ + this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")?.setAttribute('checked', String(unit.getTaskData().isTanker)); + this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.remove("hide"); + } + else { + this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.add("hide"); + } + + if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS")){ + this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input")?.setAttribute('checked', String(unit.getTaskData().isAWACS)); + this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.remove("hide"); + } else { + this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.add("hide"); + } + } + } + } } \ No newline at end of file diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 41efeeb5..b58114b8 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -52,6 +52,12 @@ export class Unit extends Marker { targetAltitude: 0, isTanker: false, isAWACS: false, + radioOn: false, + TACANOn: false, + radioFrequency: 0, + TACANChannel: 0, + TACANXY: "X", + TACANCallsign: "", }, optionsData: { ROE: "", @@ -409,16 +415,6 @@ export class Unit extends Marker { if (this.getBaseData().category == "Aircraft") { options.push("Refuel"); // TODO Add some way of knowing which aircraft can AAR - - if (getUnitsManager().getSelectedUnits().length == 1){ - var roles = aircraftDatabase.getByName(this.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles}) - if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker")){ - options.push(this.getTaskData().isTanker? "Stop tanker": "Start tanker"); - } - if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS")){ - options.push(this.getTaskData().isAWACS? "Stop AWACS": "Start AWACS"); - } - } } } @@ -437,10 +433,6 @@ export class Unit extends Marker { getUnitsManager().selectedUnitsAttackUnit(this.ID); if (action === "Refuel") getUnitsManager().selectedUnitsRefuel(); - if (action === "Start tanker" || action === "Stop tanker") - getUnitsManager().selectedUnitsToggleTanker(); - if (action === "Start AWACS" || action === "Stop AWACS") - getUnitsManager().selectedUnitsToggleAWACS(); } #updateMarker() { diff --git a/client/views/dialogs.ejs b/client/views/dialogs.ejs index f285d098..45615680 100644 --- a/client/views/dialogs.ejs +++ b/client/views/dialogs.ejs @@ -19,18 +19,16 @@ -
- +
-

Olympus 1-1

+

Olympus 1-1

-
- + + +
+ +
+ +
+ +
-
+
-
40
@@ -111,10 +123,9 @@
-
-
+
-
120
@@ -163,19 +173,14 @@
-
-
- -
\ No newline at end of file diff --git a/client/views/unitcontrolpanel.ejs b/client/views/unitcontrolpanel.ejs index fd37c1ff..62f781dd 100644 --- a/client/views/unitcontrolpanel.ejs +++ b/client/views/unitcontrolpanel.ejs @@ -54,9 +54,10 @@
- +
+ +
+
diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 5dc3913f..2c3c15d4 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -105,6 +105,12 @@ public: void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));} void setIsTanker(bool newIsTanker) { isTanker = newIsTanker; addMeasure(L"isTanker", json::value(newIsTanker));} void setIsAWACS(bool newIsAWACS) { isAWACS = newIsAWACS; addMeasure(L"isAWACS", json::value(newIsAWACS));} + void setRadioOn(bool newRadioOn) { radioOn = newRadioOn; addMeasure(L"radioOn", json::value(newRadioOn)); } + void setTACANOn(bool newTACANOn) { TACANOn = newTACANOn; addMeasure(L"TACANOn", json::value(newTACANOn)); } + void setRadioFrequency(int newRadioFrequency) { radioFrequency = newRadioFrequency; addMeasure(L"radioFrequency", json::value(newRadioFrequency)); } + void setTACANChannel(int newTACANChannel) { TACANChannel = newTACANChannel; addMeasure(L"TACANChannel", json::value(newTACANChannel)); } + void setTACANXY(wstring newTACANXY) { TACANXY = newTACANXY; addMeasure(L"TACANXY", json::value(newTACANXY)); } + void setTACANCallsign(wstring newTACANCallsign) { TACANCallsign = newTACANCallsign; addMeasure(L"TACANCallsign", json::value(newTACANCallsign)); } wstring getCurrentTask() { return currentTask; } virtual double getTargetSpeed() { return targetSpeed; }; virtual double getTargetAltitude() { return targetAltitude; }; @@ -113,7 +119,13 @@ public: int getTargetID() { return targetID; } bool getIsTanker() { return isTanker; } bool getIsAWACS() { return isAWACS; } - + bool setRadioOn() { return radioOn; } + bool setTACANOn() { return TACANOn; } + int setRadioFrequency() { return radioFrequency; } + int setTACANChannel() { return TACANChannel; } + wstring setTACANXY() { return TACANXY; } + wstring setTACANCallsign() { return TACANCallsign; } + /********** Options data **********/ void setROE(wstring newROE); void setReactionToThreat(wstring newReactionToThreat); @@ -174,6 +186,12 @@ protected: int targetID = NULL; bool isTanker = false; bool isAWACS = false; + bool radioOn = false; + bool TACANOn = false; + int radioFrequency = 0; + int TACANChannel = 0; + wstring TACANXY = "X"; + wstring TACANCallsign = "TKR"; /********** Options data **********/ wstring ROE = L""; diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index db4e83f1..aa5a5475 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -256,19 +256,20 @@ void Scheduler::handleRequest(wstring key, json::value value) Unit* unit = unitsManager->getUnit(ID); unit->setState(State::REFUEL); } - else if (key.compare(L"setIsTanker") == 0) + else if (key.compare(L"setAdvancedOptions") == 0) { int ID = value[L"ID"].as_integer(); - bool state = value[L"state"].as_bool(); Unit* unit = unitsManager->getUnit(ID); - unit->setIsTanker(state); - } - else if (key.compare(L"setIsAWACS") == 0) - { - int ID = value[L"ID"].as_integer(); - bool state = value[L"state"].as_bool(); - Unit* unit = unitsManager->getUnit(ID); - unit->setIsAWACS(state); + if (unit != nullptr) + { + unit->setIsTanker(value[L"isTanker"].as_bool()); + unit->setIsAWACS(value[L"isAWACS"].as_bool()); + unit->setRadioOn(value[L"radioOn"].as_bool()); + unit->setTACANOn(value[L"TACANOn"].as_bool()); + unit->setTACANCallsign(value[L"TACANCallsign"].as_string()); + unit->setTACANChannel(value[L"TACANChannel"].as_number().to_int32()); + unit->setTACANXY(value[L"TACANXY"].as_string()); + } } else { From 7eee469bed41ab69032b5a4aa88fa0fe230ddbf8 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 12 Apr 2023 17:21:36 +0200 Subject: [PATCH 03/11] More work on advanced settings dialog --- client/public/stylesheets/layout.css | 135 ++++++++++---------- client/public/stylesheets/olympus.css | 1 + client/src/@types/unit.d.ts | 1 + client/src/controls/dropdown.ts | 82 +++++++------ client/src/panels/unitcontrolpanel.ts | 44 ++++++- client/src/server/server.ts | 22 ++-- client/src/units/unit.ts | 11 +- client/src/units/unitsmanager.ts | 18 --- client/views/dialogs.ejs | 170 +------------------------- client/views/unitcontrolpanel.ejs | 137 ++++++++++++++++++++- 10 files changed, 315 insertions(+), 306 deletions(-) diff --git a/client/public/stylesheets/layout.css b/client/public/stylesheets/layout.css index afa76477..0fc20782 100644 --- a/client/public/stylesheets/layout.css +++ b/client/public/stylesheets/layout.css @@ -1,4 +1,3 @@ - /* Page style */ #map-container { height: 100%; @@ -14,32 +13,30 @@ } #olympus-toolbar-summary { - background-image: url( "/images/icon-round.png" ); + background-image: url("/images/icon-round.png"); background-position: 25px 20px; background-repeat: no-repeat; - background-size:36px 36px; + background-size: 36px 36px; display: flex; flex-direction: column; text-indent: 44px; } - - dl.ol-data-grid { align-items: center; - display:flex; + display: flex; flex-direction: row; flex-wrap: wrap; - margin:0; + margin: 0; row-gap: 4px; } dl.ol-data-grid dt { - width:60%; + width: 60%; } dl.ol-data-grid dd { - width:40%; + width: 40%; } @@ -48,24 +45,23 @@ dl.ol-data-grid dt.icon { } dl.ol-data-grid dt.icon::before { - content: url( /images/icons/speed.svg ); - display:inline-block; - filter:invert(100%); + content: url(/images/icons/speed.svg ); + display: inline-block; + filter: invert(100%); width: 20px; - translate:-20px 2px; + translate: -20px 2px; } dl.ol-data-grid dt.icon-speed::before { - content: url( /images/icons/speed.svg ); + content: url(/images/icons/speed.svg ); } dl.ol-data-grid dt.icon-altitude::before { - content: url( /images/icons/altitude.svg ); + content: url(/images/icons/altitude.svg ); } - dl.ol-data-grid dd { display: flex; justify-content: flex-end; @@ -73,56 +69,50 @@ dl.ol-data-grid dd { } .br-info::after { - content: attr( data-bearing ) '\00B0 / ' attr( data-distance ) attr( data-distance-units ); + content: attr(data-bearing) '\00B0 / ' attr(data-distance) attr(data-distance-units); } .br-info[data-message]::after { - content: attr( data-message ); + content: attr(data-message); } .coordinates::after { - content: attr( data-dd ) "\00b0 " attr( data-mm ) "'" attr( data-ss ) "." attr( data-sss ) '"' attr( data-label ); + content: attr(data-dd) "\00b0 " attr(data-mm) "'" attr(data-ss) "." attr(data-sss) '"' attr(data-label); } - - - .ol-button-box { column-gap: 6px; - display:flex; + display: flex; flex-direction: row; flex-wrap: wrap; - margin:5px 0; + margin: 5px 0; row-gap: 5px; } .ol-button-box button { - background-repeat: no-repeat;; - border:1px solid var( --accent-light-blue ); - color: var( --accent-light-blue ); + background-repeat: no-repeat; + ; + border: 1px solid var(--accent-light-blue); + color: var(--accent-light-blue); } - - - - .ol-dialog { align-self: center; - background-color: var( --background-slate-blue ); - color:white; + background-color: var(--background-slate-blue); + color: white; justify-self: center; position: absolute; - z-index:1000; + z-index: 1000; } .ol-panel.ol-dialog { - padding:20px; + padding: 20px; } .ol-dialog-close { cursor: pointer; - font-size:16px; - font-weight: var( --font-weight-bolder ); + font-size: 16px; + font-weight: var(--font-weight-bolder); position: absolute; right: 25px; top: 25px; @@ -133,12 +123,12 @@ dl.ol-data-grid dd { } .ol-dialog-header { - border-bottom:1px solid var( --background-grey ); - padding-bottom:10px; + border-bottom: 1px solid var(--background-grey); + padding-bottom: 10px; } .ol-dialog-footer { - border-top: 1px solid var( --background-grey ); + border-top: 1px solid var(--background-grey); padding-top: 15px; display: flex; row-gap: 10px; @@ -157,46 +147,67 @@ dl.ol-data-grid dd { } .ol-checkbox input[type="checkbox"] { - appearance:none; + appearance: none; background-color: transparent; - border:none; - margin:0; + border: none; + margin: 0; } .ol-checkbox input[type="checkbox"]::before { - align-self:center; - background-image: url( "/images/icons/square-check-solid.svg" ); + align-self: center; + background-image: url("/images/icons/square-check-solid.svg"); background-repeat: no-repeat; content: ""; - filter: invert( 100% ); - display:flex; - height:16px; - margin-right:10px; - width:16px; + filter: invert(100%); + display: flex; + height: 16px; + margin-right: 10px; + width: 16px; } - .ol-checkbox input[type="checkbox"]:checked::before { - background-image: url( "/images/icons/square-regular.svg" ); + background-image: url("/images/icons/square-regular.svg"); } +.ol-text-input input { + height: 40px; + border-radius: 5px; + color: var(--background-offwhite); + background-color: var(--background-grey); + border: 1px solid var(--background-grey); + border-radius: var(--border-radius-sm); + text-align: center; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); +} + +input[type=number] { + -moz-appearance: textfield; + appearance: textfield; + margin: 0; +} + +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} [class|="ol-button"] { align-items: center; background-repeat: no-repeat; - display:flex; + display: flex; font-weight: normal; - padding:8px 10px; + padding: 8px 10px; white-space: nowrap; } [class|="ol-button"]::before { - margin-right:8px; + margin-right: 8px; } .ol-button-close { background: transparent; - border:1px solid white; + border: 1px solid white; } .ol-button-close::before { @@ -213,16 +224,16 @@ dl.ol-data-grid dd { } .ol-button-settings { - background-color: var( --background-slate-blue ); + background-color: var(--background-slate-blue); } .ol-button-settings::before { - background-image: url( "/images/icons/gears-solid.svg" ); - background-position:0 50%; - background-size:24px 24px; + background-image: url("/images/icons/gears-solid.svg"); + background-position: 0 50%; + background-size: 24px 24px; content: ""; - display:flex; - filter: invert( 100% ); + display: flex; + filter: invert(100%); height: 24px; width: 24px; } \ No newline at end of file diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 3e506469..a0cccbec 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -348,6 +348,7 @@ nav.ol-panel> :last-child { flex-direction: row; flex-wrap: nowrap; row-gap: 4px; + align-items: center; } .ol-panel .ol-group.wrap { diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index fca60a3b..9dc67839 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -46,6 +46,7 @@ interface TaskData { radioOn: boolean; TACANOn: boolean; radioFrequency: number; + radioCallsign: number; TACANChannel: number; TACANXY: string; TACANCallsign: string; diff --git a/client/src/controls/dropdown.ts b/client/src/controls/dropdown.ts index 2870db8c..7e59d00b 100644 --- a/client/src/controls/dropdown.ts +++ b/client/src/controls/dropdown.ts @@ -5,7 +5,8 @@ export class Dropdown { #callback: CallableFunction; #defaultValue: string; #optionsList: string[] = []; - + #index: number = 0; + constructor(ID: string, callback: CallableFunction, options: string[] | null = null) { this.#element = document.getElementById(ID); @@ -13,69 +14,39 @@ export class Dropdown { this.#value = this.#element.querySelector(".ol-select-value"); this.#defaultValue = this.#value.innerText; this.#callback = callback; - + if (options != null) { this.setOptions(options); } this.#value.addEventListener( "click", ev => { - this.#element.classList.toggle( "is-open" ); this.#clip(); - }); this.#element.addEventListener("mouseleave", ev => { this.#close(); }); - } - #clip() { - - const options = this.#options; - const bounds = options.getBoundingClientRect(); - - this.#element.dataset.position = ( bounds.bottom > window.innerHeight ) ? "top" : ""; - - } - - - #close() { - this.#element.classList.remove( "is-open" ); - this.#element.dataset.position = ""; - } - - - #open() { - this.#element.classList.add( "is-open" ); - } - - - #toggle() { - - if ( this.#element.classList.contains( "is-open" ) ) { - this.#close(); - } else { - this.#open(); - } - - } - - setOptions(optionsList: string[]) { this.#optionsList = optionsList; - this.#options.replaceChildren(...optionsList.map((option: string) => { + this.#options.replaceChildren(...optionsList.map((option: string, idx: number) => { var div = document.createElement("div"); var button = document.createElement("button"); button.textContent = option; div.appendChild(button); + + if (option === this.#defaultValue) + this.#index = idx; + button.addEventListener("click", (e: MouseEvent) => { e.stopPropagation(); this.#value.innerText = option; this.#close(); - this.#callback( option, e ); + this.#callback(option, e); + this.#index = idx; }); return div; })); @@ -87,6 +58,8 @@ export class Dropdown { { var option = this.#optionsList[idx]; this.#value.innerText = option; + this.#index = idx; + this.#close(); this.#callback(option); } } @@ -95,4 +68,35 @@ export class Dropdown { this.#options.replaceChildren(); this.#value.innerText = this.#defaultValue; } + + getValue() { + return this.#value.innerText; + } + + getIndex() { + return this.#index; + } + + #clip() { + const options = this.#options; + const bounds = options.getBoundingClientRect(); + this.#element.dataset.position = ( bounds.bottom > window.innerHeight ) ? "top" : ""; + } + + #close() { + this.#element.classList.remove( "is-open" ); + this.#element.dataset.position = ""; + } + + #open() { + this.#element.classList.add( "is-open" ); + } + + #toggle() { + if ( this.#element.classList.contains( "is-open" ) ) { + this.#close(); + } else { + this.#open(); + } + } } \ No newline at end of file diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index f3c28d63..8c71b386 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -1,4 +1,5 @@ import { getUnitsManager } from ".."; +import { Dropdown } from "../controls/dropdown"; import { Slider } from "../controls/slider"; import { dataPointMap } from "../other/utils"; import { aircraftDatabase } from "../units/aircraftdatabase"; @@ -20,6 +21,9 @@ const altitudeIncrements: { [key: string]: number } = { Aircraft: 2500, Helicopt export class UnitControlPanel extends Panel { #altitudeSlider: Slider; #airspeedSlider: Slider; + #TACANXYDropdown: Dropdown; + #radioDecimalsDropdown: Dropdown; + #radioCallsignDropdown: Dropdown; #expectedAltitude: number = -1; #expectedSpeed: number = -1; #optionButtons: { [key: string]: HTMLButtonElement[] } = {} @@ -39,6 +43,13 @@ export class UnitControlPanel extends Panel { getUnitsManager().selectedUnitsSetSpeed(value / 1.94384) }); + /* Advanced settings dropdowns */ + this.#TACANXYDropdown = new Dropdown("TACAN-XY", () => {}); + this.#TACANXYDropdown.setOptions(["X", "Y"]); + this.#radioDecimalsDropdown = new Dropdown("radio-decimals", () => {}); + this.#radioDecimalsDropdown.setOptions([".000", ".250", ".500", ".750"]); + this.#radioCallsignDropdown = new Dropdown("radio-callsign", () => {}); + /* Option buttons */ this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => { var button = document.createElement("button"); @@ -64,6 +75,11 @@ export class UnitControlPanel extends Panel { document.addEventListener("unitUpdated", (e: CustomEvent) => { if (e.detail.getSelected()) this.update() }); document.addEventListener("unitsSelection", (e: CustomEvent) => { this.show(); this.update() }); document.addEventListener("clearSelection", () => { this.hide() }); + document.addEventListener("applyAdvancedSettings", () => {this.#applyAdvancedSettings();}) + document.addEventListener("showAdvancedSettings", () => { + this.#updateAdvancedSettingsDialog(getUnitsManager().getSelectedUnits()); + this.#advancedSettingsDialog.classList.remove("hide"); + }) this.hide(); } @@ -97,7 +113,6 @@ export class UnitControlPanel extends Panel { var units = getUnitsManager().getSelectedUnits(); if (this.getElement() != null && units.length > 0) { this.#showFlightControlSliders(units); - this.#updateAdvancedSettingsDialog(units); this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => { let database: UnitDatabase | null; @@ -195,10 +210,15 @@ export class UnitControlPanel extends Panel { (this.#advancedSettingsDialog.querySelector("#unit-name")).innerText = unit.getBaseData().unitName; if (getUnitsManager().getSelectedUnits().length == 1){ + this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]); + this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign); + var roles = aircraftDatabase.getByName(unit.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles}) if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker")){ this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")?.setAttribute('checked', String(unit.getTaskData().isTanker)); this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.remove("hide"); + this.#radioCallsignDropdown.setOptions(["Texaco", "Arco", "Shell"]); + this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign); } else { this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.add("hide"); @@ -207,10 +227,32 @@ export class UnitControlPanel extends Panel { if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS")){ this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input")?.setAttribute('checked', String(unit.getTaskData().isAWACS)); this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.remove("hide"); + this.#radioCallsignDropdown.setOptions(["Overlord", "Magic", "Wizard", "Focus", "Darkstar"]); + this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign); } else { this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.add("hide"); } } } } + + #applyAdvancedSettings() + { + this.#advancedSettingsDialog.classList.add("hide"); + + const isTanker = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")?.checked; + const isAWACS= this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input")?.checked; + const TACANChannel = Number(this.#advancedSettingsDialog.querySelector("#TACAN-channel")?.querySelector("input")?.value); + const TACANXY = this.#TACANXYDropdown.getValue(); + const TACANCallsign = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input")?.value + const radioMHz = Number(this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input")?.value); + const radioDecimals = this.#radioDecimalsDropdown.getValue(); + const radioCallsign = this.#radioCallsignDropdown.getIndex(); + + var radioFrequency = (radioMHz * 1000 + Number(radioDecimals.substring(1))) * 1000; + + var units = getUnitsManager().getSelectedUnits(); + if (units.length > 0) + units[0].setAdvancedOptions(isTanker, isAWACS, TACANChannel, TACANXY, TACANCallsign, radioFrequency, radioCallsign); + } } \ No newline at end of file diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 4e4c4e02..b6ee81d2 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -183,14 +183,18 @@ export function refuel(ID: number) { POST(data, () => { }); } -export function setTanker(ID: number, state: boolean) { - var command = { "ID": ID, "state": state }; - var data = { "setIsTanker": command } - POST(data, () => { }); -} +export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACANChannel: number, TACANXY: string, TACANCallsign: string, radioFrequency: number, radioCallsign: number) +{ + var command = { "ID": ID, + "isTanker": isTanker, + "isAWACS": isAWACS, + "TACANChannel": TACANChannel, + "TACANXY": TACANXY, + "TACANCallsign": TACANCallsign, + "radioFrequency": radioFrequency, + "radioCallsign": radioCallsign + }; -export function setAWACS(ID: number, state: boolean) { - var command = { "ID": ID, "state": state }; - var data = { "setIsAWACS": command } + var data = { "setAdvancedOptions": command }; POST(data, () => { }); -} +} \ No newline at end of file diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index b58114b8..24f40250 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -1,7 +1,7 @@ import { Marker, LatLng, Polyline, Icon, DivIcon } from 'leaflet'; import { getMap, getUnitsManager } from '..'; import { rad2deg } from '../other/utils'; -import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setTanker, setAWACS } from '../server/server'; +import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions } from '../server/server'; import { aircraftDatabase } from './aircraftdatabase'; import { groundUnitsDatabase } from './groundunitsdatabase'; @@ -55,6 +55,7 @@ export class Unit extends Marker { radioOn: false, TACANOn: false, radioFrequency: 0, + radioCallsign: 0, TACANChannel: 0, TACANXY: "X", TACANCallsign: "", @@ -375,12 +376,8 @@ export class Unit extends Marker { refuel(this.ID); } - toggleTanker() { - setTanker(this.ID, !this.getTaskData().isTanker); - } - - toggleAWACS() { - setAWACS(this.ID, !this.getTaskData().isAWACS); + setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACANChannel: number, TACANXY: string, TACANcallsign: string, radioFrequency: number, radioCallsign: number) { + setAdvacedOptions(this.ID, isTanker, isAWACS, TACANChannel, TACANXY, TACANcallsign, radioFrequency, radioCallsign); } #onClick(e: any) { diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 754705b6..4fc63a34 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -334,24 +334,6 @@ export class UnitsManager { } } - selectedUnitsToggleTanker() - { - var selectedUnits = this.getSelectedUnits(); - for (let idx in selectedUnits) - { - selectedUnits[idx].toggleTanker(); - } - } - - selectedUnitsToggleAWACS() - { - var selectedUnits = this.getSelectedUnits(); - for (let idx in selectedUnits) - { - selectedUnits[idx].toggleAWACS(); - } - } - copyUnits() { this.#copiedUnits = this.getSelectedUnits(); diff --git a/client/views/dialogs.ejs b/client/views/dialogs.ejs index 45615680..99d09c92 100644 --- a/client/views/dialogs.ejs +++ b/client/views/dialogs.ejs @@ -5,7 +5,7 @@

DCS Olympus

Dynamic Unit Command

-
Version v0.1.0
+
Version v0.1.2
- - - -
-
- -
-

Olympus 1-1

-
- -
-
- - -
- -
- -
- -
- -
- -
- -
-
-
40
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
X
-
-
- -
-
- -
-
- -
-
- -
- -
- -
-
-
120
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
.750
-
-
- -
-
- -
-
- -
-
- -
-
-
-
-
-
- - - -
\ No newline at end of file diff --git a/client/views/unitcontrolpanel.ejs b/client/views/unitcontrolpanel.ejs index 62f781dd..42c1b9e0 100644 --- a/client/views/unitcontrolpanel.ejs +++ b/client/views/unitcontrolpanel.ejs @@ -55,10 +55,145 @@
- +
+ + +
+
+ +
+

Olympus 1-1

+
+ +
+
+ + +
+ +
+ +
+ +
+ +
+
+ +
+ +
+ +
+ +
+
X
+
+
+
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ +
+
.000
+
+
+
+
+ +
+ + +
+
+
+
+
+ + + +
+ +
+ +
+
+
+ + + +
\ No newline at end of file From 4a9019342698d1b55a9026d74a20161b46fff0fa Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 13 Apr 2023 08:09:15 +0200 Subject: [PATCH 04/11] Added functions to set TACAN and radio frequencies --- client/src/panels/unitcontrolpanel.ts | 3 +- client/src/server/server.ts | 5 +- client/src/units/unit.ts | 4 +- scripts/OlympusCommand.lua | 2 +- src/core/include/unit.h | 40 +++++++----- src/core/src/scheduler.cpp | 15 ++++- src/core/src/unit.cpp | 88 ++++++++++++++++++++++++++- src/dcstools/include/dcstools.h | 1 + src/dcstools/src/dcstools.cpp | 7 ++- 9 files changed, 138 insertions(+), 27 deletions(-) diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 8c71b386..d2f010e7 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -248,11 +248,12 @@ export class UnitControlPanel extends Panel { const radioMHz = Number(this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input")?.value); const radioDecimals = this.#radioDecimalsDropdown.getValue(); const radioCallsign = this.#radioCallsignDropdown.getIndex(); + const radioCallsignNumber = Number(this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input")?.value); var radioFrequency = (radioMHz * 1000 + Number(radioDecimals.substring(1))) * 1000; var units = getUnitsManager().getSelectedUnits(); if (units.length > 0) - units[0].setAdvancedOptions(isTanker, isAWACS, TACANChannel, TACANXY, TACANCallsign, radioFrequency, radioCallsign); + units[0].setAdvancedOptions(isTanker, isAWACS, TACANChannel, TACANXY, TACANCallsign, radioFrequency, radioCallsign, radioCallsignNumber); } } \ No newline at end of file diff --git a/client/src/server/server.ts b/client/src/server/server.ts index b6ee81d2..48e89148 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -183,7 +183,7 @@ export function refuel(ID: number) { POST(data, () => { }); } -export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACANChannel: number, TACANXY: string, TACANCallsign: string, radioFrequency: number, radioCallsign: number) +export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACANChannel: number, TACANXY: string, TACANCallsign: string, radioFrequency: number, radioCallsign: number, radioCallsignNumber: number) { var command = { "ID": ID, "isTanker": isTanker, @@ -192,7 +192,8 @@ export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolea "TACANXY": TACANXY, "TACANCallsign": TACANCallsign, "radioFrequency": radioFrequency, - "radioCallsign": radioCallsign + "radioCallsign": radioCallsign, + "radioCallsignNumber": radioCallsignNumber }; var data = { "setAdvancedOptions": command }; diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 24f40250..35836b16 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -376,8 +376,8 @@ export class Unit extends Marker { refuel(this.ID); } - setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACANChannel: number, TACANXY: string, TACANcallsign: string, radioFrequency: number, radioCallsign: number) { - setAdvacedOptions(this.ID, isTanker, isAWACS, TACANChannel, TACANXY, TACANcallsign, radioFrequency, radioCallsign); + setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACANChannel: number, TACANXY: string, TACANcallsign: string, radioFrequency: number, radioCallsign: number, radioCallsignNumber: number) { + setAdvacedOptions(this.ID, isTanker, isAWACS, TACANChannel, TACANXY, TACANcallsign, radioFrequency, radioCallsign, radioCallsignNumber); } #onClick(e: any) { diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 1563ebfb..0c087c0a 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -399,7 +399,7 @@ function Olympus.setCommand(ID, command) end function Olympus.setOption(ID, optionID, optionValue) - Olympus.debug("Olympus.setCommand " .. ID .. " " .. optionID .. " " .. optionValue, 2) + Olympus.debug("Olympus.setOption " .. ID .. " " .. optionID .. " " .. optionValue, 2) local unit = Olympus.getUnitByID(ID) if unit then unit:getGroup():getController():setOption(optionID, optionValue) diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 2c3c15d4..92b50083 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -105,12 +105,16 @@ public: void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));} void setIsTanker(bool newIsTanker) { isTanker = newIsTanker; addMeasure(L"isTanker", json::value(newIsTanker));} void setIsAWACS(bool newIsAWACS) { isAWACS = newIsAWACS; addMeasure(L"isAWACS", json::value(newIsAWACS));} - void setRadioOn(bool newRadioOn) { radioOn = newRadioOn; addMeasure(L"radioOn", json::value(newRadioOn)); } - void setTACANOn(bool newTACANOn) { TACANOn = newTACANOn; addMeasure(L"TACANOn", json::value(newTACANOn)); } - void setRadioFrequency(int newRadioFrequency) { radioFrequency = newRadioFrequency; addMeasure(L"radioFrequency", json::value(newRadioFrequency)); } - void setTACANChannel(int newTACANChannel) { TACANChannel = newTACANChannel; addMeasure(L"TACANChannel", json::value(newTACANChannel)); } - void setTACANXY(wstring newTACANXY) { TACANXY = newTACANXY; addMeasure(L"TACANXY", json::value(newTACANXY)); } - void setTACANCallsign(wstring newTACANCallsign) { TACANCallsign = newTACANCallsign; addMeasure(L"TACANCallsign", json::value(newTACANCallsign)); } + void setTACANOn(bool newTACANOn); + void setTACANChannel(int newTACANChannel); + void setTACANXY(wstring newTACANXY); + void setTACANCallsign(wstring newTACANCallsign); + void setTACAN(); + void setRadioOn(bool newRadioOn); + void setRadioFrequency(int newRadioFrequency); + void setRadioCallsign(int newRadioCallsign); + void setRadioCallsignNumber(int newRadioCallsignNumber); + void setRadio(); wstring getCurrentTask() { return currentTask; } virtual double getTargetSpeed() { return targetSpeed; }; virtual double getTargetAltitude() { return targetAltitude; }; @@ -119,12 +123,14 @@ public: int getTargetID() { return targetID; } bool getIsTanker() { return isTanker; } bool getIsAWACS() { return isAWACS; } - bool setRadioOn() { return radioOn; } - bool setTACANOn() { return TACANOn; } - int setRadioFrequency() { return radioFrequency; } - int setTACANChannel() { return TACANChannel; } - wstring setTACANXY() { return TACANXY; } - wstring setTACANCallsign() { return TACANCallsign; } + bool getTACANOn() { return TACANOn; } + int getTACANChannel() { return TACANChannel; } + wstring getTACANXY() { return TACANXY; } + wstring getTACANCallsign() { return TACANCallsign; } + bool getRadioOn() { return radioOn; } + int getRadioFrequency() { return radioFrequency; } + int getRadioCallsign() { return radioCallsign; } + int getRadioCallsignNumber() { return radioCallsignNumber; } /********** Options data **********/ void setROE(wstring newROE); @@ -186,12 +192,14 @@ protected: int targetID = NULL; bool isTanker = false; bool isAWACS = false; - bool radioOn = false; bool TACANOn = false; - int radioFrequency = 0; int TACANChannel = 0; - wstring TACANXY = "X"; - wstring TACANCallsign = "TKR"; + wstring TACANXY = L"X"; + wstring TACANCallsign = L"TKR"; + bool radioOn = false; + int radioFrequency = 0; + int radioCallsign = 0; + int radioCallsignNumber = 1; /********** Options data **********/ wstring ROE = L""; diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index aa5a5475..61dc7507 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -264,11 +264,20 @@ void Scheduler::handleRequest(wstring key, json::value value) { unit->setIsTanker(value[L"isTanker"].as_bool()); unit->setIsAWACS(value[L"isAWACS"].as_bool()); - unit->setRadioOn(value[L"radioOn"].as_bool()); - unit->setTACANOn(value[L"TACANOn"].as_bool()); - unit->setTACANCallsign(value[L"TACANCallsign"].as_string()); + + unit->setTACANOn(true); // TODO Remove unit->setTACANChannel(value[L"TACANChannel"].as_number().to_int32()); unit->setTACANXY(value[L"TACANXY"].as_string()); + unit->setTACANCallsign(value[L"TACANCallsign"].as_string()); + unit->setTACAN(); + + unit->setRadioOn(true); // TODO Remove + unit->setRadioFrequency(value[L"radioFrequency"].as_number().to_int32()); + unit->setRadioCallsign(value[L"radioCallsign"].as_number().to_int32()); + unit->setRadioCallsignNumber(value[L"radioCallsignNumber"].as_number().to_int32()); + unit->setRadio(); + + unit->resetActiveDestination(); } } else diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 5c14c6bd..7d77aa1e 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -346,4 +346,90 @@ void Unit::landAt(Coords loc) { clearActivePath(); pushActivePathBack(loc); setState(State::LAND); -} \ No newline at end of file +} + +void Unit::setTACANOn(bool newTACANOn) { + TACANOn = newTACANOn; + addMeasure(L"TACANOn", json::value(newTACANOn)); +} + +void Unit::setTACANChannel(int newTACANChannel) { + TACANChannel = newTACANChannel; + addMeasure(L"TACANChannel", json::value(newTACANChannel)); +} + +void Unit::setTACANXY(wstring newTACANXY) { + TACANXY = newTACANXY; + addMeasure(L"TACANXY", json::value(newTACANXY)); +} +void Unit::setTACANCallsign(wstring newTACANCallsign) { + TACANCallsign = newTACANCallsign; + addMeasure(L"TACANCallsign", json::value(newTACANCallsign)); +} + +void Unit::setRadioOn(bool newRadioOn) { + radioOn = newRadioOn; + addMeasure(L"radioOn", json::value(newRadioOn)); +} + +void Unit::setRadioFrequency(int newRadioFrequency) { + radioFrequency = newRadioFrequency; + addMeasure(L"radioFrequency", json::value(newRadioFrequency)); +} + +void Unit::setRadioCallsign(int newRadioCallsign) { + radioCallsign = newRadioCallsign; + addMeasure(L"radioCallsign", json::value(newRadioCallsign)); +} + +void Unit::setRadioCallsignNumber(int newRadioCallsignNumber) { + radioCallsignNumber = newRadioCallsignNumber; + addMeasure(L"radioCallsignNumber", json::value(newRadioCallsignNumber)); +} + +void Unit::setTACAN() +{ + std::wostringstream commandSS; + commandSS << "{" + << "id = 'ActivateBeacon'," + << "params = {" + << "type = 4," + << "system = 4," + << "name = Olympus_TACAN," + << "callsign = " << TACANCallsign << ", " + << "frequency = " << TACANChannelToFrequency(TACANChannel, TACANXY) << "," + << "}" + << "}"; + Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); + scheduler->appendCommand(command); +} + +void Unit::setRadio() +{ + { + std::wostringstream commandSS; + commandSS << "{" + << "id = 'SetFrequency'," + << "params = {" + << "modulation = 0," // TODO Allow selection + << "frequency = " << TACANChannelToFrequency(TACANChannel, TACANXY) << "," + << "}" + << "}"; + Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); + scheduler->appendCommand(command); + } + + { + std::wostringstream commandSS; + commandSS << "{" + << "id = 'SetCallsign'," + << "params = {" + << "callname = " << radioCallsign << "," + << "number = " << radioCallsignNumber << "," + << "}" + << "}"; + Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); + scheduler->appendCommand(command); + } +} + diff --git a/src/dcstools/include/dcstools.h b/src/dcstools/include/dcstools.h index b4866db4..db8ff519 100644 --- a/src/dcstools/include/dcstools.h +++ b/src/dcstools/include/dcstools.h @@ -8,4 +8,5 @@ void DllExport LogError(lua_State* L, string message); void DllExport Log(lua_State* L, string message, int level); int DllExport dostring_in(lua_State* L, string target, string command); map DllExport getAllUnits(lua_State* L); +int DllExport TACANChannelToFrequency(int channel, wstring XY); diff --git a/src/dcstools/src/dcstools.cpp b/src/dcstools/src/dcstools.cpp index 3995f9b5..1af8ebf2 100644 --- a/src/dcstools/src/dcstools.cpp +++ b/src/dcstools/src/dcstools.cpp @@ -94,7 +94,6 @@ exit: return units; } - int dostring_in(lua_State* L, string target, string command) { lua_getglobal(L, "net"); @@ -102,4 +101,10 @@ int dostring_in(lua_State* L, string target, string command) lua_pushstring(L, target.c_str()); lua_pushstring(L, command.c_str()); return lua_pcall(L, 2, 0, 0); +} + +int TACANChannelToFrequency(int channel, wstring XY) +{ + int basef = (XY == L"X" && channel > 63) || (XY == L"Y" && channel < 64) ? 1087000000 : 961000000; + return basef + 1000000 * channel; } \ No newline at end of file From b56f1ca5479606ce86eb9773226550ec2abf3f16 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Fri, 14 Apr 2023 07:59:24 +0200 Subject: [PATCH 05/11] Fixed checkbox and frequency --- client/public/stylesheets/layout.css | 4 ++-- client/src/panels/unitcontrolpanel.ts | 13 +++++++------ client/views/unitcontrolpanel.ejs | 2 +- src/core/src/unit.cpp | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/client/public/stylesheets/layout.css b/client/public/stylesheets/layout.css index 0fc20782..860d8409 100644 --- a/client/public/stylesheets/layout.css +++ b/client/public/stylesheets/layout.css @@ -155,7 +155,7 @@ dl.ol-data-grid dd { .ol-checkbox input[type="checkbox"]::before { align-self: center; - background-image: url("/images/icons/square-check-solid.svg"); + background-image: url("/images/icons/square-regular.svg"); background-repeat: no-repeat; content: ""; filter: invert(100%); @@ -166,7 +166,7 @@ dl.ol-data-grid dd { } .ol-checkbox input[type="checkbox"]:checked::before { - background-image: url("/images/icons/square-regular.svg"); + background-image: url("/images/icons/square-check-solid.svg"); } .ol-text-input input { diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index d2f010e7..f3ac1957 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -210,12 +210,14 @@ export class UnitControlPanel extends Panel { (this.#advancedSettingsDialog.querySelector("#unit-name")).innerText = unit.getBaseData().unitName; if (getUnitsManager().getSelectedUnits().length == 1){ + this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]); this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign); + this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")?.setAttribute('checked', String(unit.getTaskData().isTanker)); + this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input")?.setAttribute('checked', String(unit.getTaskData().isAWACS)); var roles = aircraftDatabase.getByName(unit.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles}) if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker")){ - this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")?.setAttribute('checked', String(unit.getTaskData().isTanker)); this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.remove("hide"); this.#radioCallsignDropdown.setOptions(["Texaco", "Arco", "Shell"]); this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign); @@ -225,7 +227,6 @@ export class UnitControlPanel extends Panel { } if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS")){ - this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input")?.setAttribute('checked', String(unit.getTaskData().isAWACS)); this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.remove("hide"); this.#radioCallsignDropdown.setOptions(["Overlord", "Magic", "Wizard", "Focus", "Darkstar"]); this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign); @@ -238,10 +239,8 @@ export class UnitControlPanel extends Panel { #applyAdvancedSettings() { - this.#advancedSettingsDialog.classList.add("hide"); - - const isTanker = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")?.checked; - const isAWACS= this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input")?.checked; + const isTanker = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")?.checked? true: false; + const isAWACS = false; //this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input")?.checked? true: false; const TACANChannel = Number(this.#advancedSettingsDialog.querySelector("#TACAN-channel")?.querySelector("input")?.value); const TACANXY = this.#TACANXYDropdown.getValue(); const TACANCallsign = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input")?.value @@ -255,5 +254,7 @@ export class UnitControlPanel extends Panel { var units = getUnitsManager().getSelectedUnits(); if (units.length > 0) units[0].setAdvancedOptions(isTanker, isAWACS, TACANChannel, TACANXY, TACANCallsign, radioFrequency, radioCallsign, radioCallsignNumber); + + this.#advancedSettingsDialog.classList.add("hide"); } } \ No newline at end of file diff --git a/client/views/unitcontrolpanel.ejs b/client/views/unitcontrolpanel.ejs index 42c1b9e0..55e92a2c 100644 --- a/client/views/unitcontrolpanel.ejs +++ b/client/views/unitcontrolpanel.ejs @@ -112,7 +112,7 @@
diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 7d77aa1e..815af80b 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -393,7 +393,7 @@ void Unit::setTACAN() commandSS << "{" << "id = 'ActivateBeacon'," << "params = {" - << "type = 4," + << "type = " << ((TACANXY.compare(L"X") == 0)? 4: 5) << "," << "system = 4," << "name = Olympus_TACAN," << "callsign = " << TACANCallsign << ", " @@ -412,7 +412,7 @@ void Unit::setRadio() << "id = 'SetFrequency'," << "params = {" << "modulation = 0," // TODO Allow selection - << "frequency = " << TACANChannelToFrequency(TACANChannel, TACANXY) << "," + << "frequency = " << radioFrequency << "," << "}" << "}"; Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); From 39698c66a33ec933f5097b0875d9392a4acd895f Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Fri, 14 Apr 2023 17:30:10 +0200 Subject: [PATCH 06/11] More work on follow and tanking --- client/demo.js | 10 +- client/public/images/icons/follow.svg | 57 ++++++++ client/public/images/icons/sword.svg | 61 ++++++++ client/public/stylesheets/contextmenus.css | 36 ++++- client/public/stylesheets/olympus.css | 4 + .../public/stylesheets/unitcontrolpanel.css | 18 ++- client/public/themes/olympus/olympus.css | 2 + client/src/@types/unit.d.ts | 8 +- client/src/controls/unitcontextmenu.ts | 6 +- client/src/panels/mouseinfopanel.ts | 2 +- client/src/panels/unitcontrolpanel.ts | 20 +-- client/src/server/server.ts | 6 + client/src/units/unit.ts | 37 +++-- client/src/units/unitsmanager.ts | 10 +- client/views/unitcontrolpanel.ejs | 135 ++++++++---------- 15 files changed, 308 insertions(+), 104 deletions(-) create mode 100644 client/public/images/icons/follow.svg create mode 100644 client/public/images/icons/sword.svg diff --git a/client/demo.js b/client/demo.js index be18be2f..b26fe651 100644 --- a/client/demo.js +++ b/client/demo.js @@ -50,7 +50,15 @@ const DEMO_UNIT_DATA = { activePath: undefined, targetSpeed: 400, targetAltitude: 3000, - isTanker: true + isTanker: false, + TACANOn: false, + TACANChannel: 32, + TACANXY: "Y", + TACANCallsign: "ASD", + radioFrequency: 123.750, + radioCallsign: 2, + radioCallsignNumber: 3, + radioAMFM: "FM" }, optionsData: { ROE: "Designated", diff --git a/client/public/images/icons/follow.svg b/client/public/images/icons/follow.svg new file mode 100644 index 00000000..2ec555ac --- /dev/null +++ b/client/public/images/icons/follow.svg @@ -0,0 +1,57 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/client/public/images/icons/sword.svg b/client/public/images/icons/sword.svg new file mode 100644 index 00000000..2d925c03 --- /dev/null +++ b/client/public/images/icons/sword.svg @@ -0,0 +1,61 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/client/public/stylesheets/contextmenus.css b/client/public/stylesheets/contextmenus.css index f6981488..78b9d3e7 100644 --- a/client/public/stylesheets/contextmenus.css +++ b/client/public/stylesheets/contextmenus.css @@ -207,10 +207,44 @@ display: flex; flex-direction: column; height: fit-content; + width: fit-content; position: absolute; row-gap: 5px; - width: 150px; z-index: 1000; + padding: 15px; +} + +#unit-contextmenu button { + border: 1px solid var(--background-offwhite); + border-radius: var(--border-radius-sm); + padding: 12px; + font-weight: normal; +} + +#unit-contextmenu div { + display: flex; + flex-direction: row; + align-content: center; +} + +#unit-contextmenu div:before { + display: inline-block; + filter: invert(100%); + height: 16px; + margin-right: 15px; + width: 16px; +} + +#refuel::before { + content: url( /images/icons/fuel.svg ); +} + +#attack::before { + content: url( /images/icons/sword.svg ); +} + +#follow::before { + content: url( /images/icons/follow.svg ); } /* Airbase context menu */ diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index a0cccbec..6e96c593 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -53,6 +53,10 @@ button { padding: 6px; } +button:hover { + background-color: var(--background-hover); +} + button[disabled="disabled"] { color: var(--highlight-color); cursor: not-allowed; diff --git a/client/public/stylesheets/unitcontrolpanel.css b/client/public/stylesheets/unitcontrolpanel.css index fcc78ca0..ba50dc42 100644 --- a/client/public/stylesheets/unitcontrolpanel.css +++ b/client/public/stylesheets/unitcontrolpanel.css @@ -93,8 +93,24 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { margin-bottom:8px; } - #unit-control-panel #threat, #unit-control-panel #roe { margin-top:12px; +} + +#advanced-settings-dialog { + width: 400px; +} + +#advanced-settings-dialog > .ol-dialog-content { + margin-top: 10px; + margin-bottom: 10px; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + row-gap: 10px; +} + +#advanced-settings-dialog > .ol-dialog-content > .ol-group { + justify-content: space-between; } \ No newline at end of file diff --git a/client/public/themes/olympus/olympus.css b/client/public/themes/olympus/olympus.css index 3836ea74..8d909b7f 100644 --- a/client/public/themes/olympus/olympus.css +++ b/client/public/themes/olympus/olympus.css @@ -31,6 +31,8 @@ --secondary-light-grey : #797e83; --secondary-yellow : #ffd46893; + --background-hover : #f2f2f333; + --nav-text : #ECECEC; diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index 9dc67839..9e621fde 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -37,19 +37,21 @@ interface FormationData { } interface TaskData { + currentState: string; currentTask: string; activePath: any; targetSpeed: number; targetAltitude: number; isTanker: boolean; isAWACS: boolean; - radioOn: boolean; TACANOn: boolean; - radioFrequency: number; - radioCallsign: number; TACANChannel: number; TACANXY: string; TACANCallsign: string; + radioFrequency: number; + radioCallsign: number; + radioCallsignNumber: number; + radioAMFM: string; } interface OptionsData { diff --git a/client/src/controls/unitcontextmenu.ts b/client/src/controls/unitcontextmenu.ts index 2579c3f0..51bde81b 100644 --- a/client/src/controls/unitcontextmenu.ts +++ b/client/src/controls/unitcontextmenu.ts @@ -5,12 +5,12 @@ export class UnitContextMenu extends ContextMenu { super(id); } - setOptions(options: string[], callback: CallableFunction) + setOptions(options: {[key: string]: string}, callback: CallableFunction) { - this.getContainer()?.replaceChildren(...options.map((option: string) => + this.getContainer()?.replaceChildren(...Object.keys(options).map((option: string, idx: number) => { var button = document.createElement("button"); - button.innerText = option; + button.innerHTML = options[option]; button.addEventListener("click", () => callback(option)); return (button); })); diff --git a/client/src/panels/mouseinfopanel.ts b/client/src/panels/mouseinfopanel.ts index 1e66e3aa..a21c67bd 100644 --- a/client/src/panels/mouseinfopanel.ts +++ b/client/src/panels/mouseinfopanel.ts @@ -18,7 +18,7 @@ export class MouseInfoPanel extends Panel { this.#measureMarker = new Marker([0, 0], {icon: this.#measureIcon, interactive: false}); this.#measureBox = document.createElement("div"); - this.#measureBox.classList.add("ol-measure-box"); + this.#measureBox.classList.add("ol-measure-box", "hide"); document.body.appendChild(this.#measureBox); getMap()?.on("click", (e: any) => this.#onMapClick(e)); diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index f3ac1957..e520eb45 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -111,6 +111,9 @@ export class UnitControlPanel extends Panel { update() { var units = getUnitsManager().getSelectedUnits(); + + this.getElement().querySelector("#advanced-settings-div")?.classList.toggle("hide", units.length != 1); + if (this.getElement() != null && units.length > 0) { this.#showFlightControlSliders(units); @@ -123,8 +126,6 @@ export class UnitControlPanel extends Panel { else database = null; // TODO add databases for other unit types - console.log(unit.getBaseData()); - var button = document.createElement("button"); var callsign = unit.getBaseData().unitName || ""; @@ -202,19 +203,20 @@ export class UnitControlPanel extends Panel { #updateAdvancedSettingsDialog(units: Unit[]) { - this.getElement().querySelector("#advanced-settings-div")?.classList.toggle("hide", units.length != 1); - if (units.length == 1) { const unit = units[0]; (this.#advancedSettingsDialog.querySelector("#unit-name")).innerText = unit.getBaseData().unitName; - if (getUnitsManager().getSelectedUnits().length == 1){ - + if (getUnitsManager().getSelectedUnits().length == 1) + { + var asd = this.#advancedSettingsDialog; this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]); this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign); - this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")?.setAttribute('checked', String(unit.getTaskData().isTanker)); - this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input")?.setAttribute('checked', String(unit.getTaskData().isAWACS)); + var tankerCheckbox = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input") + if (tankerCheckbox) tankerCheckbox.checked = unit.getTaskData().isTanker; + var AWACSCheckbox = this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input") + if (AWACSCheckbox) AWACSCheckbox.checked = unit.getTaskData().isAWACS; var roles = aircraftDatabase.getByName(unit.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles}) if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker")){ @@ -240,7 +242,7 @@ export class UnitControlPanel extends Panel { #applyAdvancedSettings() { const isTanker = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")?.checked? true: false; - const isAWACS = false; //this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input")?.checked? true: false; + const isAWACS = this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input")?.checked? true: false; const TACANChannel = Number(this.#advancedSettingsDialog.querySelector("#TACAN-channel")?.querySelector("input")?.value); const TACANXY = this.#TACANXYDropdown.getValue(); const TACANCallsign = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input")?.value diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 48e89148..1bbe50cf 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -117,6 +117,12 @@ export function attackUnit(ID: number, targetID: number) { POST(data, () => { }); } +export function followUnit(ID: number, targetID: number) { + var command = { "ID": ID, "targetID": targetID }; + var data = { "followUnit": command } + POST(data, () => { }); +} + export function cloneUnit(ID: number, latlng: L.LatLng) { var command = { "ID": ID, "location": latlng }; var data = { "cloneUnit": command } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 35836b16..e570c0ec 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -1,7 +1,7 @@ import { Marker, LatLng, Polyline, Icon, DivIcon } from 'leaflet'; import { getMap, getUnitsManager } from '..'; import { rad2deg } from '../other/utils'; -import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions } from '../server/server'; +import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit } from '../server/server'; import { aircraftDatabase } from './aircraftdatabase'; import { groundUnitsDatabase } from './groundunitsdatabase'; @@ -46,19 +46,21 @@ export class Unit extends Marker { wingmenIDs: [], }, taskData: { + currentState: "IDLE", currentTask: "", activePath: {}, targetSpeed: 0, targetAltitude: 0, isTanker: false, isAWACS: false, - radioOn: false, TACANOn: false, - radioFrequency: 0, - radioCallsign: 0, TACANChannel: 0, TACANXY: "X", TACANCallsign: "", + radioFrequency: 0, + radioCallsign: 0, + radioCallsignNumber: 0, + radioAMFM: "AM" }, optionsData: { ROE: "", @@ -336,6 +338,16 @@ export class Unit extends Marker { } } + followUnit(targetID: number) { + /* Call DCS attackUnit function */ + if (this.ID != targetID) { + followUnit(this.ID, targetID); + } + else { + // TODO: show a message + } + } + landAt(latlng: LatLng) { landAt(this.ID, latlng); } @@ -400,22 +412,23 @@ export class Unit extends Marker { } #onContextMenu(e: any) { - var options: string[] = []; + var options: {[key: string]: string} = {}; if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().includes(this))) { - options = [ - 'Attack' - ] + options = { + 'Attack': `
Attack
`, + 'Follow': `
Follow
` + } } - else if (getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) + else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) { if (this.getBaseData().category == "Aircraft") { - options.push("Refuel"); // TODO Add some way of knowing which aircraft can AAR + options["Refuel"] = `
Refuel
`; // TODO Add some way of knowing which aircraft can AAR } } - if (options.length > 0) + if (Object.keys(options).length > 0) { getMap().showUnitContextMenu(e); getMap().getUnitContextMenu().setOptions(options, (option: string) => { @@ -430,6 +443,8 @@ export class Unit extends Marker { getUnitsManager().selectedUnitsAttackUnit(this.ID); if (action === "Refuel") getUnitsManager().selectedUnitsRefuel(); + if (action === "Follow") + getUnitsManager().selectedUnitsFollowUnit(this.ID); } #updateMarker() { diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 4fc63a34..3e861717 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -334,6 +334,14 @@ export class UnitsManager { } } + selectedUnitsFollowUnit(ID: number) { + var selectedUnits = this.getSelectedUnits(); + for (let idx in selectedUnits) { + var commandedUnit = selectedUnits[idx]; + commandedUnit.followUnit(ID); + } + } + copyUnits() { this.#copiedUnits = this.getSelectedUnits(); @@ -370,7 +378,7 @@ export class UnitsManager { setTimeout(() => { document.dispatchEvent(new CustomEvent("unitsSelection", {detail: this.getSelectedUnits()})); this.#selectionEventDisabled = false; - }, 300); + }, 100); this.#selectionEventDisabled = true; } } diff --git a/client/views/unitcontrolpanel.ejs b/client/views/unitcontrolpanel.ejs index 55e92a2c..40629d4d 100644 --- a/client/views/unitcontrolpanel.ejs +++ b/client/views/unitcontrolpanel.ejs @@ -72,91 +72,81 @@
-
- + -
- -
+
+ +
-
- -
- +
+ +
+ +
+ +
-
- -
-
- +
X
- -
+
+ +
+
-
- -
-
@@ -167,12 +157,12 @@
+ + +
+
- -
@@ -186,9 +176,8 @@
-
- +
+
+
+ +
+

Custom formation

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+
+ +
+ +
+
+
+ + +
+

From cfd98e74ea20734fa80dc0d4f4f5111dc9373458 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Tue, 18 Apr 2023 11:01:31 +0200 Subject: [PATCH 09/11] Added states and more work on custom formations --- client/demo.js | 2 +- client/public/stylesheets/contextmenus.css | 7 + client/public/stylesheets/units.css | 40 ++-- .../themes/olympus/images/state_attack.svg | 53 +++++ .../themes/olympus/images/state_follow.svg | 222 ++++++++++++++++++ .../themes/olympus/images/state_idle.svg | 4 + .../themes/olympus/images/state_refuel.svg | 61 +++++ .../themes/olympus/images/state_rtb.svg | 4 + client/public/themes/olympus/olympus.css | 23 +- client/src/controls/unitcontextmenu.ts | 12 + client/src/server/server.ts | 4 +- client/src/units/unit.ts | 50 +++- client/src/units/unitsmanager.ts | 6 +- client/views/contextmenus.ejs | 38 --- client/views/dialogs.ejs | 175 ++++++++++++++ client/views/uikit.ejs | 36 +-- client/views/unitcontrolpanel.ejs | 124 ---------- src/core/src/airunit.cpp | 22 +- src/core/src/unit.cpp | 3 +- 19 files changed, 638 insertions(+), 248 deletions(-) create mode 100644 client/public/themes/olympus/images/state_attack.svg create mode 100644 client/public/themes/olympus/images/state_follow.svg create mode 100644 client/public/themes/olympus/images/state_idle.svg create mode 100644 client/public/themes/olympus/images/state_refuel.svg create mode 100644 client/public/themes/olympus/images/state_rtb.svg diff --git a/client/demo.js b/client/demo.js index 1a4bf887..02c7979b 100644 --- a/client/demo.js +++ b/client/demo.js @@ -635,7 +635,7 @@ class DemoDataGenerator { units(req, res){ var ret = this.demoUnits; for (let ID in this.demoUnits["units"]){ - this.demoUnits["units"][ID].flightData.latitude += 0.00; + this.demoUnits["units"][ID].flightData.latitude += 0.00001; } ret.time = Date.now(); res.send(JSON.stringify(ret)); diff --git a/client/public/stylesheets/contextmenus.css b/client/public/stylesheets/contextmenus.css index 7443e972..606f197f 100644 --- a/client/public/stylesheets/contextmenus.css +++ b/client/public/stylesheets/contextmenus.css @@ -304,6 +304,13 @@ } .formation-position-clock { + position: relative; + height: 100px; + width: 100px; + margin: 15px; +} + +.formation-position-clock > .clock-hand { transform: translate(-50%, -50%); display: flex; position: absolute; diff --git a/client/public/stylesheets/units.css b/client/public/stylesheets/units.css index 6ec7a3cf..f64543a6 100644 --- a/client/public/stylesheets/units.css +++ b/client/public/stylesheets/units.css @@ -506,46 +506,38 @@ } -[data-object|="unit"] .unit-status { +[data-object|="unit"] .unit-state { background-repeat: no-repeat; position:absolute; - height:var( --unit-aircraft-status-rtb-height ); - width:var( --unit-aircraft-status-rtb-width ); + height:var( --unit-aircraft-state-height ); + width:var( --unit-aircraft-state-width ); z-index: 10; } -[data-object|="unit"][data-status="rtb"] .unit-status { - background-image: var( --unit-aircraft-status-rtb-neutral-url ); - height:var( --unit-aircraft-status-rtb-height ); - width:var( --unit-aircraft-status-rtb-width ); +[data-object|="unit"][data-state="rtb"] .unit-state { + background-image: var( --unit-aircraft-state-rtb ); } -[data-object|="unit"][data-status="rtb"][data-coalition="blue"] .unit-status { - background-image: var( --unit-aircraft-status-rtb-blue-url ); +[data-object|="unit"][data-state="land"] .unit-state { + background-image: var( --unit-aircraft-state-rtb ); } -[data-object|="unit"][data-status="rtb"][data-coalition="red"] .unit-status { - background-image: var( --unit-aircraft-status-rtb-red-url ); +[data-object|="unit"][data-state="idle"] .unit-state { + background-image: var( --unit-aircraft-state-idle ); } - - - -[data-object|="unit"][data-status="hold"] .unit-status { - background-image: var( --unit-aircraft-status-hold-neutral-url ); - height:var( --unit-aircraft-status-hold-height ); - width:var( --unit-aircraft-status-hold-width ); +[data-object|="unit"][data-state="attack"] .unit-state { + background-image: var( --unit-aircraft-state-attack ); } -[data-object|="unit"][data-status="hold"][data-coalition="blue"] .unit-status { - background-image: var( --unit-aircraft-status-hold-blue-url ); +[data-object|="unit"][data-state="follow"] .unit-state { + background-image: var( --unit-aircraft-state-follow ); } -[data-object|="unit"][data-status="hold"][data-coalition="red"] .unit-status { - background-image: var( --unit-aircraft-status-hold-red-url ); +[data-object|="unit"][data-state="refuel"] .unit-state { + background-image: var( --unit-aircraft-state-refuel ); } - /*** DEAD ***/ [data-object|="unit-aircraft"][ data-is-dead ] { cursor: default; @@ -571,7 +563,7 @@ [data-object|="unit-aircraft"][ data-is-dead ] .unit-vvi, [data-object|="unit-aircraft"][ data-is-dead ] .unit-hotgroup, [data-object|="unit-aircraft"][ data-is-dead ] .unit-hotgroup-id, -[data-object|="unit-aircraft"][ data-is-dead ] .unit-status, +[data-object|="unit-aircraft"][ data-is-dead ] .unit-state, [data-object|="unit-aircraft"][ data-is-dead ] .unit-fuel, [data-object|="unit-aircraft"][ data-is-dead ] .unit-ammo, [data-object|="unit-aircraft"][ data-is-dead ]:hover .unit-fuel, diff --git a/client/public/themes/olympus/images/state_attack.svg b/client/public/themes/olympus/images/state_attack.svg new file mode 100644 index 00000000..84cd084f --- /dev/null +++ b/client/public/themes/olympus/images/state_attack.svg @@ -0,0 +1,53 @@ + + + + + + + + + diff --git a/client/public/themes/olympus/images/state_follow.svg b/client/public/themes/olympus/images/state_follow.svg new file mode 100644 index 00000000..92dadcac --- /dev/null +++ b/client/public/themes/olympus/images/state_follow.svg @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/state_idle.svg b/client/public/themes/olympus/images/state_idle.svg new file mode 100644 index 00000000..757ee216 --- /dev/null +++ b/client/public/themes/olympus/images/state_idle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/public/themes/olympus/images/state_refuel.svg b/client/public/themes/olympus/images/state_refuel.svg new file mode 100644 index 00000000..422193f1 --- /dev/null +++ b/client/public/themes/olympus/images/state_refuel.svg @@ -0,0 +1,61 @@ + + + + + + + + + + diff --git a/client/public/themes/olympus/images/state_rtb.svg b/client/public/themes/olympus/images/state_rtb.svg new file mode 100644 index 00000000..f40e2627 --- /dev/null +++ b/client/public/themes/olympus/images/state_rtb.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/public/themes/olympus/olympus.css b/client/public/themes/olympus/olympus.css index 8d909b7f..497ef3b7 100644 --- a/client/public/themes/olympus/olympus.css +++ b/client/public/themes/olympus/olympus.css @@ -122,24 +122,17 @@ --unit-aircraft-marker-red-dead-url: url( "/themes/olympus/images/icon_death_red.svg" ); - /*** Air units' statuses ***/ + /*** Air units' states ***/ - --unit-aircraft-status-rtb-height: 50px; - --unit-aircraft-status-rtb-width: 50px; + --unit-aircraft-state-height: 50px; + --unit-aircraft-state-width: 50px; - --unit-aircraft-status-rtb-blue-url: url( "data:image/svg+xml,%3Csvg width='19' height='15' viewBox='0 0 19 15' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9.8125 4.99219L5.5 8.55469V12.375C5.5 12.5859 5.66406 12.75 5.875 12.75H8.5C8.6875 12.75 8.85156 12.5859 8.85156 12.375V10.125C8.85156 9.9375 9.03906 9.75 9.22656 9.75H10.7266C10.9375 9.75 11.1016 9.9375 11.1016 10.125V12.375C11.1016 12.5859 11.2891 12.75 11.4766 12.75H14.125C14.3125 12.75 14.5 12.5859 14.5 12.375V8.53125L10.1641 4.99219C10.1172 4.94531 10.0469 4.92188 10 4.92188C9.92969 4.92188 9.85938 4.94531 9.8125 4.99219ZM16.6328 7.40625L14.6875 5.78906V2.55469C14.6875 2.39062 14.5469 2.27344 14.4062 2.27344H13.0938C12.9297 2.27344 12.8125 2.39062 12.8125 2.55469V4.24219L10.7031 2.50781C10.5156 2.36719 10.2578 2.27344 10 2.27344C9.71875 2.27344 9.46094 2.36719 9.27344 2.50781L3.34375 7.40625C3.27344 7.45312 3.22656 7.54688 3.22656 7.61719C3.22656 7.6875 3.27344 7.75781 3.29688 7.80469L3.90625 8.53125C3.95312 8.60156 4.02344 8.625 4.11719 8.625C4.1875 8.625 4.25781 8.60156 4.30469 8.55469L9.8125 4.03125C9.85938 3.98438 9.92969 3.96094 10 3.96094C10.0469 3.96094 10.1172 3.98438 10.1641 4.03125L15.6719 8.55469C15.7188 8.60156 15.7891 8.625 15.8594 8.625C15.9531 8.625 16.0234 8.60156 16.0703 8.53125L16.6797 7.80469C16.7266 7.75781 16.75 7.6875 16.75 7.61719C16.75 7.54688 16.7031 7.45312 16.6328 7.40625Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M14.5 8.53125V12.375C14.5 12.5859 14.3125 12.75 14.125 12.75H11.4766C11.2891 12.75 11.1016 12.5859 11.1016 12.375V10.125C11.1016 9.9375 10.9375 9.75 10.7266 9.75H9.22656C9.03906 9.75 8.85156 9.9375 8.85156 10.125V12.375C8.85156 12.5859 8.6875 12.75 8.5 12.75H5.875C5.66406 12.75 5.5 12.5859 5.5 12.375V8.55469L9.8125 4.99219C9.85938 4.94531 9.92969 4.92188 10 4.92188C10.0469 4.92188 10.1172 4.94531 10.1641 4.99219L14.5 8.53125ZM15.6719 8.55469C15.673 8.55582 15.6741 8.55693 15.6753 8.55803C15.7136 8.5945 15.7664 8.61608 15.8218 8.62275C15.8342 8.62425 15.8468 8.625 15.8594 8.625C15.9531 8.625 16.0234 8.60156 16.0703 8.53125L16.6797 7.80469C16.7266 7.75781 16.75 7.6875 16.75 7.61719C16.75 7.58321 16.7391 7.54375 16.7198 7.50676C16.6992 7.46721 16.6691 7.43047 16.6328 7.40625L14.6875 5.78906V2.55469C14.6875 2.39062 14.5469 2.27344 14.4062 2.27344H13.0938C12.9297 2.27344 12.8125 2.39062 12.8125 2.55469V4.24219L10.7031 2.50781C10.6607 2.47601 10.6147 2.44661 10.5659 2.42014C10.399 2.32958 10.1995 2.27344 10 2.27344C9.71875 2.27344 9.46094 2.36719 9.27344 2.50781L3.34375 7.40625C3.31845 7.42312 3.29618 7.44605 3.27804 7.47178C3.27013 7.483 3.26301 7.49475 3.25676 7.50676C3.25438 7.51134 3.25212 7.51595 3.25 7.52059C3.23497 7.55343 3.22656 7.58741 3.22656 7.61719C3.22656 7.64997 3.23675 7.68274 3.25 7.71315C3.25988 7.73582 3.27147 7.75718 3.2818 7.77623C3.28734 7.78642 3.29251 7.79596 3.29688 7.80469L3.90625 8.53125C3.9119 8.53973 3.9179 8.54753 3.92423 8.55469C3.97043 8.60688 4.03475 8.625 4.11719 8.625C4.12975 8.625 4.14232 8.62425 4.15475 8.62275C4.20858 8.61627 4.2599 8.59576 4.29785 8.56121C4.30018 8.55909 4.30246 8.55692 4.30469 8.55469L9.8125 4.03125C9.85938 3.98438 9.92969 3.96094 10 3.96094C10.0469 3.96094 10.1172 3.98438 10.1641 4.03125L15.6719 8.55469ZM3.25 10.7274C2.87411 10.5959 2.46972 10.355 2.13819 9.92451L1.40281 9.04772L1.29673 8.83555C1.29616 8.83449 1.29556 8.83338 1.29494 8.83223C1.28063 8.80565 1.24385 8.73712 1.20461 8.65078C1.13819 8.50467 0.976562 8.12434 0.976562 7.61719C0.976562 7.12558 1.1254 6.71911 1.27773 6.4362C1.41591 6.17958 1.63146 5.88605 1.95204 5.63749L7.88118 0.739506L7.92344 0.707813C8.5004 0.275088 9.23192 0.0234375 10 0.0234375C10.6011 0.0234375 11.1776 0.186404 11.6675 0.458244C12.0727 0.183444 12.5631 0.0234375 13.0938 0.0234375H14.4062C15.6666 0.0234375 16.9375 1.02855 16.9375 2.55469V4.73359L18.0288 5.64079C18.347 5.88864 18.5613 6.18072 18.6988 6.4362C18.8512 6.71911 19 7.12558 19 7.61719C19 8.10158 18.8551 8.74439 18.3724 9.28773L17.8384 9.92451C17.5137 10.3461 17.1192 10.5858 16.75 10.719V12.375C16.75 13.9179 15.4639 15 14.125 15H11.4766C10.9469 15 10.4254 14.8306 9.98865 14.5304C9.57667 14.823 9.06944 15 8.5 15H5.875C4.42142 15 3.25 13.8286 3.25 12.375V10.7274Z' fill='%23082E44'/%3E%3C/svg%3E" ); - --unit-aircraft-status-rtb-neutral-url: url( "data:image/svg+xml,%3Csvg width='19' height='15' viewBox='0 0 19 15' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8.85938 4.99219L4.54688 8.55469V12.375C4.54688 12.5859 4.71094 12.75 4.92188 12.75H7.54688C7.73438 12.75 7.89844 12.5859 7.89844 12.375V10.125C7.89844 9.9375 8.08594 9.75 8.27344 9.75H9.77344C9.98438 9.75 10.1484 9.9375 10.1484 10.125V12.375C10.1484 12.5859 10.3359 12.75 10.5234 12.75H13.1719C13.3594 12.75 13.5469 12.5859 13.5469 12.375V8.53125L9.21094 4.99219C9.16406 4.94531 9.09375 4.92188 9.04688 4.92188C8.97656 4.92188 8.90625 4.94531 8.85938 4.99219ZM15.6797 7.40625L13.7344 5.78906V2.55469C13.7344 2.39062 13.5938 2.27344 13.4531 2.27344H12.1406C11.9766 2.27344 11.8594 2.39062 11.8594 2.55469V4.24219L9.75 2.50781C9.5625 2.36719 9.30469 2.27344 9.04688 2.27344C8.76562 2.27344 8.50781 2.36719 8.32031 2.50781L2.39062 7.40625C2.32031 7.45312 2.27344 7.54688 2.27344 7.61719C2.27344 7.6875 2.32031 7.75781 2.34375 7.80469L2.95312 8.53125C3 8.60156 3.07031 8.625 3.16406 8.625C3.23438 8.625 3.30469 8.60156 3.35156 8.55469L8.85938 4.03125C8.90625 3.98438 8.97656 3.96094 9.04688 3.96094C9.09375 3.96094 9.16406 3.98438 9.21094 4.03125L14.7188 8.55469C14.7656 8.60156 14.8359 8.625 14.9062 8.625C15 8.625 15.0703 8.60156 15.1172 8.53125L15.7266 7.80469C15.7734 7.75781 15.7969 7.6875 15.7969 7.61719C15.7969 7.54688 15.75 7.45312 15.6797 7.40625Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.5469 8.53125V12.375C13.5469 12.5859 13.3594 12.75 13.1719 12.75H10.5234C10.3359 12.75 10.1484 12.5859 10.1484 12.375V10.125C10.1484 9.9375 9.98438 9.75 9.77344 9.75H8.27344C8.08594 9.75 7.89844 9.9375 7.89844 10.125V12.375C7.89844 12.5859 7.73438 12.75 7.54688 12.75H4.92188C4.71094 12.75 4.54688 12.5859 4.54688 12.375V8.55469L8.85938 4.99219C8.90625 4.94531 8.97656 4.92188 9.04688 4.92188C9.09375 4.92188 9.16406 4.94531 9.21094 4.99219L13.5469 8.53125ZM14.7188 8.55469C14.7199 8.55582 14.721 8.55693 14.7222 8.55803C14.7604 8.5945 14.8132 8.61608 14.8687 8.62275C14.8811 8.62425 14.8937 8.625 14.9062 8.625C15 8.625 15.0703 8.60156 15.1172 8.53125L15.7266 7.80469C15.7734 7.75781 15.7969 7.6875 15.7969 7.61719C15.7969 7.58321 15.7859 7.54375 15.7667 7.50676C15.7461 7.46721 15.716 7.43047 15.6797 7.40625L13.7344 5.78906V2.55469C13.7344 2.39062 13.5938 2.27344 13.4531 2.27344H12.1406C11.9766 2.27344 11.8594 2.39062 11.8594 2.55469V4.24219L9.75 2.50781C9.7076 2.47601 9.6616 2.44661 9.61282 2.42014C9.4459 2.32958 9.24639 2.27344 9.04688 2.27344C8.76562 2.27344 8.50781 2.36719 8.32031 2.50781L2.39062 7.40625C2.36532 7.42312 2.34306 7.44605 2.32492 7.47178C2.31701 7.483 2.30989 7.49475 2.30364 7.50676C2.30125 7.51134 2.299 7.51595 2.29688 7.52059C2.28184 7.55343 2.27344 7.58741 2.27344 7.61719C2.27344 7.64997 2.28362 7.68274 2.29688 7.71315C2.30676 7.73582 2.31834 7.75718 2.32868 7.77623C2.33421 7.78642 2.33939 7.79596 2.34375 7.80469L2.95312 8.53125C2.95878 8.53973 2.96477 8.54753 2.97111 8.55469C3.0173 8.60688 3.08162 8.625 3.16406 8.625C3.17663 8.625 3.1892 8.62425 3.20163 8.62275C3.25545 8.61627 3.30677 8.59576 3.34472 8.56121C3.34705 8.55909 3.34933 8.55692 3.35156 8.55469L8.85938 4.03125C8.90625 3.98438 8.97656 3.96094 9.04688 3.96094C9.09375 3.96094 9.16406 3.98438 9.21094 4.03125L14.7188 8.55469ZM2.29688 10.7274C1.92098 10.5959 1.51659 10.355 1.18506 9.92451L0.449688 9.04772L0.343607 8.83555C0.343034 8.83449 0.342438 8.83338 0.341816 8.83223C0.327503 8.80565 0.290725 8.73712 0.25148 8.65078C0.185064 8.50467 0.0234375 8.12434 0.0234375 7.61719C0.0234375 7.12558 0.172273 6.71911 0.324604 6.4362C0.462785 6.17958 0.67834 5.88605 0.998912 5.63749L6.92805 0.739506L6.97031 0.707813C7.54728 0.275088 8.27879 0.0234375 9.04688 0.0234375C9.64799 0.0234375 10.2245 0.186404 10.7144 0.458244C11.1195 0.183444 11.61 0.0234375 12.1406 0.0234375H13.4531C14.7135 0.0234375 15.9844 1.02855 15.9844 2.55469V4.73359L17.0756 5.64079C17.3939 5.88864 17.6081 6.18072 17.7457 6.4362C17.898 6.71911 18.0469 7.12558 18.0469 7.61719C18.0469 8.10158 17.902 8.74439 17.4193 9.28773L16.8852 9.92451C16.5606 10.3461 16.1661 10.5858 15.7969 10.719V12.375C15.7969 13.9179 14.5108 15 13.1719 15H10.5234C9.99374 15 9.47232 14.8306 9.03552 14.5304C8.62355 14.823 8.11631 15 7.54688 15H4.92188C3.4683 15 2.29688 13.8286 2.29688 12.375V10.7274Z' fill='%232F2F2F'/%3E%3C/svg%3E" ); - --unit-aircraft-status-rtb-red-url: url( "data:image/svg+xml,%3Csvg width='19' height='15' viewBox='0 0 19 15' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8.83594 4.99219L4.52344 8.55469V12.375C4.52344 12.5859 4.6875 12.75 4.89844 12.75H7.52344C7.71094 12.75 7.875 12.5859 7.875 12.375V10.125C7.875 9.9375 8.0625 9.75 8.25 9.75H9.75C9.96094 9.75 10.125 9.9375 10.125 10.125V12.375C10.125 12.5859 10.3125 12.75 10.5 12.75H13.1484C13.3359 12.75 13.5234 12.5859 13.5234 12.375V8.53125L9.1875 4.99219C9.14062 4.94531 9.07031 4.92188 9.02344 4.92188C8.95312 4.92188 8.88281 4.94531 8.83594 4.99219ZM15.6562 7.40625L13.7109 5.78906V2.55469C13.7109 2.39062 13.5703 2.27344 13.4297 2.27344H12.1172C11.9531 2.27344 11.8359 2.39062 11.8359 2.55469V4.24219L9.72656 2.50781C9.53906 2.36719 9.28125 2.27344 9.02344 2.27344C8.74219 2.27344 8.48438 2.36719 8.29688 2.50781L2.36719 7.40625C2.29688 7.45312 2.25 7.54688 2.25 7.61719C2.25 7.6875 2.29688 7.75781 2.32031 7.80469L2.92969 8.53125C2.97656 8.60156 3.04688 8.625 3.14062 8.625C3.21094 8.625 3.28125 8.60156 3.32812 8.55469L8.83594 4.03125C8.88281 3.98438 8.95312 3.96094 9.02344 3.96094C9.07031 3.96094 9.14062 3.98438 9.1875 4.03125L14.6953 8.55469C14.7422 8.60156 14.8125 8.625 14.8828 8.625C14.9766 8.625 15.0469 8.60156 15.0938 8.53125L15.7031 7.80469C15.75 7.75781 15.7734 7.6875 15.7734 7.61719C15.7734 7.54688 15.7266 7.45312 15.6562 7.40625Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.5234 8.53125V12.375C13.5234 12.5859 13.3359 12.75 13.1484 12.75H10.5C10.3125 12.75 10.125 12.5859 10.125 12.375V10.125C10.125 9.9375 9.96094 9.75 9.75 9.75H8.25C8.0625 9.75 7.875 9.9375 7.875 10.125V12.375C7.875 12.5859 7.71094 12.75 7.52344 12.75H4.89844C4.6875 12.75 4.52344 12.5859 4.52344 12.375V8.55469L8.83594 4.99219C8.88281 4.94531 8.95312 4.92188 9.02344 4.92188C9.07031 4.92188 9.14062 4.94531 9.1875 4.99219L13.5234 8.53125ZM14.6953 8.55469C14.6964 8.55582 14.6976 8.55693 14.6987 8.55803C14.737 8.5945 14.7898 8.61608 14.8452 8.62275C14.8577 8.62425 14.8702 8.625 14.8828 8.625C14.9766 8.625 15.0469 8.60156 15.0938 8.53125L15.7031 7.80469C15.75 7.75781 15.7734 7.6875 15.7734 7.61719C15.7734 7.58321 15.7625 7.54375 15.7432 7.50676C15.7227 7.46721 15.6926 7.43047 15.6562 7.40625L13.7109 5.78906V2.55469C13.7109 2.39062 13.5703 2.27344 13.4297 2.27344H12.1172C11.9531 2.27344 11.8359 2.39062 11.8359 2.55469V4.24219L9.72656 2.50781C9.68416 2.47601 9.63816 2.44661 9.58938 2.42014C9.42246 2.32958 9.22295 2.27344 9.02344 2.27344C8.74219 2.27344 8.48438 2.36719 8.29688 2.50781L2.36719 7.40625C2.34189 7.42312 2.31962 7.44605 2.30148 7.47178C2.29357 7.483 2.28645 7.49475 2.2802 7.50676C2.27782 7.51134 2.27556 7.51595 2.27344 7.52059C2.25841 7.55343 2.25 7.58741 2.25 7.61719C2.25 7.64997 2.26019 7.68274 2.27344 7.71315C2.28332 7.73582 2.29491 7.75718 2.30524 7.77623C2.31078 7.78642 2.31595 7.79596 2.32031 7.80469L2.92969 8.53125C2.93534 8.53973 2.94134 8.54753 2.94767 8.55469C2.99386 8.60688 3.05818 8.625 3.14062 8.625C3.15319 8.625 3.16576 8.62425 3.17819 8.62275C3.23202 8.61627 3.28333 8.59576 3.32129 8.56121C3.32362 8.55909 3.3259 8.55692 3.32812 8.55469L8.83594 4.03125C8.88281 3.98438 8.95312 3.96094 9.02344 3.96094C9.07031 3.96094 9.14062 3.98438 9.1875 4.03125L14.6953 8.55469ZM2.27344 10.7274C1.89755 10.5959 1.49315 10.355 1.16162 9.92451L0.426251 9.04772L0.32017 8.83555C0.319597 8.83449 0.319 8.83338 0.318378 8.83223C0.304066 8.80565 0.267288 8.73712 0.228043 8.65078C0.161627 8.50467 0 8.12434 0 7.61719C0 7.12558 0.148836 6.71911 0.301167 6.4362C0.439348 6.17958 0.654902 5.88605 0.975475 5.63749L6.90462 0.739506L6.94688 0.707813C7.52384 0.275088 8.25535 0.0234375 9.02344 0.0234375C9.62455 0.0234375 10.201 0.186404 10.691 0.458244C11.0961 0.183444 11.5866 0.0234375 12.1172 0.0234375H13.4297C14.6901 0.0234375 15.9609 1.02855 15.9609 2.55469V4.73359L17.0522 5.64079C17.3704 5.88864 17.5847 6.18072 17.7223 6.4362C17.8746 6.71911 18.0234 7.12558 18.0234 7.61719C18.0234 8.10158 17.8785 8.74439 17.3959 9.28773L16.8618 9.92451C16.5372 10.3461 16.1426 10.5858 15.7734 10.719V12.375C15.7734 13.9179 14.4874 15 13.1484 15H10.5C9.9703 15 9.44888 14.8306 9.01208 14.5304C8.60011 14.823 8.09287 15 7.52344 15H4.89844C3.44486 15 2.27344 13.8286 2.27344 12.375V10.7274Z' fill='%23262222'/%3E%3C/svg%3E" ); + --unit-aircraft-state-rtb: url( "/themes/olympus/images/state_rtb.svg" ); + --unit-aircraft-state-idle: url( "/themes/olympus/images/state_idle.svg" ); + --unit-aircraft-state-attack: url( "/themes/olympus/images/state_attack.svg" ); + --unit-aircraft-state-follow: url( "/themes/olympus/images/state_follow.svg" ); + --unit-aircraft-state-refuel: url( "/themes/olympus/images/state_refuel.svg" ); - - --unit-aircraft-status-hold-height: 50px; - --unit-aircraft-status-hold-width: 50px; - - --unit-aircraft-status-hold-blue-url: url( "data:image/svg+xml,%3Csvg width='18' height='17' viewBox='0 0 18 17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M9.48873 0.900581C10.6482 -0.282964 12.5476 -0.302474 13.7311 0.857004L15.5175 2.607C16.0935 3.17128 16.4181 3.94367 16.4181 4.75C16.4181 4.98835 16.3897 5.22373 16.3349 5.45165C16.7777 5.85549 17.1041 6.39558 17.2419 7.02558L17.3634 7.58115L17.3634 7.58116C18.1864 11.3434 15.254 14.75 11.5216 14.75H9.59627C9.45579 15.0566 9.26081 15.3446 9.01119 15.5994C7.85171 16.783 5.95232 16.8025 4.76878 15.643L2.98245 13.893C2.40646 13.3287 2.08187 12.5563 2.08187 11.75C2.08187 11.5117 2.11023 11.2763 2.16507 11.0484C1.72225 10.6445 1.3958 10.1044 1.258 9.47436L1.13648 8.91882C1.13648 8.91881 1.13648 8.9188 1.13648 8.9188C0.313576 5.15664 3.24591 1.75 6.97832 1.75H8.90365C9.04413 1.44337 9.23911 1.15538 9.48873 0.900581ZM8.90365 7.75H8.85271C8.88343 7.77715 8.91372 7.80505 8.94357 7.83367C8.92981 7.80595 8.9165 7.77805 8.90365 7.75ZM9.55635 8.66633C9.57011 8.69406 9.58342 8.72195 9.59627 8.75H9.64721C9.61649 8.72285 9.5862 8.69496 9.55635 8.66633Z' fill='%23082E44'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M11.0958 2.47515C11.3856 2.17926 11.8605 2.17438 12.1564 2.46425L13.9427 4.21425C14.0867 4.35532 14.1678 4.54842 14.1678 4.75C14.1678 4.95158 14.0867 5.14468 13.9427 5.28575L12.1564 7.03575C11.8605 7.32562 11.3856 7.32074 11.0958 7.02486C10.8059 6.72897 10.8108 6.25412 11.1067 5.96425L11.5805 5.5H6.9781C5.54192 5.5 4.50895 6.78851 4.79965 8.11751L4.79965 8.11751L4.92117 8.67308C5.00967 9.07772 4.75339 9.4775 4.34875 9.56601C3.9441 9.65452 3.54432 9.39824 3.45581 8.99359L3.33429 8.43803C3.33429 8.43803 3.33429 8.43803 3.33429 8.43802C2.83071 6.13576 4.62343 4 6.9781 4H11.5805L11.1067 3.53575C10.8108 3.24588 10.8059 2.77103 11.0958 2.47515ZM14.1507 6.93399C14.5554 6.84548 14.9552 7.10175 15.0437 7.5064L15.1652 8.06196L14.4609 8.21604L15.1652 8.06196C15.6688 10.3642 13.8761 12.5 11.5214 12.5H6.91894L7.39283 12.9643C7.68872 13.2541 7.69359 13.729 7.40372 14.0249C7.11385 14.3207 6.63901 14.3256 6.34312 14.0357L4.5568 12.2857C4.4128 12.1447 4.33165 11.9516 4.33165 11.75C4.33165 11.5484 4.4128 11.3553 4.5568 11.2143L6.34312 9.46425C6.63901 9.17438 7.11385 9.17926 7.40372 9.47515C7.69359 9.77103 7.68872 10.2459 7.39283 10.5357L6.91894 11H11.5214C12.9576 11 13.9906 9.71149 13.6998 8.38251L13.5783 7.82694C13.4898 7.42229 13.7461 7.02251 14.1507 6.93399Z' fill='white'/%3E%3C/svg%3E" ); - --unit-aircraft-status-hold-neutral-url: url( "data:image/svg+xml,%3Csvg width='17' height='17' viewBox='0 0 17 17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.5356 0.900581C9.69508 -0.282964 11.5945 -0.302474 12.778 0.857004L14.5643 2.607C15.1403 3.17128 15.4649 3.94367 15.4649 4.75C15.4649 4.98835 15.4366 5.22373 15.3817 5.45165C15.8245 5.85549 16.151 6.39558 16.2888 7.02558L16.4103 7.58115L16.4103 7.58116C17.2333 11.3434 14.3009 14.75 10.5685 14.75H8.64315C8.50267 15.0566 8.30769 15.3446 8.05807 15.5994C6.89859 16.783 4.99919 16.8025 3.81565 15.643L2.02933 13.893C1.45334 13.3287 1.12875 12.5563 1.12875 11.75C1.12875 11.5117 1.15711 11.2763 1.21194 11.0484C0.769126 10.6445 0.442675 10.1044 0.30487 9.47436L0.183357 8.91882C0.183355 8.91881 0.183354 8.9188 0.183352 8.9188C-0.639549 5.15664 2.29279 1.75 6.02519 1.75H7.95052C8.09101 1.44337 8.28598 1.15538 8.5356 0.900581ZM7.95052 7.75H7.89958C7.93031 7.77715 7.9606 7.80505 7.99045 7.83367C7.97668 7.80595 7.96338 7.77805 7.95052 7.75ZM8.60322 8.66633C8.61699 8.69406 8.6303 8.72195 8.64315 8.75H8.69409C8.66337 8.72285 8.63307 8.69496 8.60322 8.66633Z' fill='%232F2F2F'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.1426 2.47515C10.4325 2.17926 10.9074 2.17438 11.2032 2.46425L12.9896 4.21425C13.1336 4.35532 13.2147 4.54842 13.2147 4.75C13.2147 4.95158 13.1336 5.14468 12.9896 5.28575L11.2032 7.03575C10.9074 7.32562 10.4325 7.32074 10.1426 7.02486C9.85276 6.72897 9.85764 6.25412 10.1535 5.96425L10.6274 5.5H6.02497C4.5888 5.5 3.55582 6.78851 3.84652 8.11751L3.84652 8.11751L3.96804 8.67308C4.05655 9.07772 3.80027 9.4775 3.39562 9.56601C2.99097 9.65452 2.59119 9.39824 2.50268 8.99359L2.38117 8.43803C2.38117 8.43803 2.38117 8.43803 2.38117 8.43802C1.87759 6.13576 3.6703 4 6.02497 4H10.6274L10.1535 3.53575C9.85764 3.24588 9.85276 2.77103 10.1426 2.47515ZM13.1976 6.93399C13.6022 6.84548 14.002 7.10175 14.0905 7.5064L14.2121 8.06196L13.5077 8.21604L14.2121 8.06196C14.7157 10.3642 12.9229 12.5 10.5683 12.5H5.96582L6.4397 12.9643C6.73559 13.2541 6.74047 13.729 6.4506 14.0249C6.16073 14.3207 5.68588 14.3256 5.38999 14.0357L3.60367 12.2857C3.45967 12.1447 3.37853 11.9516 3.37853 11.75C3.37853 11.5484 3.45967 11.3553 3.60367 11.2143L5.38999 9.46425C5.68588 9.17438 6.16073 9.17926 6.4506 9.47515C6.74047 9.77103 6.73559 10.2459 6.4397 10.5357L5.96582 11H10.5683C12.0044 11 13.0374 9.71149 12.7467 8.38251L12.6252 7.82694C12.5367 7.42229 12.7929 7.02251 13.1976 6.93399Z' fill='white'/%3E%3C/svg%3E" ); - --unit-aircraft-status-hold-red-url: url( "data:image/svg+xml,%3Csvg width='17' height='17' viewBox='0 0 17 17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.51217 0.900581C9.67164 -0.282964 11.571 -0.302474 12.7546 0.857004L14.5409 2.607C15.1169 3.17128 15.4415 3.94367 15.4415 4.75C15.4415 4.98835 15.4131 5.22373 15.3583 5.45165C15.8011 5.85549 16.1275 6.39558 16.2654 7.02558L16.3869 7.58115L16.3869 7.58116C17.2098 11.3434 14.2774 14.75 10.5451 14.75H8.61971C8.47923 15.0566 8.28425 15.3446 8.03463 15.5994C6.87515 16.783 4.97576 16.8025 3.79221 15.643L2.00589 13.893C1.4299 13.3287 1.10531 12.5563 1.10531 11.75C1.10531 11.5117 1.13367 11.2763 1.1885 11.0484C0.745688 10.6445 0.419237 10.1044 0.281433 9.47436L0.159919 8.91882C0.159918 8.91881 0.159916 8.9188 0.159915 8.9188C-0.662986 5.15664 2.26935 1.75 6.00175 1.75H7.92709C8.06757 1.44337 8.26255 1.15538 8.51217 0.900581ZM7.92709 7.75H7.87615C7.90687 7.77715 7.93716 7.80505 7.96701 7.83367C7.95325 7.80595 7.93994 7.77805 7.92709 7.75ZM8.57979 8.66633C8.59355 8.69406 8.60686 8.72195 8.61971 8.75H8.67065C8.63993 8.72285 8.60963 8.69496 8.57979 8.66633Z' fill='%23262222'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.1192 2.47515C10.4091 2.17926 10.8839 2.17438 11.1798 2.46425L12.9661 4.21425C13.1101 4.35532 13.1913 4.54842 13.1913 4.75C13.1913 4.95158 13.1101 5.14468 12.9661 5.28575L11.1798 7.03575C10.8839 7.32562 10.4091 7.32074 10.1192 7.02486C9.82932 6.72897 9.8342 6.25412 10.1301 5.96425L10.604 5.5H6.00153C4.56536 5.5 3.53239 6.78851 3.82309 8.11751L3.82309 8.11751L3.9446 8.67308C4.03311 9.07772 3.77683 9.4775 3.37218 9.56601C2.96754 9.65452 2.56775 9.39824 2.47925 8.99359L2.35773 8.43803C2.35773 8.43803 2.35773 8.43803 2.35773 8.43802C1.85415 6.13576 3.64687 4 6.00153 4H10.604L10.1301 3.53575C9.8342 3.24588 9.82932 2.77103 10.1192 2.47515ZM13.1742 6.93399C13.5788 6.84548 13.9786 7.10175 14.0671 7.5064L14.1886 8.06196L13.4843 8.21604L14.1886 8.06196C14.6922 10.3642 12.8995 12.5 10.5448 12.5H5.94238L6.41627 12.9643C6.71215 13.2541 6.71703 13.729 6.42716 14.0249C6.13729 14.3207 5.66244 14.3256 5.36656 14.0357L3.58023 12.2857C3.43624 12.1447 3.35509 11.9516 3.35509 11.75C3.35509 11.5484 3.43624 11.3553 3.58023 11.2143L5.36656 9.46425C5.66244 9.17438 6.13729 9.17926 6.42716 9.47515C6.71703 9.77103 6.71215 10.2459 6.41627 10.5357L5.94238 11H10.5448C11.981 11 13.014 9.71149 12.7233 8.38251L12.6018 7.82694C12.5132 7.42229 12.7695 7.02251 13.1742 6.93399Z' fill='white'/%3E%3C/svg%3E" ); - - /*** Ground ***/ --unit-groundunit-marker-height: 50px; --unit-groundunit-marker-width: 50px; diff --git a/client/src/controls/unitcontextmenu.ts b/client/src/controls/unitcontextmenu.ts index ea6a82cf..ea27bcd0 100644 --- a/client/src/controls/unitcontextmenu.ts +++ b/client/src/controls/unitcontextmenu.ts @@ -11,6 +11,18 @@ export class UnitContextMenu extends ContextMenu { if (dialog) { dialog.classList.add("hide"); + var clock = 1; + while (clock < 8) + { + if (( dialog.querySelector(`#formation-${clock}`)).checked) + break + clock++; + } + var angleDeg = 360 - (clock - 1) * 45; + var distance = parseInt(( dialog.querySelector(`#distance`)?.querySelector("input")).value); + var upDown = parseInt(( dialog.querySelector(`#up-down`)?.querySelector("input")).value); + var asd= 1; + } if (this.#callback) diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 7a97967b..dfd04efb 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -117,12 +117,12 @@ export function attackUnit(ID: number, targetID: number) { POST(data, () => { }); } -export function followUnit(ID: number, targetID: number) { +export function followUnit(ID: number, targetID: number, offset: {"x": number, "y": number, "z": number}) { // X: front-rear, positive front // Y: top-bottom, positive bottom // Z: left-right, positive right - var command = { "ID": ID, "targetID": targetID, "offsetX": -50, "offsetY": -10, "offsetZ": 50}; + var command = { "ID": ID, "targetID": targetID, "offsetX": offset["x"], "offsetY": offset["y"], "offsetZ": offset["z"]}; var data = { "followUnit": command } POST(data, () => { }); } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index b282b4a9..f45390c6 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -330,7 +330,6 @@ export class Unit extends Marker { } attackUnit(targetID: number) { - /* Call DCS attackUnit function */ if (this.ID != targetID) { attackUnit(this.ID, targetID); } @@ -339,10 +338,9 @@ export class Unit extends Marker { } } - followUnit(targetID: number) { - /* Call DCS attackUnit function */ + followUnit(targetID: number, offset: {"x": number, "y": number, "z": number}) { if (this.ID != targetID) { - followUnit(this.ID, targetID); + followUnit(this.ID, targetID, offset); } else { // TODO: show a message @@ -458,9 +456,10 @@ export class Unit extends Marker { options = { 'Trail': `
Trail
`, - 'Echelon (LH)': `
Echelon (LH)
`, - 'Echelon (RH)': `
Echelon (RH)
`, - 'Line abreast': `
Line abreast
`, + 'Echelon (LH)': `
Echelon (left)
`, + 'Echelon (RH)': `
Echelon (right)
`, + 'Line abreast (LH)': `
Line abreast (left)
`, + 'Line abreast (RH)': `
Line abreast (right)
`, 'Front': `
In front
`, 'Custom': `
Custom
` } @@ -479,7 +478,36 @@ export class Unit extends Marker { document.getElementById("custom-formation-dialog")?.classList.remove("hide"); } else { - getUnitsManager().selectedUnitsFollowUnit(this.ID); + // X: front-rear, positive front + // Y: top-bottom, positive top + // Z: left-right, positive right + + var offset = {"x": 0, "y": 0, "z": 0}; + if (action == "Trail") + { + offset.x = -50; offset.y = -30; offset.z = 0; + } + else if (action == "Echelon (LH)") + { + offset.x = -50; offset.y = -10; offset.z = -50; + } + else if (action == "Echelon (RH)") + { + offset.x = -50; offset.y = -10; offset.z = 50; + } + else if (action == "Line abreast (RH)") + { + offset.x = 0; offset.y = 0; offset.z = 50; + } + else if (action == "Line abreast (LH)") + { + offset.x = 0; offset.y = 0; offset.z = -50; + } + else if (action == "Front") + { + offset.x = 100; offset.y = 0; offset.z = 0; + } + getUnitsManager().selectedUnitsFollowUnit(this.ID, offset); } } @@ -499,6 +527,8 @@ export class Unit extends Marker { element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getBaseData().alive); + element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase()); + var unitHeadingDiv = element.querySelector(".unit-heading"); if (unitHeadingDiv != null) unitHeadingDiv.innerHTML = String(Math.floor(rad2deg(this.getFlightData().heading))); @@ -609,10 +639,10 @@ export class Aircraft extends AirUnit { getMarkerHTML() { - return `
+ return `
-
+
diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 3e861717..bab60807 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -334,11 +334,13 @@ export class UnitsManager { } } - selectedUnitsFollowUnit(ID: number) { + selectedUnitsFollowUnit(ID: number, offset: {"x": number, "y": number, "z": number}) { var selectedUnits = this.getSelectedUnits(); + var count = 1; for (let idx in selectedUnits) { var commandedUnit = selectedUnits[idx]; - commandedUnit.followUnit(ID); + commandedUnit.followUnit(ID, {"x": offset.x * count, "y": offset.y * count, "z": offset.z * count} ); + count++; } } diff --git a/client/views/contextmenus.ejs b/client/views/contextmenus.ejs index 6c3e0335..b386ec9a 100644 --- a/client/views/contextmenus.ejs +++ b/client/views/contextmenus.ejs @@ -79,44 +79,6 @@
-
-
- -
-

Custom formation

-
- -
-
-
-
-
-
-
-
-
-
-
-
- -
- - -
-
- -
- -
-
-
- - -
-

diff --git a/client/views/dialogs.ejs b/client/views/dialogs.ejs index 99d09c92..9c641408 100644 --- a/client/views/dialogs.ejs +++ b/client/views/dialogs.ejs @@ -16,3 +16,178 @@
+ + + +
+
+ +
+

Olympus 1-1

+
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+
+ +
+ +
+
X
+
+
+
+ +
+ +
+
+
+ +
+ + +
+
+ +
+ +
+
.000
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+ + + +
+ +
+
+
+
+ + + +
+ + +
+
+ +
+

Custom formation

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+
+ + +
\ No newline at end of file diff --git a/client/views/uikit.ejs b/client/views/uikit.ejs index 3bfa3697..851b2a16 100644 --- a/client/views/uikit.ejs +++ b/client/views/uikit.ejs @@ -502,9 +502,9 @@
-
+
-
+
4
@@ -532,9 +532,9 @@
-
+
-
+
4
@@ -561,9 +561,9 @@
-
+
-
+
4
@@ -591,9 +591,9 @@
-
+
-
+
4
@@ -621,9 +621,9 @@
-
+
-
+
4
@@ -650,9 +650,9 @@
-
+
-
+
4
@@ -688,9 +688,9 @@
-
+
-
+
4
@@ -718,9 +718,9 @@
-
+
-
+
4
@@ -747,9 +747,9 @@
-
+
-
+
4
diff --git a/client/views/unitcontrolpanel.ejs b/client/views/unitcontrolpanel.ejs index 40629d4d..276b569c 100644 --- a/client/views/unitcontrolpanel.ejs +++ b/client/views/unitcontrolpanel.ejs @@ -62,127 +62,3 @@
- - -
-
- -
-

Olympus 1-1

-
- -
- - -
- -
- -
- -
- -
- - -
-
- -
- -
-
X
-
-
-
- -
- -
-
-
- -
- - -
-
- -
- -
-
.000
-
-
-
-
-
- -
- - -
-
-
-
-
-
- - - -
- -
-
-
-
- - - -
\ No newline at end of file diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index cf3c9079..5d580b65 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -52,10 +52,12 @@ void AirUnit::setState(int newState) case State::IDLE: { clearActivePath(); resetActiveDestination(); + addMeasure(L"currentState", json::value(L"Idle")); break; } case State::REACH_DESTINATION: { resetActiveDestination(); + addMeasure(L"currentState", json::value(L"Reach destination")); break; } case State::ATTACK: { @@ -65,21 +67,25 @@ void AirUnit::setState(int newState) clearActivePath(); pushActivePathFront(targetPosition); resetActiveDestination(); + addMeasure(L"currentState", json::value(L"Attack")); } break; } case State::FOLLOW: { clearActivePath(); resetActiveDestination(); + addMeasure(L"currentState", json::value(L"Follow")); break; } case State::LAND: { resetActiveDestination(); + addMeasure(L"currentState", json::value(L"Land")); break; } case State::REFUEL: { clearActivePath(); resetActiveDestination(); + addMeasure(L"currentState", json::value(L"Refuel")); break; } default: @@ -184,7 +190,7 @@ void AirUnit::AIloop() { std::wostringstream taskSS; if (isTanker) { - taskSS << "{ [1] = { id = 'Orbit', pattern = 'Race-Track' }, [2] = { id = 'Tanker' } }"; + taskSS << "{ [1] = { id = 'Tanker' }, [2] = { id = 'Orbit', pattern = 'Race-Track' } }"; } else { taskSS << "{ id = 'Orbit', pattern = 'Circle' }"; @@ -260,21 +266,12 @@ void AirUnit::AIloop() wstring enrouteTask = enrouteTaskSS.str(); currentTask = L"Attacking " + getTargetName(); - if (activeDestination == NULL || !hasTask) + if (!hasTask) { setActiveDestination(); goToDestination(enrouteTask); } - else { - if (isDestinationReached()) { - if (updateActivePath(false) && setActiveDestination()) - goToDestination(enrouteTask); - else { - setState(State::IDLE); - break; - } - } - } + break; } case State::FOLLOW: { @@ -327,5 +324,4 @@ void AirUnit::AIloop() break; } addMeasure(L"currentTask", json::value(currentTask)); - addMeasure(L"currentState", json::value(state)); } diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index ee64b6c3..bf848269 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -19,6 +19,7 @@ Unit::Unit(json::value json, int ID) : ID(ID) { log("Creating unit with ID: " + to_string(ID)); + addMeasure(L"currentState", json::value(L"Idle")); } Unit::~Unit() @@ -134,7 +135,7 @@ json::value Unit::getData(long long time) /********** Task data **********/ json[L"taskData"] = json::value::object(); - for (auto key : { L"currentTask", L"targetSpeed", L"targetAltitude", L"activePath", L"isTanker", L"isAWACS", L"TACANChannel", L"TACANXY", L"TACANCallsign", L"radioFrequency", L"radioCallsign", L"radioCallsignNumber"}) + for (auto key : { L"currentState", L"currentTask", L"targetSpeed", L"targetAltitude", L"activePath", L"isTanker", L"isAWACS", L"TACANChannel", L"TACANXY", L"TACANCallsign", L"radioFrequency", L"radioCallsign", L"radioCallsignNumber"}) { if (measures.find(key) != measures.end() && measures[key]->getTime() > time) json[L"taskData"][key] = measures[key]->getValue(); From f24c57cc18b94042e8c9ffb7709e10ebf2415529 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 19 Apr 2023 10:19:56 +0200 Subject: [PATCH 10/11] Follow feature completed --- client/public/stylesheets/olympus.css | 4 +- client/public/stylesheets/popup.css | 23 +++++++ client/src/controls/unitcontextmenu.ts | 27 ++++++--- client/src/index.ts | 15 ++++- client/src/popups/popup.ts | 26 ++++++++ client/src/units/unit.ts | 3 + client/src/units/unitsmanager.ts | 84 +++++++------------------- client/views/dialogs.ejs | 6 +- client/views/index.ejs | 1 + client/views/popups.ejs | 5 ++ 10 files changed, 117 insertions(+), 77 deletions(-) create mode 100644 client/public/stylesheets/popup.css create mode 100644 client/src/popups/popup.ts create mode 100644 client/views/popups.ejs diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 6e96c593..3570cffb 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -7,7 +7,7 @@ @import url("unitdatatable.css"); @import url("unitcontrolpanel.css"); @import url("unitinfopanel.css"); - +@import url("popup.css"); * { @@ -441,8 +441,6 @@ nav.ol-panel> :last-child { font-weight: var(--font-weight-bolder); } - - .hide { display: none !important; } diff --git a/client/public/stylesheets/popup.css b/client/public/stylesheets/popup.css new file mode 100644 index 00000000..fb514460 --- /dev/null +++ b/client/public/stylesheets/popup.css @@ -0,0 +1,23 @@ +#info-popup { + position: absolute; + width: fit-content; + height: fit-content; + top: 100px; + left: 50%; + translate: -50% 0%; + z-index: 9999; +} + +.ol-popup > div { + padding-left: 15px; + padding-right: 15px; +} + +.visible { + opacity: 1; +} + +.invisible { + opacity: 0; + transition: opacity 2s linear; +} \ No newline at end of file diff --git a/client/src/controls/unitcontextmenu.ts b/client/src/controls/unitcontextmenu.ts index ea27bcd0..aaa1b205 100644 --- a/client/src/controls/unitcontextmenu.ts +++ b/client/src/controls/unitcontextmenu.ts @@ -1,7 +1,9 @@ +import { getUnitsManager } from ".."; +import { deg2rad } from "../other/utils"; import { ContextMenu } from "./contextmenu"; export class UnitContextMenu extends ContextMenu { - #callback: CallableFunction | null = null; + #customFormationCallback: CallableFunction | null = null; constructor(id: string) { super(id); @@ -19,17 +21,28 @@ export class UnitContextMenu extends ContextMenu { clock++; } var angleDeg = 360 - (clock - 1) * 45; - var distance = parseInt(( dialog.querySelector(`#distance`)?.querySelector("input")).value); - var upDown = parseInt(( dialog.querySelector(`#up-down`)?.querySelector("input")).value); - var asd= 1; + var angleRad = deg2rad(angleDeg); + var distance = parseInt(( dialog.querySelector(`#distance`)?.querySelector("input")).value) * 0.3048; + var upDown = parseInt(( dialog.querySelector(`#up-down`)?.querySelector("input")).value) * 0.3048; - } + // X: front-rear, positive front + // Y: top-bottom, positive top + // Z: left-right, positive right - if (this.#callback) - this.#callback() + var x = distance * Math.cos(angleRad); + var y = upDown; + var z = distance * Math.sin(angleRad); + + if (this.#customFormationCallback) + this.#customFormationCallback({"x": x, "y": y, "z": z}) + } }) } + setCustomFormationCallback(callback: CallableFunction) { + this.#customFormationCallback = callback; + } + setOptions(options: {[key: string]: string}, callback: CallableFunction) { this.getContainer()?.replaceChildren(...Object.keys(options).map((option: string, idx: number) => diff --git a/client/src/index.ts b/client/src/index.ts index f95e4d5a..dda20f45 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -12,6 +12,7 @@ import { LogPanel } from "./panels/logpanel"; import { getAirbases, getBullseye as getBullseyes, getConfig, getMission, getUnits, setAddress, toggleDemoEnabled } from "./server/server"; import { UnitDataTable } from "./units/unitdatatable"; import { keyEventWasInInput } from "./other/utils"; +import { Popup } from "./popups/popup"; var map: Map; @@ -27,6 +28,8 @@ var unitControlPanel: UnitControlPanel; var mouseInfoPanel: MouseInfoPanel; var logPanel: LogPanel; +var infoPopup: Popup; + var connected: boolean = false; var paused: boolean = false; var activeCoalition: string = "blue"; @@ -52,6 +55,9 @@ function setup() { mouseInfoPanel = new MouseInfoPanel("mouse-info-panel"); //logPanel = new LogPanel("log-panel"); + /* Popups */ + infoPopup = new Popup("info-popup"); + unitDataTable = new UnitDataTable("unit-data-table"); /* AIC */ @@ -269,7 +275,9 @@ export function getActiveCoalition() { } export function setConnected(newConnected: boolean) { - connected = newConnected + if (connected != newConnected) + newConnected? getInfoPopup().setText("Connected to DCS Olympus server"): getInfoPopup().setText("Disconnected from DCS Olympus server"); + connected = newConnected; } export function getConnected() { @@ -278,10 +286,15 @@ export function getConnected() { export function setPaused(newPaused: boolean) { paused = newPaused; + paused? getInfoPopup().setText("Paused"): getInfoPopup().setText("Unpaused"); } export function getPaused() { return paused; } +export function getInfoPopup() { + return infoPopup; +} + window.onload = setup; \ No newline at end of file diff --git a/client/src/popups/popup.ts b/client/src/popups/popup.ts new file mode 100644 index 00000000..29fad207 --- /dev/null +++ b/client/src/popups/popup.ts @@ -0,0 +1,26 @@ +import { Panel } from "../panels/panel"; + +export class Popup extends Panel { + #fadeTime: number = 2000; // Milliseconds + #hideTimer: number | undefined = undefined; + #visibilityTimer: number | undefined = undefined; + + setFadeTime(fadeTime: number) { + this.#fadeTime = fadeTime; + } + + setText(text: string) { + ( this.getElement().querySelector("div")).innerText = text; + this.show(); + this.getElement().classList.remove("invisible"); + this.getElement().classList.add("visible"); + + clearTimeout(this.#visibilityTimer); + clearTimeout(this.#hideTimer); + this.#visibilityTimer = setTimeout(() => { + this.getElement().classList.remove("visible"); + this.getElement().classList.add("invisible"); + this.#hideTimer = setTimeout(() => this.hide(), 2000); + }, this.#fadeTime); + } +} \ No newline at end of file diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index f45390c6..4bd59997 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -476,6 +476,9 @@ export class Unit extends Marker { if (action === "Custom") { document.getElementById("custom-formation-dialog")?.classList.remove("hide"); + getMap().getUnitContextMenu().setCustomFormationCallback((offset: {x: number, y: number, z: number}) => { + getUnitsManager().selectedUnitsFollowUnit(this.ID, offset); + }) } else { // X: front-rear, positive front diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index bab60807..becb74c5 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -1,5 +1,5 @@ import { LatLng, LatLngBounds } from "leaflet"; -import { getMap, getUnitDataTable } from ".."; +import { getInfoPopup, getMap, getUnitDataTable } from ".."; import { Unit } from "./unit"; import { cloneUnit } from "../server/server"; import { IDLE, MOVE_UNIT } from "../map/map"; @@ -176,6 +176,7 @@ export class UnitsManager { var commandedUnit = selectedUnits[idx]; commandedUnit.addDestination(latlng); } + this.#showActionMessage(selectedUnits, " new destination added"); } selectedUnitsClearDestinations() { @@ -193,6 +194,7 @@ export class UnitsManager { { selectedUnits[idx].landAt(latlng); } + this.#showActionMessage(selectedUnits, " landing"); } selectedUnitsChangeSpeed(speedChange: string) @@ -220,6 +222,8 @@ export class UnitsManager { { selectedUnits[idx].setSpeed(speed); } + + this.#showActionMessage(selectedUnits, `setting speed to ${speed * 1.94384} kts`); } selectedUnitsSetAltitude(altitude: number) @@ -229,6 +233,7 @@ export class UnitsManager { { selectedUnits[idx].setAltitude(altitude); } + this.#showActionMessage(selectedUnits, `setting altitude to ${altitude / 0.3048} ft`); } selectedUnitsSetROE(ROE: string) @@ -238,6 +243,7 @@ export class UnitsManager { { selectedUnits[idx].setROE(ROE); } + this.#showActionMessage(selectedUnits, `ROE set to ${ROE}`); } selectedUnitsSetReactionToThreat(reactionToThreat: string) @@ -247,73 +253,15 @@ export class UnitsManager { { selectedUnits[idx].setReactionToThreat(reactionToThreat); } + this.#showActionMessage(selectedUnits, `reaction to threat set to ${reactionToThreat}`); } selectedUnitsAttackUnit(ID: number) { var selectedUnits = this.getSelectedUnits(); for (let idx in selectedUnits) { - /* If a unit is a wingman, send the command to its leader */ - var commandedUnit = selectedUnits[idx]; - //if (selectedUnits[idx].wingman) - //{ - // commandedUnit = this.getLeader(selectedUnits[idx].ID); - //} - commandedUnit.attackUnit(ID); - } - } - - selectedUnitsCreateFormation(ID: number | null = null) - { - var selectedUnits = this.getSelectedUnits(); - if (selectedUnits.length >= 2) - { - if (ID == null) - ID = selectedUnits[0].ID - - var wingmenIDs = []; - for (let idx in selectedUnits) - { - if (selectedUnits[idx].getFormationData().isWingman) - { - //console.log(selectedUnits[idx].unitName + " is already in a formation."); - return; - } - else if (selectedUnits[idx].getFormationData().isLeader) - { - //console.log(selectedUnits[idx].unitName + " is already in a formation."); - return; - } - else - { - /* TODO - if (selectedUnits[idx].category !== this.getUnitByID(ID).category) - { - showMessage("All units must be of the same category to create a formation."); - } - */ - if (selectedUnits[idx].ID != ID) - { - wingmenIDs.push(selectedUnits[idx].ID); - } - } - } - if (wingmenIDs.length > 0) - { - this.getUnitByID(ID)?.setLeader(true, wingmenIDs); - } - else - { - //console.log("At least 2 units must be selected to create a formation."); - } - } - } - - selectedUnitsUndoFormation() - { - for (let leader of this.getSelectedLeaders()) - { - leader.setLeader(false); + selectedUnits[idx].attackUnit(ID); } + this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getBaseData().unitName}`); } selectedUnitsDelete() @@ -323,6 +271,7 @@ export class UnitsManager { { selectedUnits[idx].delete(); } + this.#showActionMessage(selectedUnits, `deleted`); } selectedUnitsRefuel() @@ -332,6 +281,7 @@ export class UnitsManager { { selectedUnits[idx].refuel(); } + this.#showActionMessage(selectedUnits, `sent to nearest tanker`); } selectedUnitsFollowUnit(ID: number, offset: {"x": number, "y": number, "z": number}) { @@ -342,11 +292,13 @@ export class UnitsManager { commandedUnit.followUnit(ID, {"x": offset.x * count, "y": offset.y * count, "z": offset.z * count} ); count++; } + this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`); } copyUnits() { this.#copiedUnits = this.getSelectedUnits(); + this.#showActionMessage(this.#copiedUnits, `copied`); } pasteUnits() @@ -357,6 +309,7 @@ export class UnitsManager { { var unit = this.#copiedUnits[idx]; cloneUnit(unit.ID, getMap().getMouseCoordinates()); + this.#showActionMessage(this.#copiedUnits, `pasted`); } this.#pasteDisabled = true; setTimeout(() => this.#pasteDisabled = false, 250); @@ -398,4 +351,11 @@ export class UnitsManager { else document.dispatchEvent(new CustomEvent("unitsDeselection", {detail: this.getSelectedUnits()})); } + + #showActionMessage(units: Unit[], message: string) { + if (units.length == 1) + getInfoPopup().setText(`${units[0].getBaseData().unitName} ${message}`); + else + getInfoPopup().setText(`${units[0].getBaseData().unitName} and ${units.length - 1} other units ${message}`); + } } \ No newline at end of file diff --git a/client/views/dialogs.ejs b/client/views/dialogs.ejs index 9c641408..1f2981d1 100644 --- a/client/views/dialogs.ejs +++ b/client/views/dialogs.ejs @@ -17,8 +17,6 @@
- -
@@ -143,7 +141,7 @@
-
+
@@ -168,7 +166,7 @@
- +
diff --git a/client/views/index.ejs b/client/views/index.ejs index 79e15902..f23920bf 100644 --- a/client/views/index.ejs +++ b/client/views/index.ejs @@ -33,6 +33,7 @@ <%- include('connectionstatuspanel.ejs') %> <%- include('dialogs.ejs') %> <%- include('unitdatatable.ejs') %> + <%- include('popups.ejs') %> <% /* %> <%- include('log.ejs') %> diff --git a/client/views/popups.ejs b/client/views/popups.ejs new file mode 100644 index 00000000..80043521 --- /dev/null +++ b/client/views/popups.ejs @@ -0,0 +1,5 @@ +
+
+ +
+
\ No newline at end of file From c3c84b211578528715379324dfe1ab0bd8bff1bf Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 19 Apr 2023 16:30:14 +0200 Subject: [PATCH 11/11] Follow, tankers and AWACS completed --- client/src/controls/dropdown.ts | 16 ++++++++-- client/src/missionhandler/missionhandler.ts | 8 +++-- client/src/panels/unitcontrolpanel.ts | 33 ++++++++++++++++++--- src/core/include/unit.h | 7 +++-- src/core/src/airunit.cpp | 25 +++++++++++----- 5 files changed, 68 insertions(+), 21 deletions(-) diff --git a/client/src/controls/dropdown.ts b/client/src/controls/dropdown.ts index 7e59d00b..c675d1dd 100644 --- a/client/src/controls/dropdown.ts +++ b/client/src/controls/dropdown.ts @@ -24,9 +24,10 @@ export class Dropdown { this.#clip(); }); - this.#element.addEventListener("mouseleave", ev => { - this.#close(); - }); + // Commented out since it is a bit frustrating, particularly when the dropdown opens towards the top and not to the bottom + //this.#element.addEventListener("mouseleave", ev => { + // this.#close(); + //}); } setOptions(optionsList: string[]) @@ -61,7 +62,10 @@ export class Dropdown { this.#index = idx; this.#close(); this.#callback(option); + return true; } + else + return false; } reset() { @@ -73,6 +77,12 @@ export class Dropdown { return this.#value.innerText; } + setValue(value: string) { + var index = this.#optionsList.findIndex((option) => {return option === value}); + if (index > -1) + this.selectValue(index); + } + getIndex() { return this.#index; } diff --git a/client/src/missionhandler/missionhandler.ts b/client/src/missionhandler/missionhandler.ts index 98dae71a..e225eb1c 100644 --- a/client/src/missionhandler/missionhandler.ts +++ b/client/src/missionhandler/missionhandler.ts @@ -1,5 +1,5 @@ import { Marker, LatLng, Icon } from "leaflet"; -import { getMap, getUnitsManager } from ".."; +import { getInfoPopup, getMap, getUnitsManager } from ".."; import { Airbase } from "./airbase"; var bullseyeIcons = [ @@ -58,6 +58,8 @@ export class MissionHandler getMap().setView(new LatLng(-50.6, -42.7), 7); else if (this.#theatre == "Caucasus") getMap().setView(new LatLng(42.1, 42.3), 8); + + getInfoPopup().setText("Map set to " + this.#theatre); } } } @@ -93,8 +95,8 @@ export class MissionHandler { this.#airbasesMarkers[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude)); this.#airbasesMarkers[idx].setCoalition(airbase.coalition); - this.#airbasesMarkers[idx].setProperties(["Runway 1: 31L / 13R", "Runway 2: 31R / 13L", "TCN: 17X", "ILS: ---" ]); - this.#airbasesMarkers[idx].setParkings(["2x big", "5x small"]); + //this.#airbasesMarkers[idx].setProperties(["Runway 1: 31L / 13R", "Runway 2: 31R / 13L", "TCN: 17X", "ILS: ---" ]); + //this.#airbasesMarkers[idx].setParkings(["2x big", "5x small"]); } } } diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 1880eba9..1af37976 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -210,13 +210,37 @@ export class UnitControlPanel extends Panel { if (getUnitsManager().getSelectedUnits().length == 1) { + var radioMHz = Math.floor(unit.getTaskData().radioFrequency / 1000000); + var radioDecimals = (unit.getTaskData().radioFrequency / 1000000 - radioMHz) * 1000; + + // Default values for "normal" units this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]); this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign); - var tankerCheckbox = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input") - if (tankerCheckbox) tankerCheckbox.checked = unit.getTaskData().isTanker; - var AWACSCheckbox = this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input") - if (AWACSCheckbox) AWACSCheckbox.checked = unit.getTaskData().isAWACS; + // Input values + var tankerCheckbox = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input") + var AWACSCheckbox = this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input") + + var TACANChannelInput = this.#advancedSettingsDialog.querySelector("#TACAN-channel")?.querySelector("input"); + var TACANCallsignInput = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input"); + var radioMhzInput = this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input"); + var radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input"); + + if (tankerCheckbox) tankerCheckbox.checked = unit.getTaskData().isTanker; + if (AWACSCheckbox) AWACSCheckbox.checked = unit.getTaskData().isAWACS; + if (TACANChannelInput) TACANChannelInput.value = String(unit.getTaskData().TACANChannel); + if (TACANCallsignInput) TACANCallsignInput.value = String(unit.getTaskData().TACANCallsign); + if (radioMhzInput) radioMhzInput.value = String(radioMHz); + if (radioCallsignNumberInput) radioCallsignNumberInput.value = String(unit.getTaskData().radioCallsignNumber); + + this.#TACANXYDropdown.setValue(unit.getTaskData().TACANXY); + this.#radioDecimalsDropdown.setValue("." + radioDecimals); + + // Make sure its in the valid range + if (!this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign)) + this.#radioCallsignDropdown.selectValue(0); + + // Set options for tankers var roles = aircraftDatabase.getByName(unit.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles}) if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker")){ this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.remove("hide"); @@ -227,6 +251,7 @@ export class UnitControlPanel extends Panel { this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.add("hide"); } + // Set options for AWACS if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS")){ this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.remove("hide"); this.#radioCallsignDropdown.setOptions(["Overlord", "Magic", "Wizard", "Focus", "Darkstar"]); diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 92b50083..441c3e75 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -169,6 +169,7 @@ protected: /********** Mission data **********/ double fuel = 0; + double initialFuel = 0; // Used internally to detect refueling completed json::value ammo = json::value::null(); json::value targets = json::value::null(); bool hasTask = false; @@ -193,12 +194,12 @@ protected: bool isTanker = false; bool isAWACS = false; bool TACANOn = false; - int TACANChannel = 0; + int TACANChannel = 40; wstring TACANXY = L"X"; wstring TACANCallsign = L"TKR"; bool radioOn = false; - int radioFrequency = 0; - int radioCallsign = 0; + int radioFrequency = 260000000; // MHz + int radioCallsign = 1; int radioCallsignNumber = 1; /********** Options data **********/ diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index 5d580b65..cf3d2369 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -83,6 +83,7 @@ void AirUnit::setState(int newState) break; } case State::REFUEL: { + initialFuel = fuel; clearActivePath(); resetActiveDestination(); addMeasure(L"currentState", json::value(L"Refuel")); @@ -192,6 +193,9 @@ void AirUnit::AIloop() if (isTanker) { taskSS << "{ [1] = { id = 'Tanker' }, [2] = { id = 'Orbit', pattern = 'Race-Track' } }"; } + else if (isAWACS) { + taskSS << "{ [1] = { id = 'AWACS' }, [2] = { id = 'Orbit', pattern = 'Circle' } }"; + } else { taskSS << "{ id = 'Orbit', pattern = 'Circle' }"; } @@ -239,7 +243,7 @@ void AirUnit::AIloop() break; } case State::LAND: { - wstring enrouteTask = L"{" "id = 'land' }"; + wstring enrouteTask = L"{ id = 'Land' }"; currentTask = L"Landing"; if (activeDestination == NULL) @@ -311,13 +315,18 @@ void AirUnit::AIloop() currentTask = L"Refueling"; if (!hasTask) { - std::wostringstream taskSS; - taskSS << "{" - << "id = 'Refuel'" - << "}"; - Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); - scheduler->appendCommand(command); - hasTask = true; + if (fuel <= initialFuel) { + std::wostringstream taskSS; + taskSS << "{" + << "id = 'Refuel'" + << "}"; + Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); + scheduler->appendCommand(command); + hasTask = true; + } + else { + setState(State::IDLE); + } } } default: