From 4f2debeb4f69292f370a21b5c349d421bd10796b Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Tue, 13 Jun 2023 17:03:04 +0200 Subject: [PATCH] Added ability to take control of units And bugfixes --- client/demo.js | 71 +++---- client/src/@types/unit.d.ts | 32 +-- client/src/atc/atcboard.ts | 2 +- client/src/atc/unitdatatable.ts | 2 +- client/src/panels/unitcontrolpanel.ts | 213 ++++++++----------- client/src/panels/unitinfopanel.ts | 90 ++++---- client/src/units/aircraftdatabase.ts | 24 ++- client/src/units/unit.ts | 289 ++++++++++++++++--------- scripts/OlympusCommand.lua | 231 +++++++++++++++----- src/core/include/unit.h | 95 ++++++--- src/core/include/unitsmanager.h | 9 +- src/core/src/aircraft.cpp | 61 +++--- src/core/src/airunit.cpp | 152 +++++++------- src/core/src/core.cpp | 1 + src/core/src/groundunit.cpp | 148 +++++++++---- src/core/src/helicopter.cpp | 48 ++--- src/core/src/navyunit.cpp | 5 +- src/core/src/scheduler.cpp | 146 ++++++++++--- src/core/src/unit.cpp | 291 ++++++++++++++++++++------ src/core/src/unitsmanager.cpp | 22 ++ 20 files changed, 1266 insertions(+), 666 deletions(-) diff --git a/client/demo.js b/client/demo.js index 292e2f52..5a76f8d1 100644 --- a/client/demo.js +++ b/client/demo.js @@ -49,17 +49,12 @@ const DEMO_UNIT_DATA = { currentTask: "Holding", currentState: "Idle", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000, + desiredSpeed: 400, + desiredSpeedType: "CAS", + desiredAltitude: 3000, + desiredAltitudeType: "ASL", isTanker: false, - TACANOn: false, - TACANChannel: 32, - TACANXY: "Y", - TACANCallsign: "ASD", - radioFrequency: 123.750, - radioCallsign: 2, - radioCallsignNumber: 3, - radioAMFM: "FM" + }, optionsData: { ROE: "Designated", @@ -101,8 +96,8 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 300, - targetAltitude: 3000 + desiredSpeed: 300, + desiredAltitude: 3000 }, optionsData: { ROE: "Designated", @@ -112,7 +107,7 @@ const DEMO_UNIT_DATA = { ["3"]:{ baseData: { AI: true, - name: "2S6 Tunguska", + name: "M-60", unitName: "Olympus 1-3", groupName: "Group 4", alive: true, @@ -144,8 +139,9 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000 + desiredSpeed: 400, + desiredAltitude: 3000, + onOff: false }, optionsData: { ROE: "None", @@ -157,7 +153,6 @@ const DEMO_UNIT_DATA = { AI: true, name: "2S6 Tunguska", unitName: "Olympus 1-4", - groupName: "Group 1", alive: true, category: "GroundUnit", }, @@ -187,8 +182,8 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000 + desiredSpeed: 400, + desiredAltitude: 3000 }, optionsData: { ROE: "None", @@ -230,8 +225,8 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000 + desiredSpeed: 400, + desiredAltitude: 3000 }, optionsData: { ROE: "None", @@ -273,8 +268,8 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000 + desiredSpeed: 400, + desiredAltitude: 3000 }, optionsData: { ROE: "None", @@ -316,8 +311,8 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000 + desiredSpeed: 400, + desiredAltitude: 3000 }, optionsData: { ROE: "None", @@ -359,8 +354,8 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000 + desiredSpeed: 400, + desiredAltitude: 3000 }, optionsData: { ROE: "None", @@ -402,8 +397,8 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000 + desiredSpeed: 400, + desiredAltitude: 3000 }, optionsData: { ROE: "None", @@ -445,8 +440,8 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000 + desiredSpeed: 400, + desiredAltitude: 3000 }, optionsData: { ROE: "None", @@ -488,8 +483,8 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000 + desiredSpeed: 400, + desiredAltitude: 3000 }, optionsData: { ROE: "None", @@ -531,8 +526,8 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000 + desiredSpeed: 400, + desiredAltitude: 3000 }, optionsData: { ROE: "None", @@ -574,8 +569,8 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000 + desiredSpeed: 400, + desiredAltitude: 3000 }, optionsData: { ROE: "None", @@ -617,8 +612,8 @@ const DEMO_UNIT_DATA = { taskData: { currentTask: "Example task", activePath: undefined, - targetSpeed: 400, - targetAltitude: 3000 + desiredSpeed: 400, + desiredAltitude: 3000 }, optionsData: { ROE: "None", diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index e6b1b345..0a7847b9 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -3,7 +3,7 @@ interface UpdateData { } interface BaseData { - AI: boolean; + controlled: boolean; name: string; unitName: string; groupName: string; @@ -23,7 +23,7 @@ interface MissionData { fuel: number; flags: any; ammo: any; - targets: any; + contacts: any; hasTask: boolean; coalition: string; } @@ -36,10 +36,16 @@ interface TaskData { currentState: string; currentTask: string; activePath: any; - targetSpeed: number; - targetAltitude: number; + desiredSpeed: number; + desiredSpeedType: string; + desiredAltitude: number; + desiredAltitudeType: string; + targetLocation: any; isTanker: boolean; isAWACS: boolean; + onOff: boolean; + followRoads: boolean; + targetID: number; } interface OptionsData { @@ -51,15 +57,6 @@ interface OptionsData { generalSettings: GeneralSettings; } -interface UnitData { - baseData: BaseData; - flightData: FlightData; - missionData: MissionData; - formationData: FormationData; - taskData: TaskData; - optionsData: OptionsData; -} - interface TACAN { isOn: boolean; channel: number; @@ -91,4 +88,13 @@ interface UnitIconOptions { showAmmo: boolean, showSummary: boolean, rotateToHeading: boolean +} + +interface UnitData { + baseData: BaseData; + flightData: FlightData; + missionData: MissionData; + formationData: FormationData; + taskData: TaskData; + optionsData: OptionsData; } \ No newline at end of file diff --git a/client/src/atc/atcboard.ts b/client/src/atc/atcboard.ts index 959874ab..2edcbc9e 100644 --- a/client/src/atc/atcboard.ts +++ b/client/src/atc/atcboard.ts @@ -123,7 +123,7 @@ export abstract class ATCBoard { return false; } - if ( baseData.AI === true ) { + if ( baseData.controlled === true ) { // return false; } diff --git a/client/src/atc/unitdatatable.ts b/client/src/atc/unitdatatable.ts index 3b0b97b8..1ef21f2c 100644 --- a/client/src/atc/unitdatatable.ts +++ b/client/src/atc/unitdatatable.ts @@ -48,7 +48,7 @@ export class UnitDataTable extends Panel { for (const unit of unitsArray) { - const dataset = [unit.getBaseData().unitName, unit.getBaseData().name, unit.getBaseData().category, (unit.getBaseData().AI) ? "AI" : "Human"]; + const dataset = [unit.getBaseData().unitName, unit.getBaseData().name, unit.getBaseData().category, (unit.getBaseData().controlled) ? "AI" : "Human"]; addRow(el, dataset); } diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index ea90161d..c5d6f489 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -3,34 +3,22 @@ import { getUnitsManager } from ".."; import { Dropdown } from "../controls/dropdown"; import { Slider } from "../controls/slider"; import { aircraftDatabase } from "../units/aircraftdatabase"; -import { groundUnitsDatabase } from "../units/groundunitsdatabase"; -import { Aircraft, GroundUnit, Unit } from "../units/unit"; -import { UnitDatabase } from "../units/unitdatabase"; +import { Unit } from "../units/unit"; import { Panel } from "./panel"; - -const ROEs: string[] = ["Hold", "Return", "Designated", "Free"]; -const reactionsToThreat: string[] = ["None", "Manoeuvre", "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)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"]; -const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar OFF, no ECM)", "Attack (Radar only for targeting, ECM only if locked)", "Defend (Radar for searching, ECM if locked)", "Always on (Radar and ECM always on)"]; - -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 }; -const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 }; -const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helicopter: 0 }; -const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 }; -const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 }; +import { Switch } from "../controls/switch"; +import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants"; +import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils"; export class UnitControlPanel extends Panel { #altitudeSlider: Slider; - #airspeedSlider: Slider; + #altitudeTypeSwitch: Switch; + #speedSlider: Slider; + #speedTypeSwitch: Switch; + #onOffSwitch: Switch; + #followRoadsSwitch: Switch; #TACANXYDropdown: Dropdown; #radioDecimalsDropdown: Dropdown; #radioCallsignDropdown: Dropdown; - #expectedAltitude: number = -1; - #expectedSpeed: number = -1; #optionButtons: { [key: string]: HTMLButtonElement[] } = {} #advancedSettingsDialog: HTMLElement; @@ -38,22 +26,11 @@ export class UnitControlPanel extends Panel { super(ID); /* Unit control sliders */ - this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { - this.#expectedAltitude = value; - getUnitsManager().selectedUnitsSetAltitude(value * 0.3048) - }); + this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getUnitsManager().selectedUnitsSetAltitude(ftToM(value)); }); + this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetAltitudeType(value? "AGL": "ASL"); }); - this.#airspeedSlider = new Slider("airspeed-slider", 0, 100, "kts", (value: number) => { - this.#expectedSpeed = value; - 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", () => {}); + this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getUnitsManager().selectedUnitsSetSpeed(knotsToMs(value)); }); + this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "GS": "CAS"); }); /* Option buttons */ this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => { @@ -72,8 +49,27 @@ export class UnitControlPanel extends Panel { this.getElement().querySelector("#reaction-to-threat-buttons-container")?.append(...this.#optionButtons["reactionToThreat"]); this.getElement().querySelector("#emissions-countermeasures-buttons-container")?.append(...this.#optionButtons["emissionsCountermeasures"]); + /* On off switch */ + this.#onOffSwitch = new Switch("on-off-switch", (value: boolean) => { + getUnitsManager().selectedUnitsSetOnOff(value); + }); + + /* Follow roads switch */ + this.#followRoadsSwitch = new Switch("follow-roads-switch", (value: boolean) => { + getUnitsManager().selectedUnitsSetFollowRoads(value); + }); + + /* Advanced settings dialog */ this.#advancedSettingsDialog = document.querySelector("#advanced-settings-dialog"); + /* 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", () => {}); + + /* Events and timer */ window.setInterval(() => {this.update();}, 25); document.addEventListener("unitsSelection", (e: CustomEvent) => { this.show(); this.addButtons();}); @@ -82,35 +78,30 @@ export class UnitControlPanel extends Panel { document.addEventListener("showAdvancedSettings", () => { this.#updateAdvancedSettingsDialog(getUnitsManager().getSelectedUnits()); this.#advancedSettingsDialog.classList.remove("hide"); - }) + }); this.hide(); } - // Do this after panel is hidden (make sure there's a reset) - hide() { - super.hide(); - - this.#expectedAltitude = -1; - this.#expectedSpeed = -1; + show() { + super.show(); + this.#speedTypeSwitch.resetExpectedValue(); + this.#altitudeTypeSwitch.resetExpectedValue(); + this.#onOffSwitch.resetExpectedValue(); + this.#followRoadsSwitch.resetExpectedValue(); + this.#altitudeSlider.resetExpectedValue(); + this.#speedSlider.resetExpectedValue(); } addButtons() { var units = getUnitsManager().getSelectedUnits(); if (units.length < 20) { this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => { - let database: UnitDatabase | null; - if (unit instanceof Aircraft) - database = aircraftDatabase; - else if (unit instanceof GroundUnit) - database = groundUnitsDatabase; - else - database = null; // TODO add databases for other unit types - var button = document.createElement("button"); var callsign = unit.getBaseData().unitName || ""; + var label = unit.getDatabase()?.getByName(unit.getBaseData().name)?.label || unit.getBaseData().name; - button.setAttribute("data-short-label", database?.getByName(unit.getBaseData().name)?.shortLabel || unit.getBaseData().name); + button.setAttribute("data-label", label); button.setAttribute("data-callsign", callsign); button.setAttribute("data-coalition", unit.getMissionData().coalition); @@ -131,11 +122,53 @@ export class UnitControlPanel extends Panel { update() { if (this.getVisible()){ - 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); + const element = this.getElement(); + const units = getUnitsManager().getSelectedUnits(); + const selectedUnitsTypes = getUnitsManager().getSelectedUnitsTypes(); + + if (element != null && units.length > 0) { + /* Toggle visibility of control elements */ + element.toggleAttribute("data-show-categories-tooltip", selectedUnitsTypes.length > 1); + element.toggleAttribute("data-show-speed-slider", selectedUnitsTypes.length == 1); + element.toggleAttribute("data-show-altitude-slider", selectedUnitsTypes.length == 1 && (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter"))); + element.toggleAttribute("data-show-roe", true); + element.toggleAttribute("data-show-threat", (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")) && !(selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit"))); + element.toggleAttribute("data-show-emissions-countermeasures", (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")) && !(selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit"))); + element.toggleAttribute("data-show-on-off", (selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit")) && !(selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter"))); + element.toggleAttribute("data-show-follow-roads", (selectedUnitsTypes.length == 1 && selectedUnitsTypes.includes("GroundUnit"))); + element.toggleAttribute("data-show-advanced-settings-button", units.length == 1); + + /* Flight controls */ + var desiredAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredAltitude}); + var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredAltitudeType}); + var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredSpeed}); + var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredSpeedType}); + var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().onOff}); + var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().followRoads}); + if (selectedUnitsTypes.length == 1) { + this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false); + this.#speedTypeSwitch.setValue(desiredSpeedType != undefined? desiredSpeedType == "GS": undefined, false); + + this.#speedSlider.setMinMax(minSpeedValues[selectedUnitsTypes[0]], maxSpeedValues[selectedUnitsTypes[0]]); + this.#altitudeSlider.setMinMax(minAltitudeValues[selectedUnitsTypes[0]], maxAltitudeValues[selectedUnitsTypes[0]]); + this.#speedSlider.setIncrement(speedIncrements[selectedUnitsTypes[0]]); + this.#altitudeSlider.setIncrement(altitudeIncrements[selectedUnitsTypes[0]]); + + this.#speedSlider.setActive(desiredSpeed != undefined); + if (desiredSpeed != undefined) + this.#speedSlider.setValue(msToKnots(desiredSpeed), false); + + this.#altitudeSlider.setActive(desiredAltitude != undefined); + if (desiredAltitude != undefined) + this.#altitudeSlider.setValue(mToFt(desiredAltitude), false); + } + else { + this.#speedSlider.setActive(false); + this.#altitudeSlider.setActive(false); + } + + /* Option buttons */ this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => { button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().ROE === button.value)) }); @@ -147,75 +180,13 @@ export class UnitControlPanel extends Panel { this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => { button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().emissionsCountermeasures === button.value)) }); + + this.#onOffSwitch.setValue(onOff, false); + this.#followRoadsSwitch.setValue(followRoads, false); } } } - /* 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; - return true; - } - return false; - } - - #updateCanSetSpeedSlider(altitude: number) { - if (this.#expectedSpeed < 0 || altitude === this.#expectedSpeed) { - this.#expectedSpeed = -1; - return true; - } - return false; - } - - #showFlightControlSliders(units: Unit[]) { - if (getUnitsManager().getSelectedUnitsType() !== undefined) - this.#airspeedSlider.show() - else - this.#airspeedSlider.hide(); - - if (getUnitsManager().getSelectedUnitsType() === "Aircraft" || getUnitsManager().getSelectedUnitsType() === "Helicopter") - this.#altitudeSlider.show() - else - this.#altitudeSlider.hide(); - - this.getElement().querySelector(`#categories-tooltip`)?.classList.toggle("hide", getUnitsManager().getSelectedUnitsType() !== undefined); - - var unitsType = getUnitsManager().getSelectedUnitsType(); - var targetAltitude = getUnitsManager().getSelectedUnitsTargetAltitude(); - var targetSpeed = getUnitsManager().getSelectedUnitsTargetSpeed(); - - if (unitsType != undefined) { - if (["GroundUnit", "NavyUnit"].includes(unitsType)) - this.#altitudeSlider.hide() - - this.#airspeedSlider.setMinMax(minSpeedValues[unitsType], maxSpeedValues[unitsType]); - this.#altitudeSlider.setMinMax(minAltitudeValues[unitsType], maxAltitudeValues[unitsType]); - this.#airspeedSlider.setIncrement(speedIncrements[unitsType]); - this.#altitudeSlider.setIncrement(altitudeIncrements[unitsType]); - - 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); - } - } - } - else { - this.#airspeedSlider.setActive(false); - this.#altitudeSlider.setActive(false); - } - } - #updateAdvancedSettingsDialog(units: Unit[]) { if (units.length == 1) diff --git a/client/src/panels/unitinfopanel.ts b/client/src/panels/unitinfopanel.ts index 4d4eb77e..91e18920 100644 --- a/client/src/panels/unitinfopanel.ts +++ b/client/src/panels/unitinfopanel.ts @@ -16,7 +16,7 @@ export class UnitInfoPanel extends Panel { #latitude: HTMLElement; #longitude: HTMLElement; #loadoutContainer: HTMLElement; - #silhouette: HTMLElement; + #silhouette: HTMLImageElement; #unitControl: HTMLElement; #unitLabel: HTMLElement; #unitName: HTMLElement; @@ -24,21 +24,21 @@ export class UnitInfoPanel extends Panel { constructor(ID: string) { super(ID); - this.#altitude = (this.getElement().querySelector("#altitude")); - this.#currentTask = (this.getElement().querySelector("#current-task")); - this.#groundSpeed = (this.getElement().querySelector("#ground-speed")); - this.#fuelBar = (this.getElement().querySelector("#fuel-bar")); - this.#fuelPercentage = (this.getElement().querySelector("#fuel-percentage")); - this.#groupName = (this.getElement().querySelector("#group-name")); - this.#heading = (this.getElement().querySelector("#heading")); - this.#name = (this.getElement().querySelector("#name")); - this.#latitude = (this.getElement().querySelector("#latitude")); - this.#loadoutContainer = (this.getElement().querySelector("#loadout-container")); - this.#longitude = (this.getElement().querySelector("#longitude")); - this.#silhouette = (this.getElement().querySelector("#loadout-silhouette")); - this.#unitControl = (this.getElement().querySelector("#unit-control")); - this.#unitLabel = (this.getElement().querySelector("#unit-label")); - this.#unitName = (this.getElement().querySelector("#unit-name")); + this.#altitude = (this.getElement().querySelector("#altitude")) as HTMLElement; + this.#currentTask = (this.getElement().querySelector("#current-task")) as HTMLElement; + this.#groundSpeed = (this.getElement().querySelector("#ground-speed")) as HTMLElement; + this.#fuelBar = (this.getElement().querySelector("#fuel-bar")) as HTMLElement; + this.#fuelPercentage = (this.getElement().querySelector("#fuel-percentage")) as HTMLElement; + this.#groupName = (this.getElement().querySelector("#group-name")) as HTMLElement; + this.#heading = (this.getElement().querySelector("#heading")) as HTMLElement; + this.#name = (this.getElement().querySelector("#name")) as HTMLElement; + this.#latitude = (this.getElement().querySelector("#latitude")) as HTMLElement; + this.#loadoutContainer = (this.getElement().querySelector("#loadout-container")) as HTMLElement; + this.#longitude = (this.getElement().querySelector("#longitude")) as HTMLElement; + this.#silhouette = (this.getElement().querySelector("#loadout-silhouette")) as HTMLImageElement; + this.#unitControl = (this.getElement().querySelector("#unit-control")) as HTMLElement; + this.#unitLabel = (this.getElement().querySelector("#unit-label")) as HTMLElement; + this.#unitName = (this.getElement().querySelector("#unit-name")) as HTMLElement; document.addEventListener("unitsSelection", (e: CustomEvent) => this.#onUnitsSelection(e.detail)); document.addEventListener("unitsDeselection", (e: CustomEvent) => this.#onUnitsDeselection(e.detail)); @@ -47,69 +47,65 @@ export class UnitInfoPanel extends Panel { this.hide(); } - + #onUnitUpdate(unit: Unit) { if (this.getElement() != null && this.getVisible() && unit.getSelected()) { const baseData = unit.getBaseData(); /* Set the unit info */ - this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name; - this.#unitName.innerText = baseData.unitName; - this.#unitControl.innerText = ( ( baseData.AI ) ? "AI" : "Human" ) + " controlled"; - // this.#groupName.innerText = baseData.groupName; - //this.#name.innerText = baseData.name; - //this.#heading.innerText = String(Math.floor(rad2deg(unit.getFlightData().heading)) + " °"); - //this.#altitude.innerText = String(Math.floor(unit.getFlightData().altitude / 0.3048) + " ft"); - //this.#groundSpeed.innerText = String(Math.floor(unit.getFlightData().speed * 1.94384) + " kts"); + this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name; + this.#unitName.innerText = baseData.unitName; + if (unit.getMissionData().flags.Human) + this.#unitControl.innerText = "Human"; + else if (baseData.controlled) + this.#unitControl.innerText = "Olympus controlled"; + else + this.#unitControl.innerText = "DCS Controlled"; this.#fuelBar.style.width = String(unit.getMissionData().fuel + "%"); this.#fuelPercentage.dataset.percentage = "" + unit.getMissionData().fuel; - //this.#latitude.innerText = ConvertDDToDMS(unit.getFlightData().latitude, false); - //this.#longitude.innerText = ConvertDDToDMS(unit.getFlightData().longitude, true); - this.#currentTask.dataset.currentTask = unit.getTaskData().currentTask !== ""? unit.getTaskData().currentTask: "No task"; + this.#currentTask.dataset.currentTask = unit.getTaskData().currentTask !== "" ? unit.getTaskData().currentTask : "No task"; this.#currentTask.dataset.coalition = unit.getMissionData().coalition; - - this.#silhouette.setAttribute( "style", `--loadout-background-image:url('/images/units/${aircraftDatabase.getByName( baseData.name )?.filename}');` );; - + + this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(baseData.name)?.filename}`; + this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(baseData.name)?.filename == undefined || unit.getDatabase()?.getByName(baseData.name)?.filename == ''); + /* Add the loadout elements */ - const items = this.#loadoutContainer.querySelector( "#loadout-items" ); - - - if ( items ) { - - const ammo = Object.values( unit.getMissionData().ammo ); - - if ( ammo.length > 0 ) { + const items = this.#loadoutContainer.querySelector("#loadout-items"); + if (items) { + const ammo = Object.values(unit.getMissionData().ammo); + if (ammo.length > 0) { items.replaceChildren(...Object.values(unit.getMissionData().ammo).map( (ammo: any) => { var el = document.createElement("div"); - el.dataset.qty = ammo.count; + el.dataset.qty = ammo.count; el.dataset.item = ammo.desc.displayName; return el; } )); } else { - items.innerText = "No loadout"; - } - } } } - #onUnitsSelection(units: Unit[]){ - if (units.length == 1) + #onUnitsSelection(units: Unit[]) { + if (units.length == 1) { this.show(); + this.#onUnitUpdate(units[0]); + } else this.hide(); } - #onUnitsDeselection(units: Unit[]){ - if (units.length == 1) + #onUnitsDeselection(units: Unit[]) { + if (units.length == 1) { this.show(); + this.#onUnitUpdate(units[0]); + } else this.hide(); } diff --git a/client/src/units/aircraftdatabase.ts b/client/src/units/aircraftdatabase.ts index f2448468..5ad7622b 100644 --- a/client/src/units/aircraftdatabase.ts +++ b/client/src/units/aircraftdatabase.ts @@ -2456,8 +2456,8 @@ export class AircraftDatabase extends UnitDatabase { ], "filename": "kc-135.png" }, - "KC-135MPRS": { - "name": "KC-135MPRS", + "KC135MPRS": { + "name": "KC135MPRS", "label": "KC-135 MPRS Stratotanker", "era": ["Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], "shortLabel": "135M", @@ -2476,6 +2476,26 @@ export class AircraftDatabase extends UnitDatabase { ], "filename": "kc-135.png" }, + "S-3B Tanker": { + "name": "S-3B Tanker", + "label": "S-3B Tanker", + "era": ["Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], + "shortLabel": "S3B", + "loadouts": [ + { + "fuel": 1, + "items": [ + + ], + "roles": [ + "Tanker" + ], + "code": "", + "name": "Default Tanker" + } + ], + "filename": "s-3.png" + }, "MiG-15bis": { "name": "MiG-15bis", "label": "MiG-15 Fagot", diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index d2c70b46..54e9d37b 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -1,12 +1,14 @@ 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, setEmissionsCountermeasures } from '../server/server'; +import { mToFt, msToKnots, rad2deg } from '../other/utils'; +import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server'; import { aircraftDatabase } from './aircraftdatabase'; import { groundUnitsDatabase } from './groundunitsdatabase'; import { CustomMarker } from '../map/custommarker'; import { SVGInjector } from '@tanem/svg-injector'; import { UnitDatabase } from './unitdatabase'; +import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT } from '../map/map'; +import { TargetMarker } from '../map/targetmarker'; var pathIcon = new Icon({ iconUrl: '/resources/theme/images/markers/marker-icon.png', @@ -19,7 +21,7 @@ export class Unit extends CustomMarker { #data: UnitData = { baseData: { - AI: false, + controlled: false, name: "", unitName: "", groupName: "", @@ -37,7 +39,7 @@ export class Unit extends CustomMarker { fuel: 0, flags: {}, ammo: {}, - targets: {}, + contacts: {}, hasTask: false, coalition: "", }, @@ -48,10 +50,16 @@ export class Unit extends CustomMarker { currentState: "NONE", currentTask: "", activePath: {}, - targetSpeed: 0, - targetAltitude: 0, + desiredSpeed: 0, + desiredSpeedType: "GS", + desiredAltitude: 0, + desiredAltitudeType: "AGL", + targetLocation: {}, isTanker: false, isAWACS: false, + onOff: true, + followRoads: false, + targetID: 0 }, optionsData: { ROE: "", @@ -72,8 +80,10 @@ export class Unit extends CustomMarker { #pathMarkers: Marker[] = []; #pathPolyline: Polyline; - #targetsPolylines: Polyline[]; + #contactsPolylines: Polyline[]; #miniMapMarker: CircleMarker | null = null; + #targetLocationMarker: TargetMarker; + #targetLocationPolyline: Polyline; #timer: number = 0; @@ -103,7 +113,10 @@ export class Unit extends CustomMarker { this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 }); this.#pathPolyline.addTo(getMap()); - this.#targetsPolylines = []; + this.#contactsPolylines = []; + + this.#targetLocationMarker = new TargetMarker(new LatLng(0, 0)); + this.#targetLocationPolyline = new Polyline([], { color: '#FF0000', weight: 3, opacity: 0.5, smoothFactor: 1 }); /* Deselect units if they are hidden */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { @@ -116,8 +129,6 @@ export class Unit extends CustomMarker { /* Set the unit data */ this.setData(data); - - } getMarkerCategory() { @@ -150,11 +161,16 @@ export class Unit extends CustomMarker { if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) { this.#selected = selected; this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected); - if (selected) + if (selected) { document.dispatchEvent(new CustomEvent("unitSelection", { detail: this })); - else + this.#updateMarker(); + } + else { document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this })); - this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected)); + this.#clearDetectedUnits(); + this.#clearPath(); + this.#clearTarget(); + } } } @@ -201,50 +217,23 @@ export class Unit extends CustomMarker { const positionChanged = (data.flightData != undefined && data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude)); const headingChanged = (data.flightData != undefined && data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading); const aliveChanged = (data.baseData != undefined && data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive); - var updateMarker = (positionChanged || headingChanged || aliveChanged || !getMap().hasLayer(this)); - - if (data.baseData != undefined) { - for (let key in this.#data.baseData) - if (key in data.baseData) - //@ts-ignore - this.#data.baseData[key] = data.baseData[key]; - } - - if (data.flightData != undefined) { - for (let key in this.#data.flightData) - if (key in data.flightData) - //@ts-ignore - this.#data.flightData[key] = data.flightData[key]; - } - - if (data.missionData != undefined) { - for (let key in this.#data.missionData) - if (key in data.missionData) - //@ts-ignore - this.#data.missionData[key] = data.missionData[key]; - } - - if (data.formationData != undefined) { - for (let key in this.#data.formationData) - if (key in data.formationData) - //@ts-ignore - this.#data.formationData[key] = data.formationData[key]; - } - - if (data.taskData != undefined) { - for (let key in this.#data.taskData) - if (key in data.taskData) - //@ts-ignore - this.#data.taskData[key] = data.taskData[key]; - } - - if (data.optionsData != undefined) { - for (let key in this.#data.optionsData) - if (key in data.optionsData) - //@ts-ignore - this.#data.optionsData[key] = data.optionsData[key]; - } + const stateChanged = (data.taskData != undefined && data.taskData.currentState != undefined && this.getTaskData().currentState != data.taskData.currentState); + const controlledChanged = (data.baseData != undefined && data.baseData.controlled != undefined && this.getBaseData().controlled != data.baseData.controlled); + var updateMarker = (positionChanged || headingChanged || aliveChanged || stateChanged || controlledChanged || !getMap().hasLayer(this)); + /* Load the data from the received json */ + Object.keys(this.#data).forEach((key1: string) => { + Object.keys(this.#data[key1 as keyof(UnitData)]).forEach((key2: string) => { + if (key1 in data && key2 in data[key1]) { + var value1 = this.#data[key1 as keyof(UnitData)]; + var value2 = value1[key2 as keyof typeof value1]; + if (typeof data[key1][key2] === typeof value2 || typeof value2 === "undefined") + //@ts-ignore + this.#data[key1 as keyof(UnitData)][key2 as keyof typeof struct] = data[key1][key2]; + } + }); + }); + /* Fire an event when a unit dies */ if (aliveChanged && this.getBaseData().alive == false) document.dispatchEvent(new CustomEvent("unitDeath", { detail: this })); @@ -255,13 +244,17 @@ export class Unit extends CustomMarker { if (updateMarker) this.#updateMarker(); - this.#clearTargets(); + this.#clearDetectedUnits(); if (this.getSelected()) { this.#drawPath(); - this.#drawTargets(); + this.#drawDetectedUnits(); + this.#drawTarget(); } - else + else { this.#clearPath(); + this.#clearTarget(); + } + document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); } @@ -400,7 +393,7 @@ export class Unit extends CustomMarker { const hiddenUnits = getUnitsManager().getHiddenTypes(); if (this.getMissionData().flags.Human && hiddenUnits.includes("human")) hidden = true; - else if (this.getBaseData().AI == false && hiddenUnits.includes("dcs")) + else if (this.getBaseData().controlled == false && hiddenUnits.includes("dcs")) hidden = true; else if (hiddenUnits.includes(this.getMarkerCategory())) hidden = true; @@ -431,6 +424,15 @@ export class Unit extends CustomMarker { return getUnitsManager().getUnitByID(this.getFormationData().leaderID); } + canRole(roles: string | string[]) { + if (typeof(roles) === "string") + roles = [roles]; + + return this.getDatabase()?.getByName(this.getBaseData().name)?.loadouts.some((loadout: LoadoutBlueprint) => { + return (roles as string[]).some((role: string) => {return loadout.roles.includes(role)}); + }); + } + /********************** Unit commands *************************/ addDestination(latlng: L.LatLng) { if (!this.getMissionData().flags.Human) { @@ -485,11 +487,21 @@ export class Unit extends CustomMarker { setSpeed(this.ID, speed); } + setSpeedType(speedType: string) { + if (!this.getMissionData().flags.Human) + setSpeedType(this.ID, speedType); + } + setAltitude(altitude: number) { if (!this.getMissionData().flags.Human) setAltitude(this.ID, altitude); } + setAltitudeType(altitudeType: string) { + if (!this.getMissionData().flags.Human) + setAltitudeType(this.ID, altitudeType); + } + setROE(ROE: string) { if (!this.getMissionData().flags.Human) setROE(this.ID, ROE); @@ -510,8 +522,18 @@ export class Unit extends CustomMarker { setLeader(this.ID, isLeader, wingmenIDs); } - delete() { - deleteUnit(this.ID); + setOnOff(onOff: boolean) { + if (!this.getMissionData().flags.Human) + setOnOff(this.ID, onOff); + } + + setFollowRoads(followRoads: boolean) { + if (!this.getMissionData().flags.Human) + setFollowRoads(this.ID, followRoads); + } + + delete(explosion: boolean) { + deleteUnit(this.ID, explosion); } refuel() { @@ -524,6 +546,22 @@ export class Unit extends CustomMarker { setAdvacedOptions(this.ID, isTanker, isAWACS, TACAN, radio, generalSettings); } + bombPoint(latlng: LatLng) { + bombPoint(this.ID, latlng); + } + + carpetBomb(latlng: LatLng) { + carpetBomb(this.ID, latlng); + } + + bombBuilding(latlng: LatLng) { + bombBuilding(this.ID, latlng); + } + + fireAtArea(latlng: LatLng) { + fireAtArea(this.ID, latlng); + } + /***********************************************/ onAdd(map: Map): this { super.onAdd(map); @@ -534,10 +572,9 @@ export class Unit extends CustomMarker { /***********************************************/ #onClick(e: any) { if (!this.#preventClick) { - if (getMap().getState() === 'IDLE' || getMap().getState() === 'MOVE_UNIT' || e.originalEvent.ctrlKey) { - if (!e.originalEvent.ctrlKey) { + if (getMap().getState() === IDLE || getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) { + if (!e.originalEvent.ctrlKey) getUnitsManager().deselectAllUnits(); - } this.setSelected(!this.getSelected()); } } @@ -554,20 +591,35 @@ export class Unit extends CustomMarker { #onContextMenu(e: any) { var options: {[key: string]: {text: string, tooltip: string}} = {}; + const selectedUnits = getUnitsManager().getSelectedUnits(); + const selectedUnitTypes = getUnitsManager().getSelectedUnitsTypes(); options["center-map"] = {text: "Center map", tooltip: "Center the map on the unit and follow it"}; - if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this)))) { + if (selectedUnits.length > 0 && !(selectedUnits.length == 1 && (selectedUnits.includes(this)))) { options["attack"] = {text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons"}; - if (getUnitsManager().getSelectedUnitsType() === "Aircraft") + if (getUnitsManager().getSelectedUnitsTypes().length == 1 && getUnitsManager().getSelectedUnitsTypes()[0] === "Aircraft") options["follow"] = {text: "Follow", tooltip: "Follow the unit at a user defined distance and position"};; } - else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) { + else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) { if (this.getBaseData().category == "Aircraft") { - options["refuel"] = {text: "AAR Refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB."}; // TODO Add some way of knowing which aircraft can AAR + options["refuel"] = {text: "Air to air refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB."}; // TODO Add some way of knowing which aircraft can AAR } } + if ((selectedUnits.length === 0 && this.getBaseData().category == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0]))) + { + if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canRole(["CAS", "Strike"])})) { + options["bomb"] = {text: "Precision bombing", tooltip: "Precision bombing of a specific point"}; + options["carpet-bomb"] = {text: "Carpet bombing", tooltip: "Carpet bombing close to a point"}; + } + } + + if ((selectedUnits.length === 0 && this.getBaseData().category == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) { + if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"])})) + options["fire-at-area"] = {text: "Fire at area", tooltip: "Fire at a large area"}; + } + if (Object.keys(options).length > 0) { getMap().showUnitContextMenu(e); getMap().getUnitContextMenu().setOptions(options, (option: string) => { @@ -586,6 +638,12 @@ export class Unit extends CustomMarker { getUnitsManager().selectedUnitsRefuel(); else if (action === "follow") this.#showFollowOptions(e); + else if (action === "bomb") + getMap().setState(BOMBING); + else if (action === "carpet-bomb") + getMap().setState(CARPET_BOMBING); + else if (action === "fire-at-area") + getMap().setState(FIRE_AT_AREA); } #showFollowOptions(e: any) { @@ -668,16 +726,18 @@ export class Unit extends CustomMarker { /* Set current unit state */ if (this.getMissionData().flags.Human) // Unit is human element.querySelector(".unit")?.setAttribute("data-state", "human"); - else if (!this.getBaseData().AI) // Unit is under DCS control (not Olympus) + else if (!this.getBaseData().controlled) // Unit is under DCS control (not Olympus) element.querySelector(".unit")?.setAttribute("data-state", "dcs"); + else if ((this.getBaseData().category == "Aircraft" || this.getBaseData().category == "Helicopter") && !this.getMissionData().hasTask) + element.querySelector(".unit")?.setAttribute("data-state", "no-task"); else // Unit is under Olympus control element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase()); /* Set altitude and speed */ if (element.querySelector(".unit-altitude")) - (element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 100)); + (element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.getFlightData().altitude) / 100)); if (element.querySelector(".unit-speed")) - (element.querySelector(".unit-speed")).innerText = String(Math.floor(this.getFlightData().speed * 1.94384)); + (element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.getFlightData().speed))) + "GS"; /* Rotate elements according to heading */ element.querySelectorAll("[data-rotate-to-heading]").forEach(el => { @@ -768,35 +828,74 @@ export class Unit extends CustomMarker { this.#pathPolyline.setLatLngs([]); } - #drawTargets() { - for (let index in this.getMissionData().targets) { - var targetData = this.getMissionData().targets[index]; - var target = getUnitsManager().getUnitByID(targetData.object["id_"]) - if (target != null) { - var startLatLng = new LatLng(this.getFlightData().latitude, this.getFlightData().longitude) - var endLatLng = new LatLng(target.getFlightData().latitude, target.getFlightData().longitude) + #drawDetectedUnits() { + for (let index in this.getMissionData().contacts) { + var targetData = this.getMissionData().contacts[index]; + if (targetData.object != undefined){ + var target = getUnitsManager().getUnitByID(targetData.object["id_"]) + if (target != null) { + var startLatLng = new LatLng(this.getFlightData().latitude, this.getFlightData().longitude) + var endLatLng = new LatLng(target.getFlightData().latitude, target.getFlightData().longitude) - var color; - if (targetData.detectionMethod === "RADAR") - color = "#FFFF00"; - else if (targetData.detectionMethod === "VISUAL") - color = "#FF00FF"; - else if (targetData.detectionMethod === "RWR") - color = "#00FF00"; - else - color = "#FFFFFF"; - var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1 }); - targetPolyline.addTo(getMap()); - this.#targetsPolylines.push(targetPolyline) + var color; + if (targetData.detectionMethod === "RADAR") + color = "#FFFF00"; + else if (targetData.detectionMethod === "VISUAL") + color = "#FF00FF"; + else if (targetData.detectionMethod === "RWR") + color = "#00FF00"; + else + color = "#FFFFFF"; + var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1, dashArray: "4, 8" }); + targetPolyline.addTo(getMap()); + this.#contactsPolylines.push(targetPolyline) + } } } } - #clearTargets() { - for (let index in this.#targetsPolylines) { - getMap().removeLayer(this.#targetsPolylines[index]) + #clearDetectedUnits() { + for (let index in this.#contactsPolylines) { + getMap().removeLayer(this.#contactsPolylines[index]) } } + + #drawTarget() { + const targetLocation = this.getTaskData().targetLocation; + + if (targetLocation.latitude && targetLocation.longitude && targetLocation.latitude != 0 && targetLocation.longitude != 0) { + const lat = targetLocation.latitude; + const lng = targetLocation.longitude; + if (lat && lng) + this.#drawTargetLocation(new LatLng(lat, lng)); + } + else if (this.getTaskData().targetID != 0 && getUnitsManager().getUnitByID(this.getTaskData().targetID)) { + const flightData = getUnitsManager().getUnitByID(this.getTaskData().targetID)?.getFlightData(); + const lat = flightData?.latitude; + const lng = flightData?.longitude; + if (lat && lng) + this.#drawTargetLocation(new LatLng(lat, lng)); + } + else + this.#clearTarget(); + } + + #drawTargetLocation(targetLocation: LatLng) { + if (!getMap().hasLayer(this.#targetLocationMarker)) + this.#targetLocationMarker.addTo(getMap()); + if (!getMap().hasLayer(this.#targetLocationPolyline)) + this.#targetLocationPolyline.addTo(getMap()); + this.#targetLocationMarker.setLatLng(new LatLng(targetLocation.lat, targetLocation.lng)); + this.#targetLocationPolyline.setLatLngs([new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), new LatLng(targetLocation.lat, targetLocation.lng)]) + } + + #clearTarget() { + if (getMap().hasLayer(this.#targetLocationMarker)) + this.#targetLocationMarker.removeFrom(getMap()); + + if (getMap().hasLayer(this.#targetLocationPolyline)) + this.#targetLocationPolyline.removeFrom(getMap()); + } } export class AirUnit extends Unit { @@ -850,7 +949,7 @@ export class GroundUnit extends Unit { showVvi: false, showHotgroup: true, showUnitIcon: true, - showShortLabel: true, + showShortLabel: false, showFuel: false, showAmmo: false, showSummary: false, diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index bd61f8d2..34d74cb5 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,4 +1,4 @@ -local version = "v0.2.1-alpha" +local version = "v0.3.0-alpha" local debug = true @@ -98,13 +98,14 @@ function Olympus.buildEnrouteTask(options) end -- Builds a valid task depending on the provided options -function Olympus.buildTask(options) +function Olympus.buildTask(groupName, options) local task = nil + local group = Group.getByName(groupName) if (Olympus.isArray(options)) then local tasks = {} for idx, subOptions in pairs(options) do - tasks[idx] = Olympus.buildTask(subOptions) or Olympus.buildEnrouteTask(subOptions) + tasks[idx] = Olympus.buildTask(groupName, subOptions) or Olympus.buildEnrouteTask(subOptions) end task = { id = 'ComboTask', @@ -139,29 +140,92 @@ function Olympus.buildTask(options) pattern = options['pattern'] or "Circle" } } + if options['altitude'] then + if options ['altitudeType'] then + if options ['altitudeType'] == "AGL" then + local groundHeight = 0 + if group then + local groupPos = mist.getLeadPos(group) + groundHeight = land.getHeight({x = groupPos.x, y = groupPos.z}) + end + task['params']['altitude'] = groundHeight + options['altitude'] + else + task['params']['altitude'] = options['altitude'] + end + else + task['params']['altitude'] = options['altitude'] + end + end + if options['speed'] then + task['params']['speed'] = options['speed'] + end + elseif options['id'] == 'Bombing' and options['lat'] and options['lng'] then + local point = coord.LLtoLO(options['lat'], options['lng'], 0) + task = { + id = 'Bombing', + params = { + point = {x = point.x, y = point.z}, + attackQty = 1 + } + } + elseif options['id'] == 'CarpetBombing' and options['lat'] and options['lng'] then + local point = coord.LLtoLO(options['lat'], options['lng'], 0) + task = { + id = 'CarpetBombing', + params = { + x = point.x, + y = point.z, + carpetLength = 1000, + attackType = 'Carpet', + expend = "All", + attackQty = 1, + attackQtyLimit = true + } + } + elseif options['id'] == 'AttackMapObject' and options['lat'] and options['lng'] then + local point = coord.LLtoLO(options['lat'], options['lng'], 0) + task = { + id = 'AttackMapObject', + params = { + point = {x = point.x, y = point.z} + } + } + elseif options['id'] == 'FireAtPoint' and options['lat'] and options['lng'] and options['radius'] then + local point = coord.LLtoLO(options['lat'], options['lng'], 0) + task = { + id = 'FireAtPoint', + params = { + point = {x = point.x, y = point.z}, + radius = options['radius'] + } + } end end return task end -- Move a unit. Since many tasks in DCS are Enroute tasks, this function is an important way to control the unit AI -function Olympus.move(ID, lat, lng, altitude, speed, category, taskOptions) - Olympus.debug("Olympus.move " .. ID .. " (" .. lat .. ", " .. lng ..") " .. altitude .. "m " .. speed .. "m/s " .. category, 2) - local unit = Olympus.getUnitByID(ID) - if unit then +function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedType, category, taskOptions) + Olympus.debug("Olympus.move " .. groupName .. " (" .. lat .. ", " .. lng ..") " .. altitude .. "m " .. altitudeType .. " ".. speed .. "m/s " .. category .. " " .. Olympus.serializeTable(taskOptions), 2) + local group = Group.getByName(groupName) + if group then if category == "Aircraft" then - local startPoint = mist.getLeadPos(unit:getGroup()) + local startPoint = mist.getLeadPos(group) local endPoint = coord.LLtoLO(lat, lng, 0) + if altitudeType == "AGL" then + altitude = land.getHeight({x = endPoint.x, y = endPoint.z}) + altitude + end + local path = {} if taskOptions and taskOptions['id'] == 'Land' then path = { - [1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO'), + [1] = mist.fixedWing.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'), [2] = mist.fixedWing.buildWP(endPoint, landing, speed, 0, 'AGL') } else path = { - [1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO'), + [1] = mist.fixedWing.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'), [2] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO') } end @@ -184,7 +248,6 @@ function Olympus.move(ID, lat, lng, altitude, speed, category, taskOptions) }, }, } - group = unit:getGroup() local groupCon = group:getController() if groupCon then groupCon:setTask(missionTask) @@ -193,20 +256,26 @@ function Olympus.move(ID, lat, lng, altitude, speed, category, taskOptions) elseif category == "GroundUnit" then vars = { - group = unit:getGroup(), + group = group, point = coord.LLtoLO(lat, lng, 0), - form = "Off Road", heading = 0, - speed = speed, - disableRoads = true + speed = speed } + + if taskOptions and taskOptions['id'] == 'FollowRoads' and taskOptions['value'] == true then + vars["disableRoads"] = false + else + vars["form"] = "Off Road" + vars["disableRoads"] = true + end + mist.groupToRandomPoint(vars) Olympus.debug("Olympus.move executed succesfully on a ground unit", 2) else Olympus.debug("Olympus.move not implemented yet for " .. category, 2) end else - Olympus.debug("Error in Olympus.move " .. ID, 2) + Olympus.debug("Error in Olympus.move " .. groupName, 2) end end @@ -228,6 +297,12 @@ function Olympus.smoke(color, lat, lng) trigger.action.smoke(mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0)), colorEnum) end +-- Creates an explosion on the ground +function Olympus.explosion(intensity, lat, lng) + Olympus.debug("Olympus.explosion " .. intensity .. " (" .. lat .. ", " .. lng ..")", 2) + trigger.action.explosion(mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0)), intensity) +end + -- Spawns a single ground unit function Olympus.spawnGroundUnit(coalition, unitType, lat, lng) Olympus.debug("Olympus.spawnGroundUnit " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..")", 2) @@ -279,12 +354,12 @@ end -- payloadName: a string, one of the names defined in unitPayloads.lua. Must be compatible with the unitType -- airbaseName: a string, if present the aircraft will spawn on the ground of the selected airbase -- payload: a table, if present the unit will receive this specific payload. Overrides payloadName -function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions) +function Olympus.spawnAircraft(coalition, unitType, lat, lng, alt, spawnOptions) local payloadName = spawnOptions["payloadName"] local airbaseName = spawnOptions["airbaseName"] local payload = spawnOptions["payload"] - Olympus.debug("Olympus.spawnAircraft " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..")", 2) + Olympus.debug("Olympus.spawnAircraft " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..", " .. alt .. ")", 2) local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0)) if payload == nil then @@ -304,7 +379,7 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions) ["type"] = unitType, ["x"] = spawnLocation.x, ["y"] = spawnLocation.z, - ["alt"] = 20000 * 0.3048, + ["alt"] = alt, ["alt_type"] = "BARO", ["skill"] = "Excellent", ["payload"] = @@ -359,8 +434,61 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions) }, } end + else + route = { + ["points"] = + { + [1] = + { + ["alt"] = alt, + ["alt_type"] = "BARO", + ["task"] = + { + ["id"] = "ComboTask", + ["params"] = + { + ["tasks"] = + { + [1] = + { + ["number"] = 1, + ["auto"] = true, + ["id"] = "WrappedAction", + ["enabled"] = true, + ["params"] = + { + ["action"] = + { + ["id"] = "EPLRS", + ["params"] = + { + ["value"] = true + }, + }, + }, + }, + [2] = + { + ["number"] = 2, + ["auto"] = false, + ["id"] = "Orbit", + ["enabled"] = true, + ["params"] = + { + ["pattern"] = "Circle" + }, + }, + }, + }, + }, + ["type"] = "Turning Point", + ["x"] = spawnLocation.x, + ["y"] = spawnLocation.z, + }, -- end of [1] + }, -- end of ["points"] + } -- end of ["route"] end - + local vars = { units = unitTable, @@ -390,7 +518,7 @@ function Olympus.clone(ID, lat, lng, category) local spawnOptions = { payload = Olympus.payloadRegistry[unit:getName()] } - Olympus.spawnAircraft(coalition, unit:getTypeName(), lat, lng, spawnOptions) + Olympus.spawnAircraft(coalition, unit:getTypeName(), lat, lng, unit:getPoint().y, spawnOptions) elseif category == "GroundUnit" then Olympus.spawnGroundUnit(coalition, unit:getTypeName(), lat, lng) end @@ -398,11 +526,11 @@ function Olympus.clone(ID, lat, lng, category) Olympus.debug("Olympus.clone completed successfully", 2) end -function Olympus.delete(ID, lat, lng) - Olympus.debug("Olympus.delete " .. ID, 2) +function Olympus.delete(ID, explosion) + Olympus.debug("Olympus.delete " .. ID .. " " .. tostring(explosion), 2) local unit = Olympus.getUnitByID(ID) if unit then - if unit:getPlayerName() then + if unit:getPlayerName() or explosion then trigger.action.explosion(unit:getPoint() , 250 ) --consider replacing with forcibly deslotting the player, however this will work for now Olympus.debug("Olympus.delete completed successfully", 2) else @@ -412,46 +540,55 @@ function Olympus.delete(ID, lat, lng) end end -function Olympus.setTask(ID, taskOptions) - Olympus.debug("Olympus.setTask " .. ID .. " " .. Olympus.serializeTable(taskOptions), 2) - local unit = Olympus.getUnitByID(ID) - if unit then - local task = Olympus.buildTask(taskOptions); +function Olympus.setTask(groupName, taskOptions) + Olympus.debug("Olympus.setTask " .. groupName .. " " .. Olympus.serializeTable(taskOptions), 2) + local group = Group.getByName(groupName) + if group then + local task = Olympus.buildTask(groupName, taskOptions); Olympus.debug("Olympus.setTask " .. Olympus.serializeTable(task), 20) if task then - unit:getGroup():getController():setTask(task) + group:getController():setTask(task) Olympus.debug("Olympus.setTask completed successfully", 2) end end end -function Olympus.resetTask(ID) - Olympus.debug("Olympus.resetTask " .. ID, 2) - local unit = Olympus.getUnitByID(ID) - if unit then - unit:getGroup():getController():resetTask() +function Olympus.resetTask(groupName) + Olympus.debug("Olympus.resetTask " .. groupName, 2) + local group = Group.getByName(groupName) + if group then + group:getController():resetTask() Olympus.debug("Olympus.resetTask completed successfully", 2) end end -function Olympus.setCommand(ID, command) - Olympus.debug("Olympus.setCommand " .. ID .. " " .. Olympus.serializeTable(command), 2) - local unit = Olympus.getUnitByID(ID) - if unit then - unit:getGroup():getController():setCommand(command) +function Olympus.setCommand(groupName, command) + Olympus.debug("Olympus.setCommand " .. groupName .. " " .. Olympus.serializeTable(command), 2) + local group = Group.getByName(groupName) + if group then + group:getController():setCommand(command) Olympus.debug("Olympus.setCommand completed successfully", 2) end end -function Olympus.setOption(ID, optionID, optionValue) - Olympus.debug("Olympus.setOption " .. ID .. " " .. optionID .. " " .. tostring(optionValue), 2) - local unit = Olympus.getUnitByID(ID) - if unit then - unit:getGroup():getController():setOption(optionID, optionValue) +function Olympus.setOption(groupName, optionID, optionValue) + Olympus.debug("Olympus.setOption " .. groupName .. " " .. optionID .. " " .. tostring(optionValue), 2) + local group = Group.getByName(groupName) + if group then + group:getController():setOption(optionID, optionValue) Olympus.debug("Olympus.setOption completed successfully", 2) end end +function Olympus.setOnOff(groupName, onOff) + Olympus.debug("Olympus.setOnOff " .. groupName .. " " .. tostring(onOff), 2) + local group = Group.getByName(groupName) + if group then + group:getController():setOnOff(onOff) + Olympus.debug("Olympus.setOnOff completed successfully", 2) + end +end + function Olympus.serializeTable(val, name, skipnewlines, depth) skipnewlines = skipnewlines or false depth = depth or 0 @@ -547,7 +684,7 @@ function Olympus.setMissionData(arg, time) for index, unit in pairs(group:getUnits()) do local unitController = unit:getController() local table = {} - table["targets"] = {} + table["contacts"] = {} for i, target in ipairs(controllerTargets) do for det, enum in pairs(Controller.Detection) do @@ -556,7 +693,7 @@ function Olympus.setMissionData(arg, time) if detected then target["detectionMethod"] = det - table["targets"][#table["targets"] + 1] = target + table["contacts"][#table["contacts"] + 1] = target end end end diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 2d6d6bcf..817beb29 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -6,6 +6,8 @@ #include "measure.h" #include "logger.h" +#define TASK_CHECK_INIT_VALUE 10 + namespace State { enum States @@ -18,7 +20,11 @@ namespace State LAND, REFUEL, AWACS, - TANKER + TANKER, + BOMB_POINT, + CARPET_BOMB, + BOMB_BUILDING, + FIRE_AT_AREA }; }; @@ -56,21 +62,24 @@ public: /********** Public methods **********/ void initialize(json::value json); + void setDefaults(bool force = false); int getID() { return ID; } + void runAILoop(); void updateExportData(json::value json); void updateMissionData(json::value json); - json::value getData(long long time); + json::value getData(long long time, bool getAll = false); virtual wstring getCategory() { return L"No category"; }; /********** Base data **********/ - void setAI(bool newAI) { AI = newAI; addMeasure(L"AI", json::value(newAI)); } + void setControlled(bool newControlled) { controlled = newControlled; addMeasure(L"controlled", json::value(newControlled)); } void setName(wstring newName) { name = newName; addMeasure(L"name", json::value(newName));} void setUnitName(wstring newUnitName) { unitName = newUnitName; addMeasure(L"unitName", json::value(newUnitName));} void setGroupName(wstring newGroupName) { groupName = newGroupName; addMeasure(L"groupName", json::value(newGroupName));} void setAlive(bool newAlive) { alive = newAlive; addMeasure(L"alive", json::value(newAlive));} void setType(json::value newType) { type = newType; addMeasure(L"type", newType);} void setCountry(int newCountry) { country = newCountry; addMeasure(L"country", json::value(newCountry));} - bool getAI() { return AI; } + + bool getControlled() { return controlled; } wstring getName() { return name; } wstring getUnitName() { return unitName; } wstring getGroupName() { return groupName; } @@ -84,6 +93,7 @@ public: void setAltitude(double newAltitude) {altitude = newAltitude; addMeasure(L"altitude", json::value(newAltitude));} void setHeading(double newHeading) {heading = newHeading; addMeasure(L"heading", json::value(newHeading));} void setSpeed(double newSpeed) {speed = newSpeed; addMeasure(L"speed", json::value(newSpeed));} + double getLatitude() { return latitude; } double getLongitude() { return longitude; } double getAltitude() { return altitude; } @@ -93,13 +103,14 @@ public: /********** Mission data **********/ void setFuel(double newFuel) { fuel = newFuel; addMeasure(L"fuel", json::value(newFuel));} void setAmmo(json::value newAmmo) { ammo = newAmmo; addMeasure(L"ammo", json::value(newAmmo));} - void setTargets(json::value newTargets) {targets = newTargets; addMeasure(L"targets", json::value(newTargets));} - void setHasTask(bool newHasTask) { hasTask = newHasTask; addMeasure(L"hasTask", json::value(newHasTask)); } + void setContacts(json::value newContacts) {contacts = newContacts; addMeasure(L"contacts", json::value(newContacts));} + void setHasTask(bool newHasTask); void setCoalitionID(int newCoalitionID); void setFlags(json::value newFlags) { flags = newFlags; addMeasure(L"flags", json::value(newFlags));} + double getFuel() { return fuel; } json::value getAmmo() { return ammo; } - json::value getTargets() { return targets; } + json::value getTargets() { return contacts; } bool getHasTask() { return hasTask; } wstring getCoalition() { return coalition; } int getCoalitionID(); @@ -108,40 +119,48 @@ public: /********** Formation data **********/ void setLeaderID(int newLeaderID) { leaderID = newLeaderID; addMeasure(L"leaderID", json::value(newLeaderID)); } void setFormationOffset(Offset formationOffset); + int getLeaderID() { return leaderID; } Offset getFormationoffset() { return formationOffset; } /********** Task data **********/ - void setCurrentTask(wstring newCurrentTask) { currentTask = newCurrentTask;addMeasure(L"currentTask", json::value(newCurrentTask)); } - virtual void setTargetSpeed(double newTargetSpeed) { targetSpeed = newTargetSpeed; addMeasure(L"targetSpeed", json::value(newTargetSpeed));} - virtual void setTargetAltitude(double newTargetAltitude) { targetAltitude = newTargetAltitude; addMeasure(L"targetAltitude", json::value(newTargetAltitude));} //TODO fix, double definition + void setCurrentTask(wstring newCurrentTask) { currentTask = newCurrentTask; addMeasure(L"currentTask", json::value(newCurrentTask)); } + void setDesiredSpeed(double newDesiredSpeed); + void setDesiredAltitude(double newDesiredAltitude); + void setDesiredSpeedType(wstring newDesiredSpeedType); + void setDesiredAltitudeType(wstring newDesiredAltitudeType); void setActiveDestination(Coords newActiveDestination) { activeDestination = newActiveDestination; addMeasure(L"activeDestination", json::value("")); } // TODO fix void setActivePath(list newActivePath); - void clearActivePath(); - void pushActivePathFront(Coords newActivePathFront); - void pushActivePathBack(Coords newActivePathBack); - void popActivePathFront(); void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));} + void setTargetLocation(Coords newTargetLocation); void setIsTanker(bool newIsTanker); void setIsAWACS(bool newIsAWACS); + virtual void setOnOff(bool newOnOff) { onOff = newOnOff; addMeasure(L"onOff", json::value(newOnOff));}; + virtual void setFollowRoads(bool newFollowRoads) { followRoads = newFollowRoads; addMeasure(L"followRoads", json::value(newFollowRoads)); }; wstring getCurrentTask() { return currentTask; } - virtual double getTargetSpeed() { return targetSpeed; }; - virtual double getTargetAltitude() { return targetAltitude; }; + virtual double getDesiredSpeed() { return desiredSpeed; }; + virtual double getDesiredAltitude() { return desiredAltitude; }; + virtual wstring getDesiredSpeedType() { return desiredSpeedType; }; + virtual wstring getDesiredAltitudeType() { return desiredAltitudeType; }; Coords getActiveDestination() { return activeDestination; } list getActivePath() { return activePath; } int getTargetID() { return targetID; } + Coords getTargetLocation() { return targetLocation; } bool getIsTanker() { return isTanker; } bool getIsAWACS() { return isAWACS; } + bool getOnOff() { return onOff; }; + bool getFollowRoads() { return followRoads; }; /********** 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); + void setROE(wstring newROE, bool force = false); + void setReactionToThreat(wstring newReactionToThreat, bool force = false); + void setEmissionsCountermeasures(wstring newEmissionsCountermeasures, bool force = false); + void setTACAN(Options::TACAN newTACAN, bool force = false); + void setRadio(Options::Radio newradio, bool force = false); + void setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool force = false); + void setEPLRS(bool newEPLRS, bool force = false); + wstring getROE() { return ROE; } wstring getReactionToThreat() { return reactionToThreat; } wstring getEmissionsCountermeasures() { return emissionsCountermeasures; }; @@ -152,19 +171,24 @@ public: /********** Control functions **********/ void landAt(Coords loc); - virtual void changeSpeed(wstring change){}; - virtual void changeAltitude(wstring change){}; + virtual void changeSpeed(wstring change) {}; + virtual void changeAltitude(wstring change) {}; void resetActiveDestination(); virtual void setState(int newState) { state = newState; }; void resetTask(); + void clearActivePath(); + void pushActivePathFront(Coords newActivePathFront); + void pushActivePathBack(Coords newActivePathBack); + void popActivePathFront(); protected: int ID; map measures; + int taskCheckCounter = 0; /********** Base data **********/ - bool AI = false; + bool controlled = false; wstring name = L"undefined"; wstring unitName = L"undefined"; wstring groupName = L"undefined"; @@ -183,7 +207,7 @@ protected: double fuel = 0; double initialFuel = 0; // Used internally to detect refueling completed json::value ammo = json::value::null(); - json::value targets = json::value::null(); + json::value contacts = json::value::null(); bool hasTask = false; wstring coalition = L""; json::value flags = json::value::null(); @@ -194,13 +218,18 @@ protected: /********** Task data **********/ wstring currentTask = L""; - double targetSpeed = 0; - double targetAltitude = 0; + double desiredSpeed = 0; + double desiredAltitude = 0; + wstring desiredSpeedType = L"GS"; + wstring desiredAltitudeType = L"AGL"; list activePath; - Coords activeDestination = Coords(0); + Coords activeDestination = Coords(NULL); int targetID = NULL; + Coords targetLocation = Coords(NULL); bool isTanker = false; bool isAWACS = false; + bool onOff = true; + bool followRoads = false; /********** Options data **********/ wstring ROE = L"Designated"; @@ -224,4 +253,10 @@ protected: bool isLeaderAlive(); virtual void AIloop() = 0; void addMeasure(wstring key, json::value value); + bool isDestinationReached(double threshold); + bool setActiveDestination(); + bool updateActivePath(bool looping); + void goToDestination(wstring enrouteTask = L"nil"); + bool checkTaskFailed(); + void resetTaskFailedCounter(); }; diff --git a/src/core/include/unitsmanager.h b/src/core/include/unitsmanager.h index a851a388..871c9e74 100644 --- a/src/core/include/unitsmanager.h +++ b/src/core/include/unitsmanager.h @@ -11,10 +11,17 @@ public: ~UnitsManager(); Unit* getUnit(int ID); + bool isUnitInGroup(Unit* unit); + bool isUnitGroupLeader(Unit* unit); + Unit* getGroupLeader(int ID); + Unit* getGroupLeader(Unit* unit); + vector getGroupMembers(wstring groupName); void updateExportData(lua_State* L); void updateMissionData(json::value missionData); + void runAILoop(); void getData(json::value& answer, long long time); - void deleteUnit(int ID); + void deleteUnit(int ID, bool explosion); + void acquireControl(int ID); private: map units; diff --git a/src/core/src/aircraft.cpp b/src/core/src/aircraft.cpp index 79f2694d..14c8c005 100644 --- a/src/core/src/aircraft.cpp +++ b/src/core/src/aircraft.cpp @@ -17,59 +17,52 @@ Aircraft::Aircraft(json::value json, int ID) : AirUnit(json, ID) { log("New Aircraft created with ID: " + to_string(ID)); addMeasure(L"category", json::value(getCategory())); - setTargetSpeed(targetSpeed); - setTargetAltitude(targetAltitude); + + double desiredSpeed = knotsToMs(300); + double desiredAltitude = ftToM(20000); + setDesiredSpeed(desiredSpeed); + setDesiredAltitude(desiredAltitude); }; void Aircraft::changeSpeed(wstring change) { if (change.compare(L"stop") == 0) - { setState(State::IDLE); - } else if (change.compare(L"slow") == 0) - setTargetSpeed(getTargetSpeed() - 25 / 1.94384); + setDesiredSpeed(getDesiredSpeed() - knotsToMs(25)); else if (change.compare(L"fast") == 0) - setTargetSpeed(getTargetSpeed() + 25 / 1.94384); + setDesiredSpeed(getDesiredSpeed() + knotsToMs(25)); - if (getTargetSpeed() < 50 / 1.94384) - setTargetSpeed(50 / 1.94384); + if (getDesiredSpeed() < knotsToMs(50)) + setDesiredSpeed(knotsToMs(50)); - goToDestination(); /* Send the command to reach the destination */ + if (state == State::IDLE) + resetTask(); + else + goToDestination(); /* Send the command to reach the destination */ } void Aircraft::changeAltitude(wstring change) { if (change.compare(L"descend") == 0) { - if (getTargetAltitude() > 5000) - setTargetAltitude(getTargetAltitude() - 2500 / 3.28084); - else if (getTargetAltitude() > 0) - setTargetAltitude(getTargetAltitude() - 500 / 3.28084); + if (getDesiredAltitude() > 5000) + setDesiredAltitude(getDesiredAltitude() - ftToM(2500)); + else if (getDesiredAltitude() > 0) + setDesiredAltitude(getDesiredAltitude() - ftToM(500)); } else if (change.compare(L"climb") == 0) { - if (getTargetAltitude() > 5000) - setTargetAltitude(getTargetAltitude() + 2500 / 3.28084); - else if (getTargetAltitude() >= 0) - setTargetAltitude(getTargetAltitude() + 500 / 3.28084); + if (getDesiredAltitude() > 5000) + setDesiredAltitude(getDesiredAltitude() + ftToM(2500)); + else if (getDesiredAltitude() >= 0) + setDesiredAltitude(getDesiredAltitude() + ftToM(500)); } - if (getTargetAltitude() < 0) - setTargetAltitude(0); + if (getDesiredAltitude() < 0) + setDesiredAltitude(0); - goToDestination(); /* Send the command to reach the destination */ -} - -void Aircraft::setTargetSpeed(double newTargetSpeed) { - targetSpeed = newTargetSpeed; - addMeasure(L"targetSpeed", json::value(targetSpeed)); - if (activeDestination != NULL) - goToDestination(); -} - -void Aircraft::setTargetAltitude(double newTargetAltitude) { - targetAltitude = newTargetAltitude; - addMeasure(L"targetAltitude", json::value(targetAltitude)); - if (activeDestination != NULL) - goToDestination(); + if (state == State::IDLE) + resetTask(); + else + goToDestination(); /* Send the command to reach the destination */ } \ No newline at end of file diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index 78d87cdf..06c0808a 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -20,7 +20,7 @@ AirUnit::AirUnit(json::value json, int ID) : Unit(json, ID) void AirUnit::setState(int newState) { - /************ Perform any action required when LEAVING a certain state ************/ + /************ Perform any action required when LEAVING a state ************/ if (newState != state) { switch (state) { case State::IDLE: { @@ -43,12 +43,18 @@ void AirUnit::setState(int newState) case State::REFUEL: { break; } + case State::BOMB_POINT: + case State::CARPET_BOMB: + case State::BOMB_BUILDING: { + setTargetLocation(Coords(NULL)); + break; + } default: break; } } - /************ Perform any action required when ENTERING a certain state ************/ + /************ Perform any action required when ENTERING a state ************/ switch (newState) { case State::IDLE: { clearActivePath(); @@ -90,6 +96,24 @@ void AirUnit::setState(int newState) addMeasure(L"currentState", json::value(L"Refuel")); break; } + case State::BOMB_POINT: { + addMeasure(L"currentState", json::value(L"Bombing point")); + clearActivePath(); + resetActiveDestination(); + break; + } + case State::CARPET_BOMB: { + addMeasure(L"currentState", json::value(L"Carpet bombing")); + clearActivePath(); + resetActiveDestination(); + break; + } + case State::BOMB_BUILDING: { + addMeasure(L"currentState", json::value(L"Bombing building")); + clearActivePath(); + resetActiveDestination(); + break; + } default: break; } @@ -100,69 +124,6 @@ void AirUnit::setState(int newState) state = newState; } -bool AirUnit::isDestinationReached() -{ - if (activeDestination != NULL) - { - double dist = 0; - Geodesic::WGS84().Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, dist); - if (dist < AIR_DEST_DIST_THR) - { - log(unitName + L" destination reached"); - return true; - } - else { - return false; - } - } - else - return true; -} - -bool AirUnit::setActiveDestination() -{ - if (activePath.size() > 0) - { - activeDestination = activePath.front(); - log(unitName + L" active destination set to queue front"); - return true; - } - else - { - activeDestination = Coords(0); - log(unitName + L" active destination set to NULL"); - return false; - } -} - -bool AirUnit::updateActivePath(bool looping) -{ - if (activePath.size() > 0) - { - /* Push the next destination in the queue to the front */ - if (looping) - pushActivePathBack(activePath.front()); - popActivePathFront(); - log(unitName + L" active path front popped"); - return true; - } - else { - return false; - } -} - -void AirUnit::goToDestination(wstring enrouteTask) -{ - if (activeDestination != NULL) - { - Command* command = dynamic_cast(new Move(ID, activeDestination, getTargetSpeed(), getTargetAltitude(), enrouteTask)); - scheduler->appendCommand(command); - setHasTask(true); - } - else - log(unitName + L" error, no active destination!"); -} - void AirUnit::AIloop() { /* State machine */ @@ -170,19 +131,19 @@ void AirUnit::AIloop() case State::IDLE: { currentTask = L"Idle"; - if (!hasTask) + if (!getHasTask()) { std::wostringstream taskSS; if (isTanker) { - taskSS << "{ [1] = { id = 'Tanker' }, [2] = { id = 'Orbit', pattern = 'Race-Track' } }"; + taskSS << "{ [1] = { id = 'Tanker' }, [2] = { id = 'Orbit', pattern = 'Race-Track', altitude = " << desiredAltitude << ", speed = " << desiredSpeed << ", altitudeType = '" << desiredAltitudeType << "' } }"; } else if (isAWACS) { - taskSS << "{ [1] = { id = 'AWACS' }, [2] = { id = 'Orbit', pattern = 'Circle' } }"; + taskSS << "{ [1] = { id = 'AWACS' }, [2] = { id = 'Orbit', pattern = 'Circle', altitude = " << desiredAltitude << ", speed = " << desiredSpeed << ", altitudeType = '" << desiredAltitudeType << "' } }"; } else { - taskSS << "{ id = 'Orbit', pattern = 'Circle' }"; + taskSS << "{ id = 'Orbit', pattern = 'Circle', altitude = " << desiredAltitude << ", speed = " << desiredSpeed << ", altitudeType = '" << desiredAltitudeType << "' }"; } - Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); } @@ -208,7 +169,7 @@ void AirUnit::AIloop() currentTask = L"Reaching destination"; } - if (activeDestination == NULL || !hasTask) + if (activeDestination == NULL || !getHasTask()) { if (!setActiveDestination()) setState(State::IDLE); @@ -216,7 +177,7 @@ void AirUnit::AIloop() goToDestination(enrouteTask); } else { - if (isDestinationReached()) { + if (isDestinationReached(AIR_DEST_DIST_THR)) { if (updateActivePath(looping) && setActiveDestination()) goToDestination(enrouteTask); else @@ -253,7 +214,7 @@ void AirUnit::AIloop() wstring enrouteTask = enrouteTaskSS.str(); currentTask = L"Attacking " + getTargetName(); - if (!hasTask) + if (!getHasTask()) { setActiveDestination(); goToDestination(enrouteTask); @@ -274,7 +235,7 @@ void AirUnit::AIloop() currentTask = L"Following " + getTargetName(); Unit* leader = unitsManager->getUnit(leaderID); - if (!hasTask) { + if (!getHasTask()) { if (leader != nullptr && leader->getAlive() && formationOffset != NULL) { std::wostringstream taskSS; @@ -287,7 +248,7 @@ void AirUnit::AIloop() << "z = " << formationOffset.z << "}," << "}"; - Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); } @@ -297,13 +258,13 @@ void AirUnit::AIloop() case State::REFUEL: { currentTask = L"Refueling"; - if (!hasTask) { + if (!getHasTask()) { if (fuel <= initialFuel) { std::wostringstream taskSS; taskSS << "{" << "id = 'Refuel'" << "}"; - Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); } @@ -312,9 +273,44 @@ void AirUnit::AIloop() } } } + case State::BOMB_POINT: { + currentTask = L"Bombing point"; + + if (!getHasTask()) { + std::wostringstream taskSS; + taskSS << "{id = 'Bombing', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}"; + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); + scheduler->appendCommand(command); + setHasTask(true); + } + } + case State::CARPET_BOMB: { + currentTask = L"Carpet bombing"; + + if (!getHasTask()) { + std::wostringstream taskSS; + taskSS << "{id = 'CarpetBombing', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}"; + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); + scheduler->appendCommand(command); + setHasTask(true); + } + break; + } + case State::BOMB_BUILDING: { + currentTask = L"Bombing building"; + + if (!getHasTask()) { + std::wostringstream taskSS; + taskSS << "{id = 'AttackMapObject', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}"; + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); + scheduler->appendCommand(command); + setHasTask(true); + } + break; + } default: break; } addMeasure(L"currentTask", json::value(currentTask)); -} +} \ No newline at end of file diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index 5eef82e2..38e490f2 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -69,6 +69,7 @@ extern "C" DllExport int coreFrame(lua_State* L) if (unitsManager != nullptr) { unitsManager->updateExportData(L); + unitsManager->runAILoop(); } before = std::chrono::system_clock::now(); } diff --git a/src/core/src/groundunit.cpp b/src/core/src/groundunit.cpp index 545aefc5..58b8fd50 100644 --- a/src/core/src/groundunit.cpp +++ b/src/core/src/groundunit.cpp @@ -17,58 +17,134 @@ GroundUnit::GroundUnit(json::value json, int ID) : Unit(json, ID) { log("New Ground Unit created with ID: " + to_string(ID)); addMeasure(L"category", json::value(getCategory())); - setTargetSpeed(targetSpeed); - setTargetAltitude(targetAltitude); + + double desiredSpeed = 10; + setDesiredSpeed(desiredSpeed); }; +void GroundUnit::setState(int newState) +{ + /************ Perform any action required when LEAVING a state ************/ + if (newState != state) { + switch (state) { + case State::IDLE: { + break; + } + case State::REACH_DESTINATION: { + break; + } + case State::FIRE_AT_AREA: { + setTargetLocation(Coords(NULL)); + break; + } + default: + break; + } + } + + /************ Perform any action required when ENTERING a state ************/ + switch (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::FIRE_AT_AREA: { + addMeasure(L"currentState", json::value(L"Firing at area")); + clearActivePath(); + resetActiveDestination(); + break; + } + default: + break; + } + + resetTask(); + + log(unitName + L" setting state from " + to_wstring(state) + L" to " + to_wstring(newState)); + state = newState; +} + void GroundUnit::AIloop() { - /* Set the active destination to be always equal to the first point of the active path. This is in common with all AI units */ - if (activePath.size() > 0) - { - if (activeDestination != activePath.front()) + switch (state) { + case State::IDLE: { + currentTask = L"Idle"; + if (getHasTask()) + resetTask(); + break; + } + case State::REACH_DESTINATION: { + wstring enrouteTask = L""; + bool looping = false; + + std::wostringstream taskSS; + taskSS << "{ id = 'FollowRoads', value = " << (getFollowRoads() ? "true" : "false") << " }"; + enrouteTask = taskSS.str(); + + if (activeDestination == NULL || !getHasTask()) { - activeDestination = activePath.front(); - Command* command = dynamic_cast(new Move(ID, activeDestination, getTargetSpeed(), getTargetAltitude(), L"nil")); + if (!setActiveDestination()) + setState(State::IDLE); + else + goToDestination(enrouteTask); + } + else { + if (isDestinationReached(GROUND_DEST_DIST_THR)) { + if (updateActivePath(looping) && setActiveDestination()) + goToDestination(enrouteTask); + else + setState(State::IDLE); + } + } + break; + } + case State::FIRE_AT_AREA: { + currentTask = L"Firing at area"; + + if (!getHasTask()) { + std::wostringstream taskSS; + taskSS << "{id = 'FireAtPoint', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << ", radius = 1000}"; + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); + setHasTask(true); } } - else - { - if (activeDestination != NULL) - { - log(unitName + L" no more points in active path"); - activeDestination = Coords(0); // Set the active path to NULL - currentTask = L"Idle"; - } + default: + break; } - /* Ground unit AI Loop */ - if (activeDestination != NULL) - { - double newDist = 0; - Geodesic::WGS84().Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, newDist); - if (newDist < GROUND_DEST_DIST_THR) - { - /* Destination reached */ - popActivePathFront(); - log(unitName + L" destination reached"); - } - } + addMeasure(L"currentTask", json::value(currentTask)); } void GroundUnit::changeSpeed(wstring change) { if (change.compare(L"stop") == 0) - { - - } + setState(State::IDLE); else if (change.compare(L"slow") == 0) - { - - } + setDesiredSpeed(getDesiredSpeed() - knotsToMs(5)); else if (change.compare(L"fast") == 0) - { + setDesiredSpeed(getDesiredSpeed() + knotsToMs(5)); - } + if (getDesiredSpeed() < 0) + setDesiredSpeed(0); +} + +void GroundUnit::setOnOff(bool newOnOff) +{ + Unit::setOnOff(newOnOff); + Command* command = dynamic_cast(new SetOnOff(groupName, onOff)); + scheduler->appendCommand(command); +} + +void GroundUnit::setFollowRoads(bool newFollowRoads) +{ + Unit::setFollowRoads(newFollowRoads); + resetActiveDestination(); /* Reset active destination to apply option*/ } \ No newline at end of file diff --git a/src/core/src/helicopter.cpp b/src/core/src/helicopter.cpp index 7d2d1e20..d728f4c5 100644 --- a/src/core/src/helicopter.cpp +++ b/src/core/src/helicopter.cpp @@ -17,8 +17,11 @@ Helicopter::Helicopter(json::value json, int ID) : AirUnit(json, ID) { log("New Helicopter created with ID: " + to_string(ID)); addMeasure(L"category", json::value(getCategory())); - setTargetSpeed(targetSpeed); - setTargetAltitude(targetAltitude); + + double desiredSpeed = knotsToMs(100); + double desiredAltitude = ftToM(5000); + setDesiredSpeed(desiredSpeed); + setDesiredAltitude(desiredAltitude); }; void Helicopter::changeSpeed(wstring change) @@ -29,11 +32,11 @@ void Helicopter::changeSpeed(wstring change) clearActivePath(); } else if (change.compare(L"slow") == 0) - targetSpeed -= 10 / 1.94384; + desiredSpeed -= knotsToMs(10); else if (change.compare(L"fast") == 0) - targetSpeed += 10 / 1.94384; - if (targetSpeed < 0) - targetSpeed = 0; + desiredSpeed += knotsToMs(10); + if (desiredSpeed < 0) + desiredSpeed = 0; goToDestination(); /* Send the command to reach the destination */ } @@ -42,33 +45,20 @@ void Helicopter::changeAltitude(wstring change) { if (change.compare(L"descend") == 0) { - if (targetAltitude > 100) - targetAltitude -= 100 / 3.28084; - else if (targetAltitude > 0) - targetAltitude -= 10 / 3.28084; + if (desiredAltitude > 100) + desiredAltitude -= ftToM(100); + else if (desiredAltitude > 0) + desiredAltitude -= ftToM(10); } else if (change.compare(L"climb") == 0) { - if (targetAltitude > 100) - targetAltitude += 100 / 3.28084; - else if (targetAltitude >= 0) - targetAltitude += 10 / 3.28084; + if (desiredAltitude > 100) + desiredAltitude += ftToM(100); + else if (desiredAltitude >= 0) + desiredAltitude += ftToM(10); } - if (targetAltitude < 0) - targetAltitude = 0; + if (desiredAltitude < 0) + desiredAltitude = 0; goToDestination(); /* Send the command to reach the destination */ } - - -void Helicopter::setTargetSpeed(double newTargetSpeed) { - targetSpeed = newTargetSpeed; - addMeasure(L"targetSpeed", json::value(targetSpeed)); - goToDestination(); -} - -void Helicopter::setTargetAltitude(double newTargetAltitude) { - targetAltitude = newTargetAltitude; - addMeasure(L"targetAltitude", json::value(targetAltitude)); - goToDestination(); -} \ No newline at end of file diff --git a/src/core/src/navyunit.cpp b/src/core/src/navyunit.cpp index 25f835a6..dc96f7a1 100644 --- a/src/core/src/navyunit.cpp +++ b/src/core/src/navyunit.cpp @@ -17,8 +17,9 @@ NavyUnit::NavyUnit(json::value json, int ID) : Unit(json, ID) { log("New Navy Unit created with ID: " + to_string(ID)); addMeasure(L"category", json::value(getCategory())); - setTargetSpeed(targetSpeed); - setTargetAltitude(targetAltitude); + + double desiredSpeed = 10; + setDesiredSpeed(desiredSpeed); }; void NavyUnit::AIloop() diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index ad46db11..e7a7de4e 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -59,7 +59,8 @@ void Scheduler::handleRequest(wstring key, json::value value) if (key.compare(L"setPath") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) { wstring unitName = unit->getUnitName(); @@ -75,15 +76,9 @@ void Scheduler::handleRequest(wstring key, json::value value) newPath.push_back(dest); } - Unit* unit = unitsManager->getUnit(ID); - if (unit != nullptr) - { - unit->setActivePath(newPath); - unit->setState(State::REACH_DESTINATION); - log(unitName + L" new path set successfully"); - } - else - log(unitName + L" not found, request will be discarded"); + unit->setActivePath(newPath); + unit->setState(State::REACH_DESTINATION); + log(unitName + L" new path set successfully"); } } else if (key.compare(L"smoke") == 0) @@ -111,7 +106,8 @@ void Scheduler::handleRequest(wstring key, json::value value) wstring type = value[L"type"].as_string(); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); - Coords loc; loc.lat = lat; loc.lng = lng; + double altitude = value[L"altitude"].as_double(); + Coords loc; loc.lat = lat; loc.lng = lng; loc.alt = altitude; wstring payloadName = value[L"payloadName"].as_string(); wstring airbaseName = value[L"airbaseName"].as_string(); log(L"Spawning " + coalition + L" air unit of type " + type + L" with payload " + payloadName + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L" " + airbaseName + L")"); @@ -120,9 +116,10 @@ void Scheduler::handleRequest(wstring key, json::value value) else if (key.compare(L"attackUnit") == 0) { int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); int targetID = value[L"targetID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); Unit* target = unitsManager->getUnit(targetID); wstring unitName; @@ -145,12 +142,13 @@ void Scheduler::handleRequest(wstring key, json::value value) else if (key.compare(L"followUnit") == 0) { int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); int leaderID = value[L"targetID"].as_integer(); int offsetX = value[L"offsetX"].as_integer(); int offsetY = value[L"offsetY"].as_integer(); int offsetZ = value[L"offsetZ"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); Unit* leader = unitsManager->getUnit(leaderID); wstring unitName; @@ -174,30 +172,50 @@ void Scheduler::handleRequest(wstring key, json::value value) else if (key.compare(L"changeSpeed") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) unit->changeSpeed(value[L"change"].as_string()); } else if (key.compare(L"changeAltitude") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) unit->changeAltitude(value[L"change"].as_string()); } else if (key.compare(L"setSpeed") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) - unit->setTargetSpeed(value[L"speed"].as_double()); + unit->setDesiredSpeed(value[L"speed"].as_double()); + } + else if (key.compare(L"setSpeedType") == 0) + { + int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); + if (unit != nullptr) + unit->setDesiredSpeedType(value[L"speedType"].as_string()); } else if (key.compare(L"setAltitude") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) - unit->setTargetAltitude(value[L"altitude"].as_double()); + unit->setDesiredAltitude(value[L"altitude"].as_double()); + } + else if (key.compare(L"setAltitudeType") == 0) + { + int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); + if (unit != nullptr) + unit->setDesiredAltitudeType(value[L"altitudeType"].as_string()); } else if (key.compare(L"cloneUnit") == 0) { @@ -211,28 +229,32 @@ void Scheduler::handleRequest(wstring key, json::value value) else if (key.compare(L"setROE") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); wstring ROE = value[L"ROE"].as_string(); unit->setROE(ROE); } else if (key.compare(L"setReactionToThreat") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); 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); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(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(); - Unit* unit = unitsManager->getUnit(ID); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; @@ -241,18 +263,21 @@ void Scheduler::handleRequest(wstring key, json::value value) else if (key.compare(L"deleteUnit") == 0) { int ID = value[L"ID"].as_integer(); - unitsManager->deleteUnit(ID); + bool explosion = value[L"explosion"].as_bool(); + unitsManager->deleteUnit(ID, explosion); } else if (key.compare(L"refuel") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::REFUEL); } else if (key.compare(L"setAdvancedOptions") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) { /* Advanced tasking */ @@ -286,6 +311,75 @@ void Scheduler::handleRequest(wstring key, json::value value) unit->resetActiveDestination(); } } + else if (key.compare(L"setFollowRoads") == 0) + { + int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + bool followRoads = value[L"followRoads"].as_bool(); + Unit* unit = unitsManager->getGroupLeader(ID); + unit->setFollowRoads(followRoads); + } + else if (key.compare(L"setOnOff") == 0) + { + int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + bool onOff = value[L"onOff"].as_bool(); + Unit* unit = unitsManager->getGroupLeader(ID); + unit->setOnOff(onOff); + } + else if (key.compare(L"explosion") == 0) + { + int intensity = value[L"intensity"].as_integer(); + double lat = value[L"location"][L"lat"].as_double(); + double lng = value[L"location"][L"lng"].as_double(); + log(L"Adding " + to_wstring(intensity) + L" explosion at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); + Coords loc; loc.lat = lat; loc.lng = lng; + command = dynamic_cast(new Explosion(intensity, loc)); + } + else if (key.compare(L"bombPoint") == 0) + { + int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + double lat = value[L"location"][L"lat"].as_double(); + double lng = value[L"location"][L"lng"].as_double(); + Coords loc; loc.lat = lat; loc.lng = lng; + Unit* unit = unitsManager->getGroupLeader(ID); + unit->setState(State::BOMB_POINT); + unit->setTargetLocation(loc); + } + else if (key.compare(L"carpetBomb") == 0) + { + int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + double lat = value[L"location"][L"lat"].as_double(); + double lng = value[L"location"][L"lng"].as_double(); + Coords loc; loc.lat = lat; loc.lng = lng; + Unit* unit = unitsManager->getGroupLeader(ID); + unit->setState(State::CARPET_BOMB); + unit->setTargetLocation(loc); + } + else if (key.compare(L"bombBuilding") == 0) + { + int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + double lat = value[L"location"][L"lat"].as_double(); + double lng = value[L"location"][L"lng"].as_double(); + Coords loc; loc.lat = lat; loc.lng = lng; + Unit* unit = unitsManager->getGroupLeader(ID); + unit->setState(State::BOMB_BUILDING); + unit->setTargetLocation(loc); + } + else if (key.compare(L"fireAtArea") == 0) + { + int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + double lat = value[L"location"][L"lat"].as_double(); + double lng = value[L"location"][L"lng"].as_double(); + Coords loc; loc.lat = lat; loc.lng = lng; + Unit* unit = unitsManager->getGroupLeader(ID); + unit->setState(State::FIRE_AT_AREA); + unit->setTargetLocation(loc); + } else { log(L"Unknown command: " + key); diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index b32dc785..0e402933 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -46,19 +46,30 @@ Unit::~Unit() void Unit::initialize(json::value json) { updateExportData(json); + setDefaults(); +} - if (getAI()) { +void Unit::setDefaults(bool force) +{ + const bool isUnitControlledByOlympus = getControlled(); + const bool isUnitAlive = getAlive(); + const bool isUnitLeader = unitsManager->isUnitGroupLeader(this); + const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); + const bool isUnitHuman = getFlags()[L"Human"].as_bool(); + if (isUnitControlledByOlympus && (isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits) && isUnitLeader && !isUnitHuman) { /* 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); + setROE(L"Designated", force); + setReactionToThreat(L"Evade", force); + setEmissionsCountermeasures(L"Defend", force); + setTACAN(TACAN, force); + setRadio(radio, force); + setEPLRS(EPLRS, force); + setGeneralSettings(generalSettings, force); + setOnOff(onOff); + setFollowRoads(followRoads); } } @@ -77,6 +88,24 @@ void Unit::addMeasure(wstring key, json::value value) } } +void Unit::runAILoop() { + /* If the unit is alive and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */ + const bool isUnitControlledByOlympus = getControlled(); + const bool isUnitAlive = getAlive(); + const bool isUnitLeader = unitsManager->isUnitGroupLeader(this); + const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); + const bool isUnitHuman = getFlags()[L"Human"].as_bool(); + + // Keep running the AI loop even if the unit is dead if it is the leader of a group which has other members in it + if (isUnitControlledByOlympus && (isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits) && isUnitLeader && !isUnitHuman) + { + if (checkTaskFailed() && state != State::IDLE && State::LAND) + setState(State::IDLE); + + AIloop(); + } +} + void Unit::updateExportData(json::value json) { /* Compute speed (loGetWorldObjects does not provide speed, we compute it for better performance instead of relying on many lua calls) */ @@ -112,12 +141,8 @@ void Unit::updateExportData(json::value json) setFlags(json[L"Flags"]); /* All units which contain the name "Olympus" are automatically under AI control */ - /* TODO: I don't really like using this method */ - setAI(getUnitName().find(L"Olympus") != wstring::npos); - - /* If the unit is alive and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */ - if (getAI() && getAlive() && getFlags()[L"Human"].as_bool() == false) - AIloop(); + if (getUnitName().find(L"Olympus") != wstring::npos) + setControlled(true); } void Unit::updateMissionData(json::value json) @@ -126,19 +151,23 @@ void Unit::updateMissionData(json::value json) setFuel(int(json[L"fuel"].as_number().to_double() * 100)); if (json.has_object_field(L"ammo")) setAmmo(json[L"ammo"]); - if (json.has_object_field(L"targets")) - setTargets(json[L"targets"]); + if (json.has_object_field(L"contacts")) + setContacts(json[L"contacts"]); if (json.has_boolean_field(L"hasTask")) setHasTask(json[L"hasTask"].as_bool()); } -json::value Unit::getData(long long time) +json::value Unit::getData(long long time, bool sendAll) { auto json = json::value::object(); + /* If the unit is in a group, task & option data is given by the group leader */ + if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) + json = unitsManager->getGroupLeader(this)->getData(time, true); + /********** Base data **********/ json[L"baseData"] = json::value::object(); - for (auto key : { L"AI", L"name", L"unitName", L"groupName", L"alive", L"category"}) + for (auto key : { L"controlled", L"name", L"unitName", L"groupName", L"alive", L"category"}) { if (measures.find(key) != measures.end() && measures[key]->getTime() > time) json[L"baseData"][key] = measures[key]->getValue(); @@ -146,7 +175,7 @@ json::value Unit::getData(long long time) if (json[L"baseData"].size() == 0) json.erase(L"baseData"); - if (alive) { + if (alive || sendAll) { /********** Flight data **********/ json[L"flightData"] = json::value::object(); for (auto key : { L"latitude", L"longitude", L"altitude", L"speed", L"heading" }) @@ -159,7 +188,7 @@ json::value Unit::getData(long long time) /********** Mission data **********/ json[L"missionData"] = json::value::object(); - for (auto key : { L"fuel", L"ammo", L"targets", L"hasTask", L"coalition", L"flags" }) + for (auto key : { L"fuel", L"ammo", L"contacts", L"hasTask", L"coalition", L"flags" }) { if (measures.find(key) != measures.end() && measures[key]->getTime() > time) json[L"missionData"][key] = measures[key]->getValue(); @@ -177,25 +206,28 @@ json::value Unit::getData(long long time) if (json[L"formationData"].size() == 0) json.erase(L"formationData"); - /********** 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" }) - { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"taskData"][key] = measures[key]->getValue(); - } - if (json[L"taskData"].size() == 0) - json.erase(L"taskData"); + /* If the unit is in a group, task & option data is given by the group leader */ + if (unitsManager->isUnitGroupLeader(this)) { + /********** Task data **********/ + json[L"taskData"] = json::value::object(); + for (auto key : { L"currentState", L"currentTask", L"desiredSpeed", L"desiredAltitude", L"desiredSpeedType", L"desiredAltitudeType", L"activePath", L"isTanker", L"isAWACS", L"onOff", L"followRoads", L"targetID", L"targetLocation" }) + { + if (measures.find(key) != measures.end() && measures[key]->getTime() > time) + json[L"taskData"][key] = measures[key]->getValue(); + } + if (json[L"taskData"].size() == 0) + json.erase(L"taskData"); - /********** Options data **********/ - json[L"optionsData"] = json::value::object(); - 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(); + /********** Options data **********/ + json[L"optionsData"] = json::value::object(); + 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(); + } + if (json[L"optionsData"].size() == 0) + json.erase(L"optionsData"); } - if (json[L"optionsData"].size() == 0) - json.erase(L"optionsData"); } return json; @@ -322,8 +354,10 @@ void Unit::resetActiveDestination() void Unit::resetTask() { - Command* command = dynamic_cast(new ResetTask(ID)); + Command* command = dynamic_cast(new ResetTask(groupName)); scheduler->appendCommand(command); + setHasTask(false); + resetTaskFailedCounter(); } void Unit::setFormationOffset(Offset newFormationOffset) @@ -332,10 +366,10 @@ void Unit::setFormationOffset(Offset newFormationOffset) resetTask(); } -void Unit::setROE(wstring newROE) { +void Unit::setROE(wstring newROE, bool force) { addMeasure(L"ROE", json::value(newROE)); - if (ROE != newROE) { + if (ROE != newROE || force) { ROE = newROE; int ROEEnum; @@ -352,15 +386,15 @@ void Unit::setROE(wstring newROE) { else return; - Command* command = dynamic_cast(new SetOption(ID, SetCommandType::ROE, ROEEnum)); + Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::ROE, ROEEnum)); scheduler->appendCommand(command); } } -void Unit::setReactionToThreat(wstring newReactionToThreat) { +void Unit::setReactionToThreat(wstring newReactionToThreat, bool force) { addMeasure(L"reactionToThreat", json::value(newReactionToThreat)); - if (reactionToThreat != newReactionToThreat) { + if (reactionToThreat != newReactionToThreat || force) { reactionToThreat = newReactionToThreat; int reactionToThreatEnum; @@ -377,15 +411,15 @@ void Unit::setReactionToThreat(wstring newReactionToThreat) { else return; - Command* command = dynamic_cast(new SetOption(ID, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum)); + Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum)); scheduler->appendCommand(command); } } -void Unit::setEmissionsCountermeasures(wstring newEmissionsCountermeasures) { +void Unit::setEmissionsCountermeasures(wstring newEmissionsCountermeasures, bool force) { addMeasure(L"emissionsCountermeasures", json::value(newEmissionsCountermeasures)); - if (emissionsCountermeasures != newEmissionsCountermeasures) { + if (emissionsCountermeasures != newEmissionsCountermeasures || force) { emissionsCountermeasures = newEmissionsCountermeasures; int radarEnum; @@ -420,13 +454,13 @@ void Unit::setEmissionsCountermeasures(wstring newEmissionsCountermeasures) { Command* command; - command = dynamic_cast(new SetOption(ID, SetCommandType::RADAR_USING, radarEnum)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::RADAR_USING, radarEnum)); scheduler->appendCommand(command); - command = dynamic_cast(new SetOption(ID, SetCommandType::FLARE_USING, flareEnum)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::FLARE_USING, flareEnum)); scheduler->appendCommand(command); - command = dynamic_cast(new SetOption(ID, SetCommandType::ECM_USING, ECMEnum)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::ECM_USING, ECMEnum)); scheduler->appendCommand(command); } } @@ -450,7 +484,7 @@ void Unit::setIsAWACS(bool newIsAWACS) { setEPLRS(isAWACS); } -void Unit::setTACAN(Options::TACAN newTACAN) { +void Unit::setTACAN(Options::TACAN newTACAN, bool force) { auto json = json::value(); json[L"isOn"] = json::value(newTACAN.isOn); json[L"channel"] = json::value(newTACAN.channel); @@ -458,7 +492,7 @@ void Unit::setTACAN(Options::TACAN newTACAN) { json[L"callsign"] = json::value(newTACAN.callsign); addMeasure(L"TACAN", json); - if (TACAN != newTACAN) + if (TACAN != newTACAN || force) { TACAN = newTACAN; if (TACAN.isOn) { @@ -473,7 +507,7 @@ void Unit::setTACAN(Options::TACAN newTACAN) { << "frequency = " << TACANChannelToFrequency(TACAN.channel, TACAN.XY) << "," << "}" << "}"; - Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); + Command* command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); } else { @@ -483,13 +517,13 @@ void Unit::setTACAN(Options::TACAN newTACAN) { << "params = {" << "}" << "}"; - Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); + Command* command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); } } } -void Unit::setRadio(Options::Radio newRadio) { +void Unit::setRadio(Options::Radio newRadio, bool force) { auto json = json::value(); json[L"frequency"] = json::value(newRadio.frequency); @@ -497,7 +531,7 @@ void Unit::setRadio(Options::Radio newRadio) { json[L"callsignNumber"] = json::value(newRadio.callsignNumber); addMeasure(L"radio", json); - if (radio != newRadio) + if (radio != newRadio || force) { radio = newRadio; @@ -511,7 +545,7 @@ void Unit::setRadio(Options::Radio newRadio) { << "frequency = " << radio.frequency << "," << "}" << "}"; - command = dynamic_cast(new SetCommand(ID, commandSS.str())); + command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); // Clear the stringstream @@ -524,16 +558,16 @@ void Unit::setRadio(Options::Radio newRadio) { << "number = " << radio.callsignNumber << "," << "}" << "}"; - command = dynamic_cast(new SetCommand(ID, commandSS.str())); + command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); } } -void Unit::setEPLRS(bool newEPLRS) +void Unit::setEPLRS(bool newEPLRS, bool force) { //addMeasure(L"EPLRS", json::value(newEPLRS)); // - //if (EPLRS != newEPLRS) { + //if (EPLRS != newEPLRS || force) { // EPLRS = newEPLRS; // // std::wostringstream commandSS; @@ -548,7 +582,7 @@ void Unit::setEPLRS(bool newEPLRS) //} } -void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings) { +void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool force) { auto json = json::value(); json[L"prohibitJettison"] = json::value(newGeneralSettings.prohibitJettison); @@ -563,16 +597,143 @@ void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings) { generalSettings = newGeneralSettings; Command* command; - command = dynamic_cast(new SetOption(ID, SetCommandType::PROHIBIT_AA, generalSettings.prohibitAA)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::PROHIBIT_AA, generalSettings.prohibitAA)); scheduler->appendCommand(command); - command = dynamic_cast(new SetOption(ID, SetCommandType::PROHIBIT_AG, generalSettings.prohibitAG)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::PROHIBIT_AG, generalSettings.prohibitAG)); scheduler->appendCommand(command); - command = dynamic_cast(new SetOption(ID, SetCommandType::PROHIBIT_JETT, generalSettings.prohibitJettison)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::PROHIBIT_JETT, generalSettings.prohibitJettison)); scheduler->appendCommand(command); - command = dynamic_cast(new SetOption(ID, SetCommandType::PROHIBIT_AB, generalSettings.prohibitAfterburner)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::PROHIBIT_AB, generalSettings.prohibitAfterburner)); scheduler->appendCommand(command); - command = dynamic_cast(new SetOption(ID, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn)); scheduler->appendCommand(command); } } +void Unit::setDesiredSpeed(double newDesiredSpeed) { + desiredSpeed = newDesiredSpeed; + addMeasure(L"desiredSpeed", json::value(newDesiredSpeed)); + if (state == State::IDLE) + resetTask(); + else + goToDestination(); /* Send the command to reach the destination */ +} + +void Unit::setDesiredAltitude(double newDesiredAltitude) { + desiredAltitude = newDesiredAltitude; + addMeasure(L"desiredAltitude", json::value(newDesiredAltitude)); + if (state == State::IDLE) + resetTask(); + else + goToDestination(); /* Send the command to reach the destination */ +} + +void Unit::setDesiredSpeedType(wstring newDesiredSpeedType) { + desiredSpeedType = newDesiredSpeedType; + addMeasure(L"desiredSpeedType", json::value(newDesiredSpeedType)); + if (state == State::IDLE) + resetTask(); + else + goToDestination(); /* Send the command to reach the destination */ +} + +void Unit::setDesiredAltitudeType(wstring newDesiredAltitudeType) { + desiredAltitudeType = newDesiredAltitudeType; + addMeasure(L"desiredAltitudeType", json::value(newDesiredAltitudeType)); + if (state == State::IDLE) + resetTask(); + else + goToDestination(); /* Send the command to reach the destination */ +} + +void Unit::goToDestination(wstring enrouteTask) +{ + if (activeDestination != NULL) + { + Command* command = dynamic_cast(new Move(groupName, activeDestination, getDesiredSpeed(), getDesiredSpeedType(), getDesiredAltitude(), getDesiredAltitudeType(), enrouteTask, getCategory())); + scheduler->appendCommand(command); + setHasTask(true); + } +} + +bool Unit::isDestinationReached(double threshold) +{ + if (activeDestination != NULL) + { + /* Check if any unit in the group has reached the point */ + for (auto const& p: unitsManager->getGroupMembers(groupName)) + { + double dist = 0; + Geodesic::WGS84().Inverse(p->getLatitude(), p->getLongitude(), activeDestination.lat, activeDestination.lng, dist); + if (dist < threshold) + { + log(unitName + L" destination reached"); + return true; + } + else { + return false; + } + } + } + else + return true; +} + +bool Unit::setActiveDestination() +{ + if (activePath.size() > 0) + { + activeDestination = activePath.front(); + log(unitName + L" active destination set to queue front"); + return true; + } + else + { + activeDestination = Coords(0); + log(unitName + L" active destination set to NULL"); + return false; + } +} + +bool Unit::updateActivePath(bool looping) +{ + if (activePath.size() > 0) + { + /* Push the next destination in the queue to the front */ + if (looping) + pushActivePathBack(activePath.front()); + popActivePathFront(); + log(unitName + L" active path front popped"); + return true; + } + else { + return false; + } +} + +void Unit::setTargetLocation(Coords newTargetLocation) { + targetLocation = newTargetLocation; + auto json = json::value(); + json[L"latitude"] = json::value(newTargetLocation.lat); + json[L"longitude"] = json::value(newTargetLocation.lng); + addMeasure(L"targetLocation", json::value(json)); +} + +bool Unit::checkTaskFailed() { + if (getHasTask()) + return false; + else { + if (taskCheckCounter > 0) + taskCheckCounter--; + return taskCheckCounter == 0; + } +} + +void Unit::resetTaskFailedCounter() { + taskCheckCounter = TASK_CHECK_INIT_VALUE; +} + +void Unit::setHasTask(bool newHasTask) { + hasTask = newHasTask; + addMeasure(L"hasTask", json::value(newHasTask)); +} \ No newline at end of file diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index 00b1b8cd..64e57c8e 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -95,6 +95,14 @@ void UnitsManager::updateMissionData(json::value missionData) } } +void UnitsManager::runAILoop() { + /* Run the AI Loop on all units */ + for (auto const& unit : units) + { + unit.second->runAILoop(); + } +} + void UnitsManager::getData(json::value& answer, long long time) { auto unitsJson = json::value::object(); @@ -116,3 +124,17 @@ void UnitsManager::deleteUnit(int ID) } } +void UnitsManager::acquireControl(int ID) { + Unit* unit = getUnit(ID); + if (unit != nullptr) { + for (auto const& groupMember : getGroupMembers(unit->getGroupName())) { + if (!groupMember->getControlled()) { + groupMember->setControlled(true); + groupMember->setState(State::IDLE); + groupMember->setDefaults(true); + } + } + } + +} +