From 71ef292763bddabb43d6f8c82196421be9529bb0 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 11 May 2023 17:24:56 +0200 Subject: [PATCH 1/2] Added frontend controls for emissions and countermeasures --- client/public/stylesheets/layout.css | 2 +- client/public/stylesheets/olympus.css | 70 +++++++++++++---- .../public/stylesheets/unitcontrolpanel.css | 25 +++++- client/src/index.ts | 2 +- client/src/panels/unitcontrolpanel.ts | 33 +++++--- client/src/server/server.ts | 6 ++ client/src/units/unit.ts | 7 +- client/src/units/unitsmanager.ts | 9 +++ client/views/dialogs.ejs | 77 ++++++++++--------- client/views/unitcontrolpanel.ejs | 7 ++ 10 files changed, 170 insertions(+), 68 deletions(-) diff --git a/client/public/stylesheets/layout.css b/client/public/stylesheets/layout.css index 5c60d1bb..a595382d 100644 --- a/client/public/stylesheets/layout.css +++ b/client/public/stylesheets/layout.css @@ -109,7 +109,7 @@ dl.ol-data-grid dd { } .ol-panel.ol-dialog { - padding: 20px; + padding: 24px 30px; } .ol-dialog-close { diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index d7905e9b..64647448 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -645,13 +645,15 @@ body[data-hide-navyunit] #unit-visibility-control-navyunit { } #roe-buttons-container button.selected, -#reaction-to-threat-buttons-container button.selected { +#reaction-to-threat-buttons-container button.selected, +#emissions-countermeasures-buttons-container button.selected { background-color: white; border-color: white; } #roe-buttons-container button::before, -#reaction-to-threat-buttons-container button::before { +#reaction-to-threat-buttons-container button::before, +#emissions-countermeasures-buttons-container button::before { background-position: center; background-repeat: no-repeat; content: ""; @@ -660,68 +662,104 @@ body[data-hide-navyunit] #unit-visibility-control-navyunit { width: 24px; } -#roe-buttons-container button[title="Hold"]::before { +#roe-buttons-container button[value="Hold"]::before { background-image: url("/themes/olympus/images/icons_roe_stop_light.svg"); } -#roe-buttons-container button[title="Hold"].selected::before { +#roe-buttons-container button[value="Hold"].selected::before { background-image: url("/themes/olympus/images/icons_roe_stop_dark.svg"); } /**/ -#roe-buttons-container button[title="Return"]::before { +#roe-buttons-container button[value="Return"]::before { background-image: url("/themes/olympus/images/icons_roe_defend_light.svg"); } -#roe-buttons-container button[title="Return"].selected::before { +#roe-buttons-container button[value="Return"].selected::before { background-image: url("/themes/olympus/images/icons_roe_defend_dark.svg"); } /**/ -#roe-buttons-container button[title="Designated"]::before { +#roe-buttons-container button[value="Designated"]::before { background-image: url("/themes/olympus/images/icons_roe_target_light.svg"); } -#roe-buttons-container button[title="Designated"].selected::before { +#roe-buttons-container button[value="Designated"].selected::before { background-image: url("/themes/olympus/images/icons_roe_target_dark.svg"); } /**/ -#roe-buttons-container button[title="Free"]::before { +#roe-buttons-container button[value="Free"]::before { background-image: url("/themes/olympus/images/icons_roe_free_light.svg"); } -#roe-buttons-container button[title="Free"].selected::before { +#roe-buttons-container button[value="Free"].selected::before { background-image: url("/themes/olympus/images/icons_roe_free_dark.svg"); } /****************************************************************************************/ -#reaction-to-threat-buttons-container button[title="None"]::before { +#reaction-to-threat-buttons-container button[value="None"]::before { background-image: url("/themes/olympus/images/icons_threat_nothing_light.svg"); } -#reaction-to-threat-buttons-container button[title="None"].selected::before { +#reaction-to-threat-buttons-container button[value="None"].selected::before { background-image: url("/themes/olympus/images/icons_threat_nothing_dark.svg"); } /**/ -#reaction-to-threat-buttons-container button[title="Passive"]::before { +#reaction-to-threat-buttons-container button[value="Passive"]::before { background-image: url("/themes/olympus/images/icons_threat_cms_light.svg"); } -#reaction-to-threat-buttons-container button[title="Passive"].selected::before { +#reaction-to-threat-buttons-container button[value="Passive"].selected::before { background-image: url("/themes/olympus/images/icons_threat_cms_dark.svg"); } /**/ -#reaction-to-threat-buttons-container button[title="Evade"]::before { +#reaction-to-threat-buttons-container button[value="Evade"]::before { background-image: url("/themes/olympus/images/icons_threat_defend_light.svg"); } -#reaction-to-threat-buttons-container button[title="Evade"].selected::before { +#reaction-to-threat-buttons-container button[value="Evade"].selected::before { background-image: url("/themes/olympus/images/icons_threat_defend_dark.svg"); } +/****************************************************************************************/ +#emissions-countermeasures-buttons-container button[value="Silent"]::before { + background-image: url("/themes/olympus/images/icons_roe_stop_light.svg"); +} + +#emissions-countermeasures-buttons-container button[value="Silent"].selected::before { + background-image: url("/themes/olympus/images/icons_roe_stop_dark.svg"); +} + +/**/ +#emissions-countermeasures-buttons-container button[value="Defend"]::before { + background-image: url("/themes/olympus/images/icons_roe_defend_light.svg"); +} + +#emissions-countermeasures-buttons-container button[value="Defend"].selected::before { + background-image: url("/themes/olympus/images/icons_roe_defend_dark.svg"); +} + +/**/ +#emissions-countermeasures-buttons-container button[value="Attack"]::before { + background-image: url("/themes/olympus/images/icons_roe_target_light.svg"); +} + +#emissions-countermeasures-buttons-container button[value="Attack"].selected::before { + background-image: url("/themes/olympus/images/icons_roe_target_dark.svg"); +} + +/**/ +#emissions-countermeasures-buttons-container button[value="Free"]::before { + background-image: url("/themes/olympus/images/icons_roe_free_light.svg"); +} + +#emissions-countermeasures-buttons-container button[value="Free"].selected::before { + background-image: url("/themes/olympus/images/icons_roe_free_dark.svg"); +} + /****************************************************************************************/ #splash-screen { background-image: url("/images/splash/splash_pic_ship.png"); diff --git a/client/public/stylesheets/unitcontrolpanel.css b/client/public/stylesheets/unitcontrolpanel.css index 85e5bf05..44bd5802 100644 --- a/client/public/stylesheets/unitcontrolpanel.css +++ b/client/public/stylesheets/unitcontrolpanel.css @@ -8,7 +8,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { left: 10px; position: absolute; top: 80px; - width: 250px; + width: 260px; z-index: 1000; } @@ -94,7 +94,8 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { } #unit-control-panel #threat, -#unit-control-panel #roe { +#unit-control-panel #roe, +#unit-control-panel #emissions-countermeasures { margin-top: 12px; } @@ -113,4 +114,24 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { #advanced-settings-dialog>.ol-dialog-content>.ol-group { justify-content: space-between; + +} + +#advanced-settings-dialog h4 { + white-space: nowrap; +} + +#advanced-settings-dialog hr { + margin-top: 15px; + margin-bottom: 10px; +} + +#advanced-options-grid { + display: grid; + grid-template-columns: 1fr 1fr; + row-gap: 10px; +} + +#advanced-options-grid>div { + width: 49%; } \ No newline at end of file diff --git a/client/src/index.ts b/client/src/index.ts index b3e7e63e..deb58810 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -89,7 +89,7 @@ function readConfig(config: any) { setAddress(address == "*" ? window.location.hostname : address, port); } else { - throw new Error('Could not read configuration file!'); + throw new Error('Could not read configuration file'); } } diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 75056e4a..23b9c3cb 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -1,7 +1,6 @@ import { getUnitsManager } from ".."; import { Dropdown } from "../controls/dropdown"; import { Slider } from "../controls/slider"; -import { dataPointMap } from "../other/utils"; import { aircraftDatabase } from "../units/aircraftdatabase"; import { groundUnitsDatabase } from "../units/groundunitsdatabase"; import { Aircraft, GroundUnit, Unit } from "../units/unit"; @@ -10,6 +9,11 @@ import { Panel } from "./panel"; const ROEs: string[] = ["Hold", "Return", "Designated", "Free"]; const reactionsToThreat: string[] = ["None", "Passive", "Evade"]; +const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"]; + +const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"]; +const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"]; +const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar off, no countermeasures)", "Attack (Radar only for targeting, countermeasures only if attacked/locked)", "Defend (Radar for searching, jammer if locked, countermeasures inside WEZ)", "Always on (Radar and jammer always on, countermeasures when hostile detected)"]; 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 }; @@ -52,23 +56,20 @@ export class UnitControlPanel extends Panel { /* Option buttons */ this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => { - var button = document.createElement("button"); - button.title = option; - button.value = option; - button.addEventListener("click", () => { getUnitsManager().selectedUnitsSetROE(button.title); }); - return button; + return this.#createOptionButton(option, ROEDescriptions[index], () => { getUnitsManager().selectedUnitsSetROE(option); }); }); this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string, index: number) => { - var button = document.createElement("button"); - button.title = option; - button.value = option; - button.addEventListener("click", () => { getUnitsManager().selectedUnitsSetReactionToThreat(button.title); }); - return button; + return this.#createOptionButton(option, reactionsToThreatDescriptions[index],() => { getUnitsManager().selectedUnitsSetReactionToThreat(option); }); + }); + + this.#optionButtons["emissionsCountermeasures"] = emissionsCountermeasures.map((option: string, index: number) => { + return this.#createOptionButton(option, emissionsCountermeasuresDescriptions[index],() => { getUnitsManager().selectedUnitsSetEmissionsCountermeasures(option); }); }); this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]); this.getElement().querySelector("#reaction-to-threat-buttons-container")?.append(...this.#optionButtons["reactionToThreat"]); + this.getElement().querySelector("#emissions-countermeasures-buttons-container")?.append(...this.#optionButtons["emissionsCountermeasures"]); this.#advancedSettingsDialog = document.querySelector("#advanced-settings-dialog"); @@ -122,7 +123,7 @@ export class UnitControlPanel extends Panel { })); } else { var el = document.createElement("div"); - el.innerText = "Too many units selected" + el.innerText = "Too many units selected"; this.getElement().querySelector("#selected-units-container")?.replaceChildren(el); } } @@ -298,4 +299,12 @@ export class UnitControlPanel extends Panel { this.#advancedSettingsDialog.classList.add("hide"); } + + #createOptionButton(option: string, title: string, callback: EventListenerOrEventListenerObject) { + var button = document.createElement("button"); + button.value = option; + button.title = title; + button.addEventListener("click", callback); + return button; + } } \ No newline at end of file diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 92c5b2d2..2452bff4 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -209,6 +209,12 @@ export function setReactionToThreat(ID: number, reactionToThreat: string) { POST(data, () => { }); } +export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string) { + var command = {"ID": ID, "emissionCountermeasure": emissionCountermeasure} + var data = {"setEmissionsCountermeasures": command} + POST(data, () => { }); +} + export function refuel(ID: number) { var command = { "ID": ID }; var data = { "refuel": command } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index f1350faa..2648fc72 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -1,7 +1,7 @@ import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map } 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, followUnit } from '../server/server'; +import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures } from '../server/server'; import { aircraftDatabase } from './aircraftdatabase'; import { groundUnitsDatabase } from './groundunitsdatabase'; import { field } from 'geomag' @@ -365,6 +365,11 @@ export class Unit extends Marker { setReactionToThreat(this.ID, reactionToThreat); } + setEmissionsCountermeasures(emissionCountermeasure: string) { + if (!this.getMissionData().flags.Human) + setEmissionsCountermeasures(this.ID, emissionCountermeasure); + } + setLeader(isLeader: boolean, wingmenIDs: number[] = []) { if (!this.getMissionData().flags.Human) setLeader(this.ID, isLeader, wingmenIDs); diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 62ac5db6..8684c13d 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -242,6 +242,15 @@ export class UnitsManager { this.#showActionMessage(selectedUnits, `reaction to threat set to ${reactionToThreat}`); } + selectedUnitsSetEmissionsCountermeasures(emissionCountermeasure: string) { + var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + for (let idx in selectedUnits) { + selectedUnits[idx].setEmissionsCountermeasures(emissionCountermeasure); + } + this.#showActionMessage(selectedUnits, `reaction to threat set to ${emissionCountermeasure}`); + } + + selectedUnitsAttackUnit(ID: number) { var selectedUnits = this.getSelectedUnits({excludeHumans: true}); for (let idx in selectedUnits) { diff --git a/client/views/dialogs.ejs b/client/views/dialogs.ejs index 442b89d9..1e667624 100644 --- a/client/views/dialogs.ejs +++ b/client/views/dialogs.ejs @@ -30,43 +30,46 @@
- +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+

Tasking

+
+
+
+
+

Radio options

+
+
diff --git a/client/views/unitcontrolpanel.ejs b/client/views/unitcontrolpanel.ejs index 8518555a..49f9701c 100644 --- a/client/views/unitcontrolpanel.ejs +++ b/client/views/unitcontrolpanel.ejs @@ -52,6 +52,13 @@
+
+

Emissions & countermeasures

+
+ +
+
+
From 03a8c93e9ecbf979ac28b85b7816b643c99103ef Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 17 May 2023 16:17:17 +0200 Subject: [PATCH 2/2] Added advanced unit controls Radio, ECM, countermeasures and prohibits --- client/public/stylesheets/layout.css | 2 +- client/public/stylesheets/olympus.css | 5 +- .../public/stylesheets/unitcontrolpanel.css | 41 +- client/src/@types/unit.d.ts | 31 +- client/src/panels/unitcontrolpanel.ts | 176 +++++---- client/src/server/server.ts | 13 +- client/src/units/unit.ts | 17 +- client/views/dialogs.ejs | 239 +++++++----- scripts/OlympusCommand.lua | 4 +- src/core/include/commands.h | 49 ++- src/core/include/unit.h | 80 ++-- src/core/src/airunit.cpp | 6 +- src/core/src/commands.cpp | 23 +- src/core/src/scheduler.cpp | 38 +- src/core/src/unit.cpp | 369 ++++++++++++------ src/core/src/unitsmanager.cpp | 11 +- 16 files changed, 721 insertions(+), 383 deletions(-) diff --git a/client/public/stylesheets/layout.css b/client/public/stylesheets/layout.css index a595382d..dfa23432 100644 --- a/client/public/stylesheets/layout.css +++ b/client/public/stylesheets/layout.css @@ -177,7 +177,7 @@ dl.ol-data-grid dd { } .ol-text-input input { - height: 40px; + height: 32px; border-radius: 5px; color: var(--background-offwhite); background-color: var(--background-grey); diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 64647448..941a97b2 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -163,7 +163,10 @@ form>div { align-items: center; background-color: var(--background-grey); border-radius: var(--border-radius-sm); - padding: 1em 30px 1em 20px; + height: 32px; + padding-right: 30px; + padding-left: 20px; + width: calc(100%); overflow: hidden; text-overflow: ellipsis; diff --git a/client/public/stylesheets/unitcontrolpanel.css b/client/public/stylesheets/unitcontrolpanel.css index 44bd5802..a0f7b507 100644 --- a/client/public/stylesheets/unitcontrolpanel.css +++ b/client/public/stylesheets/unitcontrolpanel.css @@ -31,7 +31,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { border-radius: var(--border-radius-lg); display: flex; font-size: 11px; - height: 30px; + height: 32px; padding: 8px 0; position: relative; width: 100%; @@ -103,6 +103,31 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { width: 400px; } +#advanced-settings-dialog:not([data-show-settings]) #general-settings { + display: none; +} + +#advanced-settings-dialog:not([data-show-tasking]) #tasking { + display: none; +} + +#advanced-settings-dialog:not([data-show-tanker]) #tanker-checkbox { + display: none; +} + +#advanced-settings-dialog:not([data-show-AWACS]) #AWACS-checkbox { + display: none; +} + +#advanced-settings-dialog:not([data-show-TACAN]) #TACAN-options { + display: none; +} + +#advanced-settings-dialog:not([data-show-radio]) #radio-options { + display: none; +} + + #advanced-settings-dialog>.ol-dialog-content { margin-top: 10px; margin-bottom: 10px; @@ -112,9 +137,15 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { row-gap: 10px; } -#advanced-settings-dialog>.ol-dialog-content>.ol-group { - justify-content: space-between; +#advanced-settings-dialog>.ol-dialog-content>div { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + row-gap: 10px; +} +#advanced-settings-dialog>.ol-dialog-content>div>.ol-group { + justify-content: space-between; } #advanced-settings-dialog h4 { @@ -126,12 +157,12 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { margin-bottom: 10px; } -#advanced-options-grid { +#general-settings-grid { display: grid; grid-template-columns: 1fr 1fr; row-gap: 10px; } -#advanced-options-grid>div { +#general-settings-grid>div { width: 49%; } \ No newline at end of file diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index d2f0d109..f3741e27 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -40,17 +40,15 @@ interface TaskData { targetAltitude: number; isTanker: boolean; isAWACS: boolean; - TACANChannel: number; - TACANXY: string; - TACANCallsign: string; - radioFrequency: number; - radioCallsign: number; - radioCallsignNumber: number; } interface OptionsData { ROE: string; reactionToThreat: string; + emissionsCountermeasures: string; + TACAN: TACAN; + radio: Radio; + generalSettings: GeneralSettings; } interface UnitData { @@ -61,3 +59,24 @@ interface UnitData { taskData: TaskData; optionsData: OptionsData; } + +interface TACAN { + isOn: boolean; + channel: number; + XY: string; + callsign: string; +} + +interface Radio { + frequency: number; + callsign: number; + callsignNumber: number; +} + +interface GeneralSettings { + prohibitJettison: boolean; + prohibitAA: boolean; + prohibitAG: boolean; + prohibitAfterburner: boolean; + prohibitAirWpn: boolean; +} \ No newline at end of file diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 23b9c3cb..a205669e 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -142,11 +142,15 @@ export class UnitControlPanel extends Panel { this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => { button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().reactionToThreat === button.value)) }); + + this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => { + button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().emissionsCountermeasures === button.value)) + }); } } } - // 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) + /* 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) { this.#expectedAltitude = -1; @@ -191,19 +195,15 @@ export class UnitControlPanel extends Panel { this.#airspeedSlider.setActive(targetSpeed != undefined); if (targetSpeed != undefined) { - targetSpeed *= 1.94384; - if (this.#updateCanSetSpeedSlider(targetSpeed)) { this.#airspeedSlider.setValue(targetSpeed); } - } this.#altitudeSlider.setActive(targetAltitude != undefined); if (targetAltitude != undefined) { targetAltitude /= 0.3048; - if (this.#updateCanSetAltitudeSlider(targetAltitude)) { this.#altitudeSlider.setValue(targetAltitude); } @@ -219,83 +219,125 @@ export class UnitControlPanel extends Panel { { if (units.length == 1) { + /* HTML Elements */ + const unitNameEl = this.#advancedSettingsDialog.querySelector("#unit-name") as HTMLElement; + const prohibitJettisonCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-jettison-checkbox")?.querySelector("input") as HTMLInputElement; + const prohibitAfterburnerCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-afterburner-checkbox")?.querySelector("input") as HTMLInputElement; + const prohibitAACheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-AA-checkbox")?.querySelector("input") as HTMLInputElement; + const prohibitAGCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-AG-checkbox")?.querySelector("input") as HTMLInputElement; + const prohibitAirWpnCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-air-wpn-checkbox")?.querySelector("input") as HTMLInputElement; + const tankerCheckbox = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input") as HTMLInputElement; + const AWACSCheckbox = this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input") as HTMLInputElement; + const TACANCheckbox = this.#advancedSettingsDialog.querySelector("#TACAN-checkbox")?.querySelector("input") as HTMLInputElement; + const TACANChannelInput = this.#advancedSettingsDialog.querySelector("#TACAN-channel")?.querySelector("input") as HTMLInputElement; + const TACANCallsignInput = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input") as HTMLInputElement; + const radioMhzInput = this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input") as HTMLInputElement; + const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement; + const unit = units[0]; - (this.#advancedSettingsDialog.querySelector("#unit-name")).innerText = unit.getBaseData().unitName; + const roles = aircraftDatabase.getByName(unit.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles}) + const tanker = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker"); + const AWACS = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS"); + const radioMHz = Math.floor(unit.getOptionsData().radio.frequency / 1000000); + const radioDecimals = (unit.getOptionsData().radio.frequency / 1000000 - radioMHz) * 1000; - if (getUnitsManager().getSelectedUnits().length == 1) - { - var radioMHz = Math.floor(unit.getTaskData().radioFrequency / 1000000); - var radioDecimals = (unit.getTaskData().radioFrequency / 1000000 - radioMHz) * 1000; + /* Activate the correct options depending on unit type */ + this.#advancedSettingsDialog.toggleAttribute("data-show-settings", !tanker && !AWACS); + this.#advancedSettingsDialog.toggleAttribute("data-show-tasking", tanker || AWACS); + this.#advancedSettingsDialog.toggleAttribute("data-show-tanker", tanker); + this.#advancedSettingsDialog.toggleAttribute("data-show-AWACS", AWACS); + this.#advancedSettingsDialog.toggleAttribute("data-show-TACAN", tanker); + this.#advancedSettingsDialog.toggleAttribute("data-show-radio", tanker || AWACS); - // Default values for "normal" units + /* Set common properties */ + // Name + unitNameEl.innerText = unit.getBaseData().unitName; + + // General settings + prohibitJettisonCheckbox.checked = unit.getOptionsData().generalSettings.prohibitJettison; + prohibitAfterburnerCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAfterburner; + prohibitAACheckbox.checked = unit.getOptionsData().generalSettings.prohibitAA; + prohibitAGCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAG; + prohibitAirWpnCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAirWpn; + + // Tasking + tankerCheckbox.checked = unit.getTaskData().isTanker; + AWACSCheckbox.checked = unit.getTaskData().isAWACS; + + // TACAN + TACANCheckbox.checked = unit.getOptionsData().TACAN.isOn; + TACANChannelInput.value = String(unit.getOptionsData().TACAN.channel); + TACANCallsignInput.value = String(unit.getOptionsData().TACAN.callsign); + this.#TACANXYDropdown.setValue(unit.getOptionsData().TACAN.XY); + + // Radio + radioMhzInput.value = String(radioMHz); + radioCallsignNumberInput.value = String(unit.getOptionsData().radio.callsignNumber); + this.#radioDecimalsDropdown.setValue("." + radioDecimals); + + if (tanker) /* Set tanker specific options */ + this.#radioCallsignDropdown.setOptions(["Texaco", "Arco", "Shell"]); + else if (AWACS) /* Set AWACS specific options */ + this.#radioCallsignDropdown.setOptions(["Overlord", "Magic", "Wizard", "Focus", "Darkstar"]); + else this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]); - this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1); - // 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 - 1)) - 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"); - this.#radioCallsignDropdown.setOptions(["Texaco", "Arco", "Shell"]); - this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1); - } - else { - 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"]); - this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1); - } else { - this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.add("hide"); - } - } + // This must be done after setting the options + if (!this.#radioCallsignDropdown.selectValue(unit.getOptionsData().radio.callsign - 1)) // Ensure the selected value is in the acceptable range + this.#radioCallsignDropdown.selectValue(0); } } #applyAdvancedSettings() { - const isTanker = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")?.checked? true: false; - const isAWACS = this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input")?.checked? true: false; + /* HTML Elements */ + const prohibitJettisonCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-jettison-checkbox")?.querySelector("input") as HTMLInputElement; + const prohibitAfterburnerCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-afterburner-checkbox")?.querySelector("input") as HTMLInputElement; + const prohibitAACheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-AA-checkbox")?.querySelector("input") as HTMLInputElement; + const prohibitAGCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-AG-checkbox")?.querySelector("input") as HTMLInputElement; + const prohibitAirWpnCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-air-wpn-checkbox")?.querySelector("input") as HTMLInputElement; + const tankerCheckbox = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input") as HTMLInputElement; + const AWACSCheckbox = this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.querySelector("input") as HTMLInputElement; + const TACANCheckbox = this.#advancedSettingsDialog.querySelector("#TACAN-checkbox")?.querySelector("input") as HTMLInputElement; + const TACANChannelInput = this.#advancedSettingsDialog.querySelector("#TACAN-channel")?.querySelector("input") as HTMLInputElement; + const TACANCallsignInput = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input") as HTMLInputElement; + const radioMhzInput = this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input") as HTMLInputElement; + const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement; - 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); + /* Tasking */ + const isTanker = tankerCheckbox.checked? true: false; + const isAWACS = AWACSCheckbox.checked? true: false; + + /* TACAN */ + const TACAN: TACAN = { + isOn: TACANCheckbox.checked? true: false, + channel: Number(TACANChannelInput.value), + XY: this.#TACANXYDropdown.getValue(), + callsign: TACANCallsignInput.value as string + } + + /* Radio */ + const radioMHz = Number(radioMhzInput.value); const radioDecimals = this.#radioDecimalsDropdown.getValue(); - const radioCallsign = this.#radioCallsignDropdown.getIndex() + 1; - const radioCallsignNumber = Number(this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input")?.value); - - var radioFrequency = (radioMHz * 1000 + Number(radioDecimals.substring(1))) * 1000; + const radio: Radio = { + frequency: (radioMHz * 1000 + Number(radioDecimals.substring(1))) * 1000, + callsign: this.#radioCallsignDropdown.getIndex() + 1, + callsignNumber: Number(radioCallsignNumberInput.value) + } + /* General settings */ + const generalSettings: GeneralSettings = { + prohibitJettison: prohibitJettisonCheckbox.checked? true: false, + prohibitAfterburner: prohibitAfterburnerCheckbox.checked? true: false, + prohibitAA: prohibitAACheckbox.checked? true: false, + prohibitAG: prohibitAGCheckbox.checked? true: false, + prohibitAirWpn: prohibitAirWpnCheckbox.checked? true: false + } + + /* Send command and close */ var units = getUnitsManager().getSelectedUnits(); if (units.length > 0) - units[0].setAdvancedOptions(isTanker, isAWACS, TACANChannel, TACANXY, TACANCallsign, radioFrequency, radioCallsign, radioCallsignNumber); + units[0].setAdvancedOptions(isTanker, isAWACS, TACAN, radio, generalSettings); this.#advancedSettingsDialog.classList.add("hide"); } diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 2452bff4..d9e4e2f3 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -210,7 +210,7 @@ export function setReactionToThreat(ID: number, reactionToThreat: string) { } export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string) { - var command = {"ID": ID, "emissionCountermeasure": emissionCountermeasure} + var command = {"ID": ID, "emissionsCountermeasures": emissionCountermeasure} var data = {"setEmissionsCountermeasures": command} POST(data, () => { }); } @@ -221,17 +221,14 @@ 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, radioCallsignNumber: number) +export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) { var command = { "ID": ID, "isTanker": isTanker, "isAWACS": isAWACS, - "TACANChannel": TACANChannel, - "TACANXY": TACANXY, - "TACANCallsign": TACANCallsign, - "radioFrequency": radioFrequency, - "radioCallsign": radioCallsign, - "radioCallsignNumber": radioCallsignNumber + "TACAN": TACAN, + "radio": radio, + "generalSettings": generalSettings }; var data = { "setAdvancedOptions": command }; diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 2648fc72..4de97930 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -4,7 +4,6 @@ import { rad2deg } from '../other/utils'; import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures } from '../server/server'; import { aircraftDatabase } from './aircraftdatabase'; import { groundUnitsDatabase } from './groundunitsdatabase'; -import { field } from 'geomag' var pathIcon = new Icon({ iconUrl: 'images/marker-icon.png', @@ -43,23 +42,21 @@ export class Unit extends Marker { leaderID: 0 }, taskData: { - currentState: "IDLE", + currentState: "NONE", currentTask: "", activePath: {}, targetSpeed: 0, targetAltitude: 0, isTanker: false, isAWACS: false, - TACANChannel: 0, - TACANXY: "X", - TACANCallsign: "", - radioFrequency: 0, - radioCallsign: 0, - radioCallsignNumber: 0 }, optionsData: { ROE: "", reactionToThreat: "", + emissionsCountermeasures: "", + TACAN: { isOn: false, channel: 0, XY: "X", callsign: "" }, + radio: { frequency: 0, callsign: 1, callsignNumber: 1}, + generalSettings: { prohibitJettison: false, prohibitAA: false, prohibitAG: false, prohibitAfterburner: false, prohibitAirWpn: false} } }; @@ -385,9 +382,9 @@ export class Unit extends Marker { refuel(this.ID); } - setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACANChannel: number, TACANXY: string, TACANcallsign: string, radioFrequency: number, radioCallsign: number, radioCallsignNumber: number) { + setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) { if (!this.getMissionData().flags.Human) - setAdvacedOptions(this.ID, isTanker, isAWACS, TACANChannel, TACANXY, TACANcallsign, radioFrequency, radioCallsign, radioCallsignNumber); + setAdvacedOptions(this.ID, isTanker, isAWACS, TACAN, radio, generalSettings); } /***********************************************/ diff --git a/client/views/dialogs.ejs b/client/views/dialogs.ejs index 1e667624..2421f78a 100644 --- a/client/views/dialogs.ejs +++ b/client/views/dialogs.ejs @@ -30,116 +30,147 @@
-
-

General settings

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

Tasking

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

Radio options

-
-
-
- - + +
-
- +

General settings

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

Tasking

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

TACAN options

+
+
+
+ +
+ +
+ + +
+
+ +
+ +
+
X
+
+
+
+ +
+ +
+
+
+
+ + +
+
+

Radio options

+
+
+ +
+ + +
+
+ +
+ +
+
.000
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+ + + +
+ +
diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index ad5a0d46..80346a9c 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,6 +1,6 @@ local version = "v0.2.1-alpha" -local debug = false +local debug = true Olympus.unitCounter = 1 Olympus.payloadRegistry = {} @@ -427,7 +427,7 @@ function Olympus.setCommand(ID, command) end function Olympus.setOption(ID, optionID, optionValue) - Olympus.debug("Olympus.setOption " .. ID .. " " .. optionID .. " " .. optionValue, 2) + Olympus.debug("Olympus.setOption " .. ID .. " " .. optionID .. " " .. tostring(optionValue), 2) local unit = Olympus.getUnitByID(ID) if unit then unit:getGroup():getController():setOption(optionID, optionValue) diff --git a/src/core/include/commands.h b/src/core/include/commands.h index 17383cf1..7752eea8 100644 --- a/src/core/include/commands.h +++ b/src/core/include/commands.h @@ -2,6 +2,7 @@ #include "framework.h" #include "luatools.h" #include "utils.h" +#include "logger.h" namespace CommandPriority { enum CommandPriorities { LOW, MEDIUM, HIGH }; @@ -24,6 +25,7 @@ namespace SetCommandType { PROHIBIT_AG = 17, MISSILE_ATTACK = 18, PROHIBIT_WP_PASS_REPORT = 19, + ENGAGE_AIR_WEAPONS = 20, OPTION_RADIO_USAGE_CONTACT = 21, OPTION_RADIO_USAGE_ENGAGE = 22, OPTION_RADIO_USAGE_KILL = 23, @@ -43,7 +45,7 @@ namespace ROE { } namespace ReactionToThreat { - enum ReactionToThreats { + enum ReactionsToThreat { NO_REACTION = 0, PASSIVE_DEFENCE = 1, EVADE_FIRE = 2, @@ -52,6 +54,35 @@ namespace ReactionToThreat { }; } +namespace RadarUse { + enum RadarUses { + NEVER = 0, + FOR_ATTACK_ONLY = 1, + FOR_SEARCH_IF_REQUIRED = 2, + FOR_CONTINUOUS_SEARCH = 3 + }; +} + +namespace FlareUse { + enum FlareUses { + NEVER = 0, + AGAINST_FIRED_MISSILE = 1, + WHEN_FLYING_IN_SAM_WEZ = 2, + WHEN_FLYING_NEAR_ENEMIES = 3 + }; +} + +namespace ECMUse { + enum ECMUses { + NEVER_USE = 0, + USE_IF_ONLY_LOCK_BY_RADAR = 1, + USE_IF_DETECTED_LOCK_BY_RADAR = 2, + ALWAYS_USE = 3 + }; +} + + + /* Base command class */ class Command { @@ -243,7 +274,19 @@ public: SetOption(int ID, int optionID, int optionValue) : ID(ID), optionID(optionID), - optionValue(optionValue) + optionValue(optionValue), + optionBool(false), + isBoolean(false) + { + priority = CommandPriority::HIGH; + }; + + SetOption(int ID, int optionID, bool optionBool) : + ID(ID), + optionID(optionID), + optionValue(0), + optionBool(optionBool), + isBoolean(true) { priority = CommandPriority::HIGH; }; @@ -254,4 +297,6 @@ private: const int ID; const int optionID; const int optionValue; + const bool optionBool; + const bool isBoolean; }; \ No newline at end of file diff --git a/src/core/include/unit.h b/src/core/include/unit.h index e54401ff..37e456f3 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -9,20 +9,44 @@ namespace State { enum States { + NONE = 0, IDLE, REACH_DESTINATION, ATTACK, - WINGMAN, FOLLOW, LAND, REFUEL, AWACS, - EWR, - TANKER, - RUN_AWAY + TANKER }; }; +namespace Options { + struct TACAN + { + bool isOn = false; + int channel = 40; + wstring XY = L"X"; + wstring callsign = L"TKR"; + }; + + struct Radio + { + int frequency = 124000000; // MHz + int callsign = 1; + int callsignNumber = 1; + }; + + struct GeneralSettings + { + bool prohibitJettison = false; + bool prohibitAA = false; + bool prohibitAG = false; + bool prohibitAfterburner = false; + bool prohibitAirWpn = false; + }; +} + class Unit { public: @@ -30,6 +54,7 @@ public: ~Unit(); /********** Public methods **********/ + void initialize(json::value json); int getID() { return ID; } void updateExportData(json::value json); void updateMissionData(json::value json); @@ -98,15 +123,7 @@ public: void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));} void setIsTanker(bool newIsTanker); void setIsAWACS(bool newIsAWACS); - void setTACANChannel(int newTACANChannel); - void setTACANXY(wstring newTACANXY); - void setTACANCallsign(wstring newTACANCallsign); - void setTACAN(); - void setEPLRS(bool state); - 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; }; @@ -115,18 +132,22 @@ public: int getTargetID() { return targetID; } bool getIsTanker() { return isTanker; } bool getIsAWACS() { return isAWACS; } - int getTACANChannel() { return TACANChannel; } - wstring getTACANXY() { return TACANXY; } - wstring getTACANCallsign() { return TACANCallsign; } - int getRadioFrequency() { return radioFrequency; } - int getRadioCallsign() { return radioCallsign; } - int getRadioCallsignNumber() { return radioCallsignNumber; } /********** Options data **********/ void setROE(wstring newROE); void setReactionToThreat(wstring newReactionToThreat); + void setEmissionsCountermeasures(wstring newEmissionsCountermeasures); + void setTACAN(Options::TACAN newTACAN); + void setRadio(Options::Radio newradio); + void setGeneralSettings(Options::GeneralSettings newGeneralSettings); + void setEPLRS(bool newEPLRS); wstring getROE() { return ROE; } - wstring getReactionToThreat() {return reactionToThreat;} + wstring getReactionToThreat() { return reactionToThreat; } + wstring getEmissionsCountermeasures() { return emissionsCountermeasures; }; + Options::TACAN getTACAN() { return TACAN; } + Options::Radio getRadio() { return radio; } + Options::GeneralSettings getGeneralSettings() { return generalSettings; } + bool getEPLRS() { return EPLRS; } /********** Control functions **********/ void landAt(Coords loc); @@ -179,19 +200,18 @@ protected: int targetID = NULL; bool isTanker = false; bool isAWACS = false; - int TACANChannel = 40; - wstring TACANXY = L"X"; - wstring TACANCallsign = L"TKR"; - int radioFrequency = 260000000; // MHz - int radioCallsign = 1; - int radioCallsignNumber = 1; - + /********** Options data **********/ - wstring ROE = L""; - wstring reactionToThreat = L""; + wstring ROE = L"Designated"; + wstring reactionToThreat = L"Evade"; + wstring emissionsCountermeasures = L"Defend"; + Options::TACAN TACAN; + Options::Radio radio; + Options::GeneralSettings generalSettings; + bool EPLRS = false; /********** State machine **********/ - int state = State::IDLE; + int state = State::NONE; /********** Other **********/ Coords oldPosition = Coords(0); // Used to approximate speed diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index 1c313d19..f40f65be 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -185,7 +185,7 @@ void AirUnit::AIloop() } Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); scheduler->appendCommand(command); - hasTask = true; + setHasTask(true); } break; } @@ -290,7 +290,7 @@ void AirUnit::AIloop() << "}"; Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); scheduler->appendCommand(command); - hasTask = true; + setHasTask(true); } } break; @@ -306,7 +306,7 @@ void AirUnit::AIloop() << "}"; Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); scheduler->appendCommand(command); - hasTask = true; + setHasTask(true); } else { setState(State::IDLE); diff --git a/src/core/src/commands.cpp b/src/core/src/commands.cpp index 68f633da..662e62ab 100644 --- a/src/core/src/commands.cpp +++ b/src/core/src/commands.cpp @@ -62,7 +62,7 @@ wstring SpawnAircraft::getString(lua_State* L) optionsSS.precision(10); optionsSS << "{" << "payloadName = \"" << payloadName << "\", " - << "airbaseName = \"" << airbaseName << "\"," + << "airbaseName = \"" << airbaseName << "\", " << "}"; std::wostringstream commandSS; @@ -71,7 +71,7 @@ wstring SpawnAircraft::getString(lua_State* L) << "\"" << coalition << "\"" << ", " << "\"" << unitType << "\"" << ", " << location.lat << ", " - << location.lng << "," + << location.lng << ", " << optionsSS.str(); return commandSS.str(); } @@ -113,7 +113,7 @@ wstring SetTask::getString(lua_State* L) std::wostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.setTask, " - << ID << "," + << ID << ", " << task; return commandSS.str(); @@ -136,7 +136,7 @@ wstring SetCommand::getString(lua_State* L) std::wostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.setCommand, " - << ID << "," + << ID << ", " << command; return commandSS.str(); @@ -147,10 +147,17 @@ wstring SetOption::getString(lua_State* L) { std::wostringstream commandSS; commandSS.precision(10); - commandSS << "Olympus.setOption, " - << ID << "," - << optionID << "," - << optionValue; + if (!isBoolean) { + commandSS << "Olympus.setOption, " + << ID << ", " + << optionID << ", " + << optionValue; + } else { + commandSS << "Olympus.setOption, " + << ID << ", " + << optionID << ", " + << (optionBool? "true": "false"); + } return commandSS.str(); } \ No newline at end of file diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index 8bf448e9..ad46db11 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -222,6 +222,13 @@ void Scheduler::handleRequest(wstring key, json::value value) wstring reactionToThreat = value[L"reactionToThreat"].as_string(); unit->setReactionToThreat(reactionToThreat); } + else if (key.compare(L"setEmissionsCountermeasures") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + wstring emissionsCountermeasures = value[L"emissionsCountermeasures"].as_string(); + unit->setEmissionsCountermeasures(emissionsCountermeasures); + } else if (key.compare(L"landAt") == 0) { int ID = value[L"ID"].as_integer(); @@ -248,18 +255,33 @@ void Scheduler::handleRequest(wstring key, json::value value) Unit* unit = unitsManager->getUnit(ID); if (unit != nullptr) { + /* Advanced tasking */ unit->setIsTanker(value[L"isTanker"].as_bool()); unit->setIsAWACS(value[L"isAWACS"].as_bool()); - 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(); + /* TACAN Options */ + auto TACAN = value[L"TACAN"]; + unit->setTACAN({ TACAN[L"isOn"].as_bool(), + TACAN[L"channel"].as_number().to_int32(), + TACAN[L"XY"].as_string(), + TACAN[L"callsign"].as_string() + }); - 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(); + /* Radio Options */ + auto radio = value[L"radio"]; + unit->setRadio({ radio[L"frequency"].as_number().to_int32(), + radio[L"callsign"].as_number().to_int32(), + radio[L"callsignNumber"].as_number().to_int32() + }); + + /* General Settings */ + auto generalSettings = value[L"generalSettings"]; + unit->setGeneralSettings({ generalSettings[L"prohibitJettison"].as_bool(), + generalSettings[L"prohibitAA"].as_bool(), + generalSettings[L"prohibitAG"].as_bool(), + generalSettings[L"prohibitAfterburner"].as_bool(), + generalSettings[L"prohibitAirWpn"].as_bool(), + }); unit->resetActiveDestination(); } diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index e68e1ccb..b32dc785 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -15,22 +15,27 @@ using namespace GeographicLib; extern Scheduler* scheduler; extern UnitsManager* unitsManager; +// TODO: Make dedicated file +bool operator==(const Options::TACAN& lhs, const Options::TACAN& rhs) +{ + return lhs.isOn == rhs.isOn && lhs.channel == rhs.channel && lhs.XY == rhs.XY && lhs.callsign == rhs.callsign; +} + +bool operator==(const Options::Radio& lhs, const Options::Radio& rhs) +{ + return lhs.frequency == rhs.frequency && lhs.callsign == rhs.callsign && lhs.callsignNumber == rhs.callsignNumber; +} + +bool operator==(const Options::GeneralSettings& lhs, const Options::GeneralSettings& rhs) +{ + return lhs.prohibitAA == rhs.prohibitAA && lhs.prohibitAfterburner == rhs.prohibitAfterburner && lhs.prohibitAG == rhs.prohibitAG && + lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison; +} + Unit::Unit(json::value json, int ID) : ID(ID) { log("Creating unit with ID: " + to_string(ID)); - addMeasure(L"currentState", json::value(L"Idle")); - - addMeasure(L"TACANChannel", json::value(TACANChannel)); - addMeasure(L"TACANXY", json::value(TACANXY)); - addMeasure(L"TACANCallsign", json::value(TACANCallsign)); - - addMeasure(L"radioFrequency", json::value(radioFrequency)); - addMeasure(L"radioCallsign", json::value(radioCallsign)); - addMeasure(L"radioCallsignNumber", json::value(radioCallsignNumber)); - - addMeasure(L"ROE", json::value(L"Designated")); - addMeasure(L"reactionToThreat", json::value(L"Evade")); } Unit::~Unit() @@ -38,6 +43,25 @@ Unit::~Unit() } +void Unit::initialize(json::value json) +{ + updateExportData(json); + + if (getAI()) { + /* Set the default IDLE state */ + setState(State::IDLE); + + /* Set the default options (these are all defaults so will only affect the export data, no DCS command will be sent) */ + setROE(L"Designated"); + setReactionToThreat(L"Evade"); + setEmissionsCountermeasures(L"Defend"); + setTACAN(TACAN); + setRadio(radio); + setEPLRS(EPLRS); + setGeneralSettings(generalSettings); + } +} + void Unit::addMeasure(wstring key, json::value value) { milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); @@ -155,7 +179,7 @@ json::value Unit::getData(long long time) /********** Task data **********/ json[L"taskData"] = json::value::object(); - 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" }) + for (auto key : { L"currentState", L"currentTask", L"targetSpeed", L"targetAltitude", L"activePath", L"isTanker", L"isAWACS" }) { if (measures.find(key) != measures.end() && measures[key]->getTime() > time) json[L"taskData"][key] = measures[key]->getValue(); @@ -165,7 +189,7 @@ json::value Unit::getData(long long time) /********** Options data **********/ json[L"optionsData"] = json::value::object(); - for (auto key : { L"ROE", L"reactionToThreat" }) + for (auto key : { L"ROE", L"reactionToThreat", L"emissionsCountermeasures", L"TACAN", L"radio", L"generalSettings"}) { if (measures.find(key) != measures.end() && measures[key]->getTime() > time) json[L"optionsData"][key] = measures[key]->getValue(); @@ -309,43 +333,102 @@ void Unit::setFormationOffset(Offset newFormationOffset) } void Unit::setROE(wstring newROE) { - ROE = newROE; - int ROEEnum; - if (newROE.compare(L"Free") == 0) - ROEEnum = ROE::WEAPON_FREE; - else if (newROE.compare(L"Designated free") == 0) - ROEEnum = ROE::OPEN_FIRE_WEAPON_FREE; - else if (newROE.compare(L"Designated") == 0) - ROEEnum = ROE::OPEN_FIRE; - else if (newROE.compare(L"Return") == 0) - ROEEnum = ROE::RETURN_FIRE; - else if (newROE.compare(L"Hold") == 0) - ROEEnum = ROE::WEAPON_HOLD; - else - return; - Command* command = dynamic_cast(new SetOption(ID, SetCommandType::ROE, ROEEnum)); - scheduler->appendCommand(command); addMeasure(L"ROE", json::value(newROE)); + + if (ROE != newROE) { + ROE = newROE; + + int ROEEnum; + if (ROE.compare(L"Free") == 0) + ROEEnum = ROE::WEAPON_FREE; + else if (ROE.compare(L"Designated free") == 0) + ROEEnum = ROE::OPEN_FIRE_WEAPON_FREE; + else if (ROE.compare(L"Designated") == 0) + ROEEnum = ROE::OPEN_FIRE; + else if (ROE.compare(L"Return") == 0) + ROEEnum = ROE::RETURN_FIRE; + else if (ROE.compare(L"Hold") == 0) + ROEEnum = ROE::WEAPON_HOLD; + else + return; + + Command* command = dynamic_cast(new SetOption(ID, SetCommandType::ROE, ROEEnum)); + scheduler->appendCommand(command); + } } void Unit::setReactionToThreat(wstring newReactionToThreat) { - reactionToThreat = newReactionToThreat; - int reactionToThreatEnum; - if (newReactionToThreat.compare(L"None") == 0) - reactionToThreatEnum = ReactionToThreat::NO_REACTION; - else if (newReactionToThreat.compare(L"Passive") == 0) - reactionToThreatEnum = ReactionToThreat::PASSIVE_DEFENCE; - else if (newReactionToThreat.compare(L"Evade") == 0) - reactionToThreatEnum = ReactionToThreat::EVADE_FIRE; - else if (newReactionToThreat.compare(L"Escape") == 0) - reactionToThreatEnum = ReactionToThreat::BYPASS_AND_ESCAPE; - else if (newReactionToThreat.compare(L"Abort") == 0) - reactionToThreatEnum = ReactionToThreat::ALLOW_ABORT_MISSION; - else - return; - Command* command = dynamic_cast(new SetOption(ID, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum)); - scheduler->appendCommand(command); addMeasure(L"reactionToThreat", json::value(newReactionToThreat)); + + if (reactionToThreat != newReactionToThreat) { + reactionToThreat = newReactionToThreat; + + int reactionToThreatEnum; + if (reactionToThreat.compare(L"None") == 0) + reactionToThreatEnum = ReactionToThreat::NO_REACTION; + else if (reactionToThreat.compare(L"Passive") == 0) + reactionToThreatEnum = ReactionToThreat::PASSIVE_DEFENCE; + else if (reactionToThreat.compare(L"Evade") == 0) + reactionToThreatEnum = ReactionToThreat::EVADE_FIRE; + else if (reactionToThreat.compare(L"Escape") == 0) + reactionToThreatEnum = ReactionToThreat::BYPASS_AND_ESCAPE; + else if (reactionToThreat.compare(L"Abort") == 0) + reactionToThreatEnum = ReactionToThreat::ALLOW_ABORT_MISSION; + else + return; + + Command* command = dynamic_cast(new SetOption(ID, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum)); + scheduler->appendCommand(command); + } +} + +void Unit::setEmissionsCountermeasures(wstring newEmissionsCountermeasures) { + addMeasure(L"emissionsCountermeasures", json::value(newEmissionsCountermeasures)); + + if (emissionsCountermeasures != newEmissionsCountermeasures) { + emissionsCountermeasures = newEmissionsCountermeasures; + + int radarEnum; + int flareEnum; + int ECMEnum; + if (emissionsCountermeasures.compare(L"Silent") == 0) + { + radarEnum = RadarUse::NEVER; + flareEnum = FlareUse::NEVER; + ECMEnum = ECMUse::NEVER_USE; + } + else if (emissionsCountermeasures.compare(L"Attack") == 0) + { + radarEnum = RadarUse::FOR_ATTACK_ONLY; + flareEnum = FlareUse::AGAINST_FIRED_MISSILE; + ECMEnum = ECMUse::USE_IF_ONLY_LOCK_BY_RADAR; + } + else if (emissionsCountermeasures.compare(L"Defend") == 0) + { + radarEnum = RadarUse::FOR_SEARCH_IF_REQUIRED; + flareEnum = FlareUse::WHEN_FLYING_IN_SAM_WEZ; + ECMEnum = ECMUse::USE_IF_DETECTED_LOCK_BY_RADAR; + } + else if (emissionsCountermeasures.compare(L"Free") == 0) + { + radarEnum = RadarUse::FOR_CONTINUOUS_SEARCH; + flareEnum = FlareUse::WHEN_FLYING_NEAR_ENEMIES; + ECMEnum = ECMUse::ALWAYS_USE; + } + else + return; + + Command* command; + + command = dynamic_cast(new SetOption(ID, SetCommandType::RADAR_USING, radarEnum)); + scheduler->appendCommand(command); + + command = dynamic_cast(new SetOption(ID, SetCommandType::FLARE_USING, flareEnum)); + scheduler->appendCommand(command); + + command = dynamic_cast(new SetOption(ID, SetCommandType::ECM_USING, ECMEnum)); + scheduler->appendCommand(command); + } } void Unit::landAt(Coords loc) { @@ -364,93 +447,131 @@ void Unit::setIsAWACS(bool newIsAWACS) { isAWACS = newIsAWACS; resetTask(); addMeasure(L"isAWACS", json::value(newIsAWACS)); - setEPLRS(true); + setEPLRS(isAWACS); } -void Unit::setTACANChannel(int newTACANChannel) { - TACANChannel = newTACANChannel; - addMeasure(L"TACANChannel", json::value(newTACANChannel)); -} +void Unit::setTACAN(Options::TACAN newTACAN) { + auto json = json::value(); + json[L"isOn"] = json::value(newTACAN.isOn); + json[L"channel"] = json::value(newTACAN.channel); + json[L"XY"] = json::value(newTACAN.XY); + json[L"callsign"] = json::value(newTACAN.callsign); + addMeasure(L"TACAN", json); -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::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::setEPLRS(bool state) -{ - std::wostringstream commandSS; - commandSS << "{" - << "id = 'EPLRS'," - << "params = {" - << "value = " << (state? "true": "false") << ", " - << "}" - << "}"; - Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); - scheduler->appendCommand(command); -} - -void Unit::setTACAN() -{ - std::wostringstream commandSS; - commandSS << "{" - << "id = 'ActivateBeacon'," - << "params = {" - << "type = " << ((TACANXY.compare(L"X") == 0)? 4: 5) << "," - << "system = 3," - << "name = \"Olympus_TACAN\"," - << "callsign = \"" << TACANCallsign << "\", " - << "frequency = " << TACANChannelToFrequency(TACANChannel, TACANXY) << "," - << "}" - << "}"; - Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); - scheduler->appendCommand(command); -} - -void Unit::setRadio() -{ + if (TACAN != newTACAN) { - std::wostringstream commandSS; - commandSS << "{" - << "id = 'SetFrequency'," - << "params = {" - << "modulation = 0," // TODO Allow selection - << "frequency = " << radioFrequency << "," - << "}" - << "}"; - Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); - scheduler->appendCommand(command); + TACAN = newTACAN; + if (TACAN.isOn) { + std::wostringstream commandSS; + commandSS << "{" + << "id = 'ActivateBeacon'," + << "params = {" + << "type = " << ((TACAN.XY.compare(L"X") == 0) ? 4 : 5) << "," + << "system = 3," + << "name = \"Olympus_TACAN\"," + << "callsign = \"" << TACAN.callsign << "\", " + << "frequency = " << TACANChannelToFrequency(TACAN.channel, TACAN.XY) << "," + << "}" + << "}"; + Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); + scheduler->appendCommand(command); + } + else { + std::wostringstream commandSS; + commandSS << "{" + << "id = 'DeactivateBeacon'," + << "params = {" + << "}" + << "}"; + Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); + scheduler->appendCommand(command); + } } +} +void Unit::setRadio(Options::Radio newRadio) { + + auto json = json::value(); + json[L"frequency"] = json::value(newRadio.frequency); + json[L"callsign"] = json::value(newRadio.callsign); + json[L"callsignNumber"] = json::value(newRadio.callsignNumber); + addMeasure(L"radio", json); + + if (radio != newRadio) { + radio = newRadio; + std::wostringstream commandSS; + Command* command; + commandSS << "{" - << "id = 'SetCallsign'," - << "params = {" - << "callname = " << radioCallsign << "," - << "number = " << radioCallsignNumber << "," - << "}" - << "}"; - Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); + << "id = 'SetFrequency'," + << "params = {" + << "modulation = 0," // TODO Allow selection + << "frequency = " << radio.frequency << "," + << "}" + << "}"; + command = dynamic_cast(new SetCommand(ID, commandSS.str())); + scheduler->appendCommand(command); + + // Clear the stringstream + commandSS.str(wstring()); + + commandSS << "{" + << "id = 'SetCallsign'," + << "params = {" + << "callname = " << radio.callsign << "," + << "number = " << radio.callsignNumber << "," + << "}" + << "}"; + command = dynamic_cast(new SetCommand(ID, commandSS.str())); + scheduler->appendCommand(command); + } +} + +void Unit::setEPLRS(bool newEPLRS) +{ + //addMeasure(L"EPLRS", json::value(newEPLRS)); + // + //if (EPLRS != newEPLRS) { + // EPLRS = newEPLRS; + // + // std::wostringstream commandSS; + // commandSS << "{" + // << "id = 'EPLRS'," + // << "params = {" + // << "value = " << (EPLRS ? "true" : "false") << ", " + // << "}" + // << "}"; + // Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); + // scheduler->appendCommand(command); + //} +} + +void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings) { + + auto json = json::value(); + json[L"prohibitJettison"] = json::value(newGeneralSettings.prohibitJettison); + json[L"prohibitAA"] = json::value(newGeneralSettings.prohibitAA); + json[L"prohibitAG"] = json::value(newGeneralSettings.prohibitAG); + json[L"prohibitAfterburner"] = json::value(newGeneralSettings.prohibitAfterburner); + json[L"prohibitAirWpn"] = json::value(newGeneralSettings.prohibitAirWpn); + addMeasure(L"generalSettings", json); + + if (generalSettings != newGeneralSettings) + { + generalSettings = newGeneralSettings; + + Command* command; + command = dynamic_cast(new SetOption(ID, SetCommandType::PROHIBIT_AA, generalSettings.prohibitAA)); + scheduler->appendCommand(command); + command = dynamic_cast(new SetOption(ID, SetCommandType::PROHIBIT_AG, generalSettings.prohibitAG)); + scheduler->appendCommand(command); + command = dynamic_cast(new SetOption(ID, SetCommandType::PROHIBIT_JETT, generalSettings.prohibitJettison)); + scheduler->appendCommand(command); + command = dynamic_cast(new SetOption(ID, SetCommandType::PROHIBIT_AB, generalSettings.prohibitAfterburner)); + scheduler->appendCommand(command); + command = dynamic_cast(new SetOption(ID, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn)); scheduler->appendCommand(command); } } diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index bd05a61c..00b1b8cd 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -64,11 +64,14 @@ void UnitsManager::updateExportData(lua_State* L) units[ID] = dynamic_cast(new Bomb(p.second, ID)); } } + /* Initialize the unit if creation was successfull */ + if (units.count(ID) != 0) + units[ID]->initialize(p.second); } - /* Update the unit if present*/ - if (units.count(ID) != 0) - { - units[ID]->updateExportData(p.second); + else { + /* Update the unit if present*/ + if (units.count(ID) != 0) + units[ID]->updateExportData(p.second); } }