From 30a9d4e7302fc7b728a57018cdd8467fb822af91 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 8 Feb 2023 18:26:45 +0100 Subject: [PATCH] Added speed and altitude controls Fixed Clone Added "Land here" command Added ROE and reaction to threat buttons --- client/TODO.txt | 3 +- client/public/stylesheets/slider.css | 47 +++++ client/public/stylesheets/style.css | 1 + .../public/stylesheets/unitcontrolpanel.css | 25 ++- client/src/controls/slider.ts | 83 ++++++++ client/src/dcs/dcs.ts | 84 +++++++- client/src/index.ts | 16 ++ client/src/map/map.ts | 8 + client/src/panels/unitcontrolpanel.ts | 182 +++++++++++++++++- client/src/units/unit.ts | 33 +++- client/src/units/unitsmanager.ts | 79 ++++++-- client/views/unitcontrolpanel.ejs | 39 ++-- installer/DCSOlympus.iss | 2 +- scripts/OlympusCommand.lua | 38 ++-- src/core/include/Commands.h | 70 ++++++- src/core/include/Unit.h | 59 +++--- src/core/include/aircraft.h | 6 +- src/core/include/airunit.h | 4 +- src/core/include/helicopter.h | 6 +- src/core/include/unitsFactory.h | 1 + src/core/src/Commands.cpp | 14 +- src/core/src/Scheduler.cpp | 33 +++- src/core/src/Unit.cpp | 50 ++++- src/core/src/aircraft.cpp | 12 +- src/core/src/airunit.cpp | 33 +++- src/core/src/helicopter.cpp | 13 +- src/core/src/unitsFactory.cpp | 13 ++ 27 files changed, 852 insertions(+), 102 deletions(-) create mode 100644 client/public/stylesheets/slider.css create mode 100644 client/src/controls/slider.ts diff --git a/client/TODO.txt b/client/TODO.txt index cb925eba..43ba41c2 100644 --- a/client/TODO.txt +++ b/client/TODO.txt @@ -5,4 +5,5 @@ explosion wrong name for ground units improve map zIndex fuel is wrong (either 0 or 1, its is casting it to int somewhere) -weapons should not be selectable \ No newline at end of file +weapons should not be selectable +human symbol if user \ No newline at end of file diff --git a/client/public/stylesheets/slider.css b/client/public/stylesheets/slider.css new file mode 100644 index 00000000..cddd49ad --- /dev/null +++ b/client/public/stylesheets/slider.css @@ -0,0 +1,47 @@ +.slider-container { + width: 100%; +} + +.slider { + width: 100%; + -webkit-appearance: none; + appearance: none; + height: 2px; + background: #d3d3d3; + outline: none; + opacity: 0.7; + -webkit-transition: .2s; + transition: opacity .2s; + margin-top: 10px; + margin-bottom: 10px; +} + +.slider:hover { + opacity: 1; +} + +.slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + background: gray; + cursor: pointer; + border-radius: 999px; +} + +.active .slider::-webkit-slider-thumb { + background: #5ca7ff; +} + +.slider::-moz-range-thumb { + width: 20px; + height: 20px; + background: gray; + cursor: pointer; + border-radius: 999px; +} + +.active .slider::-moz-range-thumb { + background: #5ca7ff; +} \ No newline at end of file diff --git a/client/public/stylesheets/style.css b/client/public/stylesheets/style.css index 424efa47..b68fd52a 100644 --- a/client/public/stylesheets/style.css +++ b/client/public/stylesheets/style.css @@ -1,4 +1,5 @@ @import url("button.css"); +@import url("slider.css"); @import url("dropdown.css"); @import url("selectionwheel.css"); diff --git a/client/public/stylesheets/unitcontrolpanel.css b/client/public/stylesheets/unitcontrolpanel.css index 9bcd3ba9..f01d791a 100644 --- a/client/public/stylesheets/unitcontrolpanel.css +++ b/client/public/stylesheets/unitcontrolpanel.css @@ -52,7 +52,7 @@ height: 100%; } -#threat-reaction-buttons-container { +#reaction-to-threat-buttons-container { display: flex; flex-direction: row; flex-wrap: wrap; @@ -106,4 +106,25 @@ color: white; font-size: 13px; width: 100%; -} \ No newline at end of file +} + +.flight-control-slider { + display: flex; + flex-wrap: wrap; + justify-content: space-between; +} + +.flight-control-title { + font-size: 13px; + color: white; +} + +.flight-control-value { + font-size: 14px; + font-weight: 600; + color: gray; +} + +.active .flight-control-value { + color: #5ca7ff; +} diff --git a/client/src/controls/slider.ts b/client/src/controls/slider.ts new file mode 100644 index 00000000..38b2a49e --- /dev/null +++ b/client/src/controls/slider.ts @@ -0,0 +1,83 @@ +export class Slider { + #container: HTMLElement | null; + #callback: CallableFunction; + #slider: HTMLInputElement | null = null; + #value: HTMLElement | null = null; + #minValue: number; + #maxValue: number; + #minValueDiv: HTMLElement | null = null; + #maxValueDiv: HTMLElement | null = null; + #unit: string; + #display: string = ""; + + constructor(ID: string, minValue: number, maxValue: number, unit: string, callback: CallableFunction) { + this.#container = document.getElementById(ID); + this.#callback = callback; + this.#minValue = minValue; + this.#maxValue = maxValue; + this.#unit = unit; + if (this.#container != null) { + this.#display = this.#container.style.display; + this.#slider = this.#container.querySelector("input"); + if (this.#slider != null) + { + this.#slider.addEventListener("input", (e: any) => this.#onInput()); + this.#slider.addEventListener("mouseup", (e: any) => this.#onFinalize()); + } + this.#value = this.#container.querySelector("#value"); + } + } + + #onValue() + { + if (this.#value != null && this.#slider != null) + this.#value.innerHTML = this.#minValue + Math.round(parseFloat(this.#slider.value) / 100 * (this.#maxValue - this.#minValue)) + this.#unit + this.setActive(true); + } + + #onInput() + { + this.#onValue(); + } + + #onFinalize() + { + if (this.#slider != null) + this.#callback(this.#minValue + parseFloat(this.#slider.value) / 100 * (this.#maxValue - this.#minValue)); + } + + show() + { + if (this.#container != null) + this.#container.style.display = this.#display; + } + + hide() + { + if (this.#container != null) + this.#container.style.display = 'none'; + } + + setActive(newActive: boolean) + { + if (this.#container) + { + this.#container.classList.toggle("active", newActive); + if (!newActive && this.#value != null) + this.#value.innerHTML = "Mixed values" + } + } + + setMinMax(newMinValue: number, newMaxValue: number) + { + this.#minValue = newMinValue; + this.#maxValue = newMaxValue; + } + + setValue(newValue: number) + { + if (this.#slider != null) + this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * 100); + this.#onValue() + } +} \ No newline at end of file diff --git a/client/src/dcs/dcs.ts b/client/src/dcs/dcs.ts index adf8bf17..c9ae53f7 100644 --- a/client/src/dcs/dcs.ts +++ b/client/src/dcs/dcs.ts @@ -99,7 +99,7 @@ export function attackUnit(ID: number, targetID: number) { xhr.send(JSON.stringify(data)); } -export function cloneUnit(ID: number) { +export function cloneUnit(ID: number, latlng: L.LatLng) { var xhr = new XMLHttpRequest(); xhr.open("PUT", RESTaddress); xhr.setRequestHeader("Content-Type", "application/json"); @@ -109,12 +109,28 @@ export function cloneUnit(ID: number) { } }; - var command = { "ID": ID }; + var command = { "ID": ID, "location": latlng }; var data = { "cloneUnit": command } xhr.send(JSON.stringify(data)); } +export function landAt(ID: number, latlng: L.LatLng) { + var xhr = new XMLHttpRequest(); + xhr.open("PUT", RESTaddress); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + //console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " cloned"); + } + }; + + var command = { "ID": ID, "location": latlng }; + var data = { "landAt": command } + + xhr.send(JSON.stringify(data)); +} + export function changeSpeed(ID: number, speedChange: string) { var xhr = new XMLHttpRequest(); xhr.open("PUT", RESTaddress); @@ -131,6 +147,22 @@ export function changeSpeed(ID: number, speedChange: string) { xhr.send(JSON.stringify(data)); } +export function setSpeed(ID: number, speed: number) { + var xhr = new XMLHttpRequest(); + xhr.open("PUT", RESTaddress); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + //console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange); + } + }; + + var command = {"ID": ID, "speed": speed} + var data = {"setSpeed": command} + + xhr.send(JSON.stringify(data)); +} + export function changeAltitude(ID: number, altitudeChange: string) { var xhr = new XMLHttpRequest(); xhr.open("PUT", RESTaddress); @@ -147,6 +179,22 @@ export function changeAltitude(ID: number, altitudeChange: string) { xhr.send(JSON.stringify(data)); } +export function setAltitude(ID: number, altitude: number) { + var xhr = new XMLHttpRequest(); + xhr.open("PUT", RESTaddress); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + //console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange); + } + }; + + var command = {"ID": ID, "altitude": altitude} + var data = {"setAltitude": command} + + xhr.send(JSON.stringify(data)); +} + export function createFormation(ID: number, isLeader: boolean, wingmenIDs: number[]) { var xhr = new XMLHttpRequest(); xhr.open("PUT", RESTaddress); @@ -160,5 +208,37 @@ export function createFormation(ID: number, isLeader: boolean, wingmenIDs: numbe var command = {"ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader} var data = {"setLeader": command} + xhr.send(JSON.stringify(data)); +} + +export function setROE(ID: number, ROE: string) { + var xhr = new XMLHttpRequest(); + xhr.open("PUT", RESTaddress); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + //console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange); + } + }; + + var command = {"ID": ID, "ROE": ROE} + var data = {"setROE": command} + + xhr.send(JSON.stringify(data)); +} + +export function setReactionToThreat(ID: number, reactionToThreat: string) { + var xhr = new XMLHttpRequest(); + xhr.open("PUT", RESTaddress); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + //console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange); + } + }; + + var command = {"ID": ID, "reactionToThreat": reactionToThreat} + var data = {"setReactionToThreat": command} + xhr.send(JSON.stringify(data)); } \ No newline at end of file diff --git a/client/src/index.ts b/client/src/index.ts index 10fbba6f..586cf390 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -10,6 +10,7 @@ import { Button } from "./controls/button"; import { MissionData } from "./missiondata/missiondata"; import { UnitControlPanel } from "./panels/unitcontrolpanel"; import { MouseInfoPanel } from "./panels/mouseInfoPanel"; +import { Slider } from "./controls/slider"; /* TODO: should this be a class? */ var map: Map; @@ -36,6 +37,9 @@ var aiVisibilityButton: Button; var weaponVisibilityButton: Button; var deadVisibilityButton: Button; +var altitudeSlider: Slider; +var airspeedSlider: Slider; + var connected: boolean; var activeCoalition: string; @@ -59,6 +63,10 @@ function setup() { climbButton = new Button("climb-button", ["images/buttons/climb.svg"], () => { getUnitsManager().selectedUnitsChangeAltitude("climb"); }); descendButton = new Button("descend-button", ["images/buttons/descend.svg"], () => { getUnitsManager().selectedUnitsChangeAltitude("descend"); }); + /* Unit control sliders */ + altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => getUnitsManager().selectedUnitsSetAltitude(value * 0.3048)); + airspeedSlider = new Slider("airspeed-slider", 0, 100, "kts", (value: number) => getUnitsManager().selectedUnitsSetSpeed(value / 1.94384)); + /* Visibility buttons */ userVisibilityButton = new Button("user-visibility-button", ["images/buttons/user-full.svg", "images/buttons/user-partial.svg", "images/buttons/user-none.svg", "images/buttons/user-hidden.svg"], () => { }); aiVisibilityButton = new Button("ai-visibility-button", ["images/buttons/ai-full.svg", "images/buttons/ai-partial.svg", "images/buttons/ai-none.svg", "images/buttons/ai-hidden.svg"], () => { }); @@ -184,4 +192,12 @@ export function getVisibilitySettings() { return visibility; } +export function getVisibilityButtons() { + return {user: userVisibilityButton, ai: aiVisibilityButton, weapon: weaponVisibilityButton, dead: deadVisibilityButton} +} + +export function getUnitControlSliders() { + return {altitude: altitudeSlider, airspeed: airspeedSlider} +} + window.onload = setup; \ No newline at end of file diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 1cf85697..5dbc9089 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -154,6 +154,14 @@ export class Map extends L.Map { getSelectionScroll().hide(); } + getMousePosition() { + return this.#lastMousePosition; + } + + getMouseCoordinates() { + return this.containerPointToLatLng(this.#lastMousePosition); + } + /* Event handlers */ #onClick(e: any) { if (!this.#preventLeftClick) { diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index e4851602..9a143ffd 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -1,7 +1,7 @@ import { imageOverlay } from "leaflet"; -import { getUnitsManager } from ".."; +import { getUnitControlSliders, getUnitsManager } from ".."; import { ConvertDDToDMS, rad2deg } from "../other/utils"; -import { AirUnit, Unit } from "../units/unit"; +import { Aircraft, AirUnit, GroundUnit, Helicopter, NavyUnit, Unit } from "../units/unit"; export class UnitControlPanel { #element: HTMLElement @@ -21,6 +21,25 @@ export class UnitControlPanel { var undoButton = formationCreationContainer.querySelector("#undo-formation"); undoButton?.addEventListener("click", () => getUnitsManager().selectedUnitsUndoFormation()); } + var ROEButtonsContainer = (this.#element.querySelector("#roe-buttons-container")); + if (ROEButtonsContainer != null) + { + (ROEButtonsContainer.querySelector("#free"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Free")); + (ROEButtonsContainer.querySelector("#designated-free"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Designated free")); + (ROEButtonsContainer.querySelector("#designated"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Designated")); + (ROEButtonsContainer.querySelector("#return"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Return")); + (ROEButtonsContainer.querySelector("#hold"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Hold")); + } + + var reactionToThreatButtonsContainer = (this.#element.querySelector("#reaction-to-threat-buttons-container")); + if (reactionToThreatButtonsContainer != null) + { + (reactionToThreatButtonsContainer.querySelector("#none"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("None")); + (reactionToThreatButtonsContainer.querySelector("#passive"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("Passive")); + (reactionToThreatButtonsContainer.querySelector("#evade"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("Evade")); + (reactionToThreatButtonsContainer.querySelector("#escape"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("Escape")); + (reactionToThreatButtonsContainer.querySelector("#abort"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("Abort")); + } this.hide(); } } @@ -41,8 +60,83 @@ export class UnitControlPanel { if (selectedUnitsContainer != null && formationCreationContainer != null) { this.#addUnitsButtons(units, selectedUnitsContainer); + this.#showFlightControlSliders(units); this.#showFormationButtons(units, formationCreationContainer); } + + var ROEButtonsContainer = (this.#element.querySelector("#roe-buttons-container")); + if (ROEButtonsContainer != null) + { + (ROEButtonsContainer.querySelector("#free"))?.classList.toggle("white", this.#getROE(units) === "Free"); + (ROEButtonsContainer.querySelector("#designated-free"))?.classList.toggle("white", this.#getROE(units) === "Designated free"); + (ROEButtonsContainer.querySelector("#designated"))?.classList.toggle("white", this.#getROE(units) === "Designated"); + (ROEButtonsContainer.querySelector("#return"))?.classList.toggle("white", this.#getROE(units) === "Return"); + (ROEButtonsContainer.querySelector("#hold"))?.classList.toggle("white", this.#getROE(units) === "Hold"); + } + + var reactionToThreatButtonsContainer = (this.#element.querySelector("#reaction-to-threat-buttons-container")); + if (reactionToThreatButtonsContainer != null) + { + (reactionToThreatButtonsContainer.querySelector("#none"))?.classList.toggle("white", this.#getReactionToThreat(units) === "None"); + (reactionToThreatButtonsContainer.querySelector("#passive"))?.classList.toggle("white", this.#getReactionToThreat(units) === "Passive"); + (reactionToThreatButtonsContainer.querySelector("#evade"))?.classList.toggle("white", this.#getReactionToThreat(units) === "Evade"); + (reactionToThreatButtonsContainer.querySelector("#escape"))?.classList.toggle("white", this.#getReactionToThreat(units) === "Escape"); + (reactionToThreatButtonsContainer.querySelector("#abort"))?.classList.toggle("white", this.#getReactionToThreat(units) === "Abort"); + } + } + } + + #showFlightControlSliders(units: Unit[]) + { + var sliders = getUnitControlSliders(); + sliders.airspeed.show(); + sliders.altitude.show(); + + if (this.#checkAllUnitsAircraft(units)) + { + sliders.airspeed.setMinMax(100, 600); + sliders.altitude.setMinMax(0, 50000); + } + else if (this.#checkAllUnitsHelicopter(units)) + { + sliders.airspeed.setMinMax(0, 200); + sliders.altitude.setMinMax(0, 10000); + } + else if (this.#checkAllUnitsGroundUnit(units)) + { + sliders.airspeed.setMinMax(0, 60); + sliders.altitude.hide(); + } + else if (this.#checkAllUnitsNavyUnit(units)) + { + sliders.airspeed.setMinMax(0, 60); + sliders.altitude.hide(); + } + else { + sliders.airspeed.hide(); + sliders.altitude.hide(); + } + + var targetSpeed = this.#getTargetAirspeed(units); + if (targetSpeed != null) + { + sliders.airspeed.setActive(true); + sliders.airspeed.setValue(targetSpeed * 1.94384); + } + else + { + sliders.airspeed.setActive(false); + } + + var targetAltitude = this.#getTargetAltitude(units); + if (targetAltitude != null) + { + sliders.altitude.setActive(true); + sliders.altitude.setValue(targetAltitude / 0.3048); + } + else + { + sliders.altitude.setActive(false); } } @@ -157,6 +251,38 @@ export class UnitControlPanel { return true } + #checkAllUnitsAircraft(units: Unit[]) + { + for (let unit of units) + if (!(unit instanceof Aircraft)) + return false + return true + } + + #checkAllUnitsHelicopter(units: Unit[]) + { + for (let unit of units) + if (!(unit instanceof Helicopter)) + return false + return true + } + + #checkAllUnitsGroundUnit(units: Unit[]) + { + for (let unit of units) + if (!(unit instanceof GroundUnit)) + return false + return true + } + + #checkAllUnitsNavyUnit(units: Unit[]) + { + for (let unit of units) + if (!(unit instanceof NavyUnit)) + return false + return true + } + #checkAllUnitsSameFormation(units: Unit[]) { var leaderFound = false; @@ -182,4 +308,56 @@ export class UnitControlPanel { return true return false } + + #getTargetAirspeed(units: Unit[]) + { + var airspeed = null; + for (let unit of units) + { + if (unit.targetSpeed != airspeed && airspeed != null) + return null + else + airspeed = unit.targetSpeed; + } + return airspeed; + } + + #getTargetAltitude(units: Unit[]) + { + var altitude = null; + for (let unit of units) + { + if (unit.targetAltitude != altitude && altitude != null) + return null + else + altitude = unit.targetAltitude; + } + return altitude; + } + + #getROE(units: Unit[]) + { + var ROE = null; + for (let unit of units) + { + if (unit.ROE !== ROE && ROE != null) + return null + else + ROE = unit.ROE; + } + return ROE; + } + + #getReactionToThreat(units: Unit[]) + { + var reactionToThreat = null; + for (let unit of units) + { + if (unit.reactionToThreat !== reactionToThreat && reactionToThreat != null) + return null + else + reactionToThreat = unit.reactionToThreat; + } + return reactionToThreat; + } } \ No newline at end of file diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index d27d847d..723aac23 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -2,7 +2,7 @@ import { Marker, LatLng, Polyline, Icon } from 'leaflet'; import { ConvertDDToDMS } from '../other/utils'; import { getMap, getUnitsManager, getVisibilitySettings } from '..'; import { UnitMarker, MarkerOptions, AircraftMarker, HelicopterMarker, GroundUnitMarker, NavyUnitMarker, WeaponMarker } from './unitmarker'; -import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader } from '../dcs/dcs'; +import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, landAt, setAltitude, setReactionToThreat, setROE, setSpeed } from '../dcs/dcs'; var pathIcon = new Icon({ iconUrl: 'images/marker-icon.png', @@ -36,6 +36,10 @@ export class Unit { leaderID: number = 0; wingmen: Unit[] = []; wingmenIDs: number[] = []; + targetSpeed: number = 0; + targetAltitude: number = 0; + ROE: string = ""; + reactionToThreat: string = ""; #selectable: boolean; #selected: boolean = false; @@ -296,7 +300,6 @@ export class Unit { } } - attackUnit(targetID: number) { /* Call DCS attackUnit function */ if (this.ID != targetID) { @@ -307,7 +310,11 @@ export class Unit { } } - + landAt(latlng: LatLng) + { + landAt(this.ID, latlng); + } + changeSpeed(speedChange: string) { changeSpeed(this.ID, speedChange); @@ -318,6 +325,26 @@ export class Unit { changeAltitude(this.ID, altitudeChange); } + setSpeed(speed: number) + { + setSpeed(this.ID, speed); + } + + setAltitude(altitude: number) + { + setAltitude(this.ID, altitude); + } + + setROE(ROE: string) + { + setROE(this.ID, ROE); + } + + setReactionToThreat(reactionToThreat: string) + { + setReactionToThreat(this.ID, reactionToThreat); + } + /* setformation(formation) { diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index b85aeaa5..1149661b 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -15,6 +15,17 @@ export class UnitsManager { document.addEventListener('paste', () => this.pasteUnits()); } + #updateUnitControlPanel() { + /* Update the unit control panel */ + if (this.getSelectedUnits().length > 0) { + getUnitControlPanel().show(); + getUnitControlPanel().update(this.getSelectedLeaders().concat(this.getSelectedSingletons())); + } + else { + getUnitControlPanel().hide(); + } + } + getUnits() { return this.#units; } @@ -167,7 +178,11 @@ export class UnitsManager { selectedUnitsLandAt(latlng: LatLng) { - + var selectedUnits = this.getSelectedUnits(); + for (let idx in selectedUnits) + { + selectedUnits[idx].landAt(latlng); + } } selectedUnitsChangeSpeed(speedChange: string) @@ -177,6 +192,8 @@ export class UnitsManager { { selectedUnits[idx].changeSpeed(speedChange); } + + setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail } selectedUnitsChangeAltitude(altitudeChange: string) @@ -186,8 +203,51 @@ export class UnitsManager { { selectedUnits[idx].changeAltitude(altitudeChange); } + + setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail } + selectedUnitsSetSpeed(speed: number) + { + var selectedUnits = this.getSelectedUnits(); + for (let idx in selectedUnits) + { + selectedUnits[idx].setSpeed(speed); + } + } + + selectedUnitsSetAltitude(altitude: number) + { + var selectedUnits = this.getSelectedUnits(); + for (let idx in selectedUnits) + { + selectedUnits[idx].setAltitude(altitude); + } + } + + selectedUnitsSetROE(ROE: string) + { + var selectedUnits = this.getSelectedUnits(); + for (let idx in selectedUnits) + { + selectedUnits[idx].setROE(ROE); + } + + setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail + } + + selectedUnitsSetReactionToThreat(reactionToThreat: string) + { + var selectedUnits = this.getSelectedUnits(); + for (let idx in selectedUnits) + { + selectedUnits[idx].setReactionToThreat(reactionToThreat); + } + + setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail + } + + copyUnits() { this.#copiedUnits = this.getSelectedUnits(); @@ -198,7 +258,7 @@ export class UnitsManager { for (let idx in this.#copiedUnits) { var unit = this.#copiedUnits[idx]; - cloneUnit(unit.ID); + cloneUnit(unit.ID, getMap().getMouseCoordinates()); } } @@ -259,7 +319,7 @@ export class UnitsManager { console.log("At least 2 units must be selected to create a formation."); } } - setTimeout(() => this.#updateUnitControlPanel(), 1000); // TODO find better method, may fail + setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail } selectedUnitsUndoFormation(ID: number | null = null) @@ -268,17 +328,6 @@ export class UnitsManager { { leader.setLeader(false); } - setTimeout(() => this.#updateUnitControlPanel(), 1000); // TODO find better method, may fail - } - - #updateUnitControlPanel() { - /* Update the unit control panel */ - if (this.getSelectedUnits().length > 0) { - getUnitControlPanel().show(); - getUnitControlPanel().update(this.getSelectedLeaders().concat(this.getSelectedSingletons())); - } - else { - getUnitControlPanel().hide(); - } + setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail } } \ No newline at end of file diff --git a/client/views/unitcontrolpanel.ejs b/client/views/unitcontrolpanel.ejs index ab0f7820..fba42229 100644 --- a/client/views/unitcontrolpanel.ejs +++ b/client/views/unitcontrolpanel.ejs @@ -18,7 +18,20 @@
- + \ No newline at end of file diff --git a/installer/DCSOlympus.iss b/installer/DCSOlympus.iss index f123559f..61c67383 100644 --- a/installer/DCSOlympus.iss +++ b/installer/DCSOlympus.iss @@ -2,7 +2,7 @@ [Setup] AppName=DCS Olympus -AppVerName=DCS Olympus Alpha v0.0.3 +AppVerName=DCS Olympus Alpha v0.0.5 DefaultDirName={usersavedgames}\DCS.openbeta DefaultGroupName=DCSOlympus OutputBaseFilename=DCSOlympus diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 790bd1a5..0ea22476 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -82,12 +82,20 @@ function Olympus.move(ID, lat, lng, altitude, speed, category, taskOptions) if unit then if category == "Aircraft" then local startPoint = mist.getLeadPos(unit:getGroup()) - local endPoint = coord.LLtoLO(lat, lng, 0) + local endPoint = coord.LLtoLO(lat, lng, 0) - local path = { - [1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO'), - [2] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO') - } + local path = {} + if taskOptions and taskOptions['id'] == 'Land' then + path = { + [1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO'), + [2] = mist.fixedWing.buildWP(endPoint, landing, speed, 0, 'AGL') + } + else + path = { + [1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO'), + [2] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO') + } + end -- If a task exists assign it to the controller if taskOptions then @@ -273,7 +281,7 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions) task = 'CAP', } - mist.dynAdd(vars) + local newGroup = mist.dynAdd(vars) -- Save the payload to be reused in case the unit is cloned. TODO: save by ID not by name (it works but I like consistency) Olympus.payloadRegistry[vars.name] = payload @@ -282,22 +290,30 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions) end -- Clones a unit by ID. Will clone the unit with the same original payload as the source unit. TODO: only works on Olympus unit not ME units. -function Olympus.clone(ID) +function Olympus.clone(ID, lat, lng) Olympus.notify("Olympus.clone " .. ID, 2) local unit = Olympus.getUnitByID(ID) if unit then local coalition = Olympus.getCoalitionByCoalitionID(unit:getCoalition()) - local lat, lng, alt = coord.LOtoLL(unit:getPoint()) -- TODO: only works on Aircraft local spawnOptions = { - payload = Olympus.payloadRegistry[unitName] + payload = Olympus.payloadRegistry[unit:getName()] } - Olympus.spawnAircraft(coalition, unit:getTypeName(), lat + 0.001, lng + 0.001, spawnOptions) + Olympus.spawnAircraft(coalition, unit:getTypeName(), lat, lng, spawnOptions) end Olympus.notify("Olympus.clone completed successfully", 2) end +function Olympus.delete(ID, lat, lng) + Olympus.notify("Olympus.delete " .. ID, 2) + local unit = Olympus.getUnitByID(ID) + if unit then + unit:destroy(); + Olympus.notify("Olympus.delete completed successfully", 2) + end +end + function Olympus.setTask(ID, taskOptions) Olympus.notify("Olympus.setTask " .. ID .. " " .. Olympus.serializeTable(taskOptions), 2) local unit = Olympus.getUnitByID(ID) @@ -337,7 +353,6 @@ function Olympus.setOption(ID, optionID, optionValue) end end - function Olympus.serializeTable(val, name, skipnewlines, depth) skipnewlines = skipnewlines or false depth = depth or 0 @@ -370,5 +385,4 @@ function Olympus.serializeTable(val, name, skipnewlines, depth) return tmp end - Olympus.notify("OlympusCommand script loaded successfully", 2) \ No newline at end of file diff --git a/src/core/include/Commands.h b/src/core/include/Commands.h index 23fb1eff..95ccefbc 100644 --- a/src/core/include/Commands.h +++ b/src/core/include/Commands.h @@ -11,6 +11,54 @@ namespace CommandType { enum CommandTypes { NO_TYPE, MOVE, SMOKE, SPAWN_AIR, SPAWN_GROUND, CLONE, FOLLOW, RESET_TASK, SET_OPTION, SET_COMMAND, SET_TASK }; }; +namespace SetCommandType { + enum SetCommandTypes { + ROE = 0, + REACTION_ON_THREAT = 1, + RADAR_USING = 3, + FLARE_USING = 4, + Formation = 5, + RTB_ON_BINGO = 6, + SILENCE = 7, + RTB_ON_OUT_OF_AMMO = 10, + ECM_USING = 13, + PROHIBIT_AA = 14, + PROHIBIT_JETT = 15, + PROHIBIT_AB = 16, + PROHIBIT_AG = 17, + MISSILE_ATTACK = 18, + PROHIBIT_WP_PASS_REPORT = 19, + OPTION_RADIO_USAGE_CONTACT = 21, + OPTION_RADIO_USAGE_ENGAGE = 22, + OPTION_RADIO_USAGE_KILL = 23, + JETT_TANKS_IF_EMPTY = 25, + FORCED_ATTACK = 26 + }; +} + +namespace ROE { + enum ROEs { + WEAPON_FREE = 0, + OPEN_FIRE_WEAPON_FREE = 1, + OPEN_FIRE = 2, + RETURN_FIRE = 3, + WEAPON_HOLD = 4, + }; +} + +namespace ReactionToThreat { + enum ReactionToThreats { + NO_REACTION = 0, + PASSIVE_DEFENCE = 1, + EVADE_FIRE = 2, + BYPASS_AND_ESCAPE = 3, + ALLOW_ABORT_MISSION = 4 + }; +} + + + + /* Base command class */ class Command { @@ -117,14 +165,32 @@ private: class Clone : public Command { public: - Clone(int ID) : - ID(ID) + Clone(int ID, Coords location) : + ID(ID), + location(location) { priority = CommandPriority::LOW; type = CommandType::CLONE; }; virtual wstring getString(lua_State* L); +private: + const int ID; + const Coords location; +}; + +/* Delete unit command */ +class Delete : public Command +{ +public: + Delete(int ID) : + ID(ID) + { + priority = CommandPriority::HIGH; + type = CommandType::CLONE; + }; + virtual wstring getString(lua_State* L); + private: const int ID; }; diff --git a/src/core/include/Unit.h b/src/core/include/Unit.h index 637a0576..3b99897b 100644 --- a/src/core/include/Unit.h +++ b/src/core/include/Unit.h @@ -5,7 +5,7 @@ #include "luatools.h" namespace State { - enum States { IDLE, REACH_DESTINATION, ATTACK, WINGMAN, FOLLOW, RTB, REFUEL, AWACS, EWR, TANKER, RUN_AWAY }; + enum States { IDLE, REACH_DESTINATION, ATTACK, WINGMAN, FOLLOW, LAND, REFUEL, AWACS, EWR, TANKER, RUN_AWAY }; }; class Unit @@ -31,6 +31,9 @@ public: void setWingmen(vector newWingmen) { wingmen = newWingmen; } void setFormation(wstring newFormation) { formation = newFormation; } void setFormationOffset(Offset formationOffset); + void setROE(wstring newROE); + void setReactionToThreat(wstring newReactionToThreat); + void landAt(Coords loc); int getID() { return ID; } wstring getName() { return name; } @@ -65,33 +68,35 @@ public: protected: int ID; - int state = State::IDLE; - bool hasTask = false; - bool AI = false; - bool alive = true; - wstring name = L"undefined"; - wstring unitName = L"undefined"; - wstring groupName = L"undefined"; - json::value type = json::value::null(); - int country = NULL; - int coalitionID = NULL; - double latitude = NULL; - double longitude = NULL; - double altitude = NULL; - double heading = NULL; - double speed = NULL; - json::value flags = json::value::null(); - int targetID = NULL; - wstring currentTask = L""; - bool isLeader = false; - bool isWingman = false; - Offset formationOffset = Offset(NULL); - wstring formation = L""; - Unit* leader = nullptr; + int state = State::IDLE; + bool hasTask = false; + bool AI = false; + bool alive = true; + wstring name = L"undefined"; + wstring unitName = L"undefined"; + wstring groupName = L"undefined"; + json::value type = json::value::null(); + int country = NULL; + int coalitionID = NULL; + double latitude = NULL; + double longitude = NULL; + double altitude = NULL; + double heading = NULL; + double speed = NULL; + json::value flags = json::value::null(); + int targetID = NULL; + wstring currentTask = L""; + bool isLeader = false; + bool isWingman = false; + Offset formationOffset = Offset(NULL); + wstring formation = L""; + Unit* leader = nullptr; + wstring ROE = L""; + wstring reactionToThreat = L""; vector wingmen; - double targetSpeed = 0; - double targetAltitude = 0; - double fuel = 0; + double targetSpeed = 0; + double targetAltitude = 0; + double fuel = 0; json::value ammo; json::value targets; diff --git a/src/core/include/aircraft.h b/src/core/include/aircraft.h index d9f6c4a2..6d15e4f6 100644 --- a/src/core/include/aircraft.h +++ b/src/core/include/aircraft.h @@ -12,8 +12,10 @@ public: virtual void changeAltitude(wstring change); virtual double getTargetSpeed() { return targetSpeed; }; virtual double getTargetAltitude() { return targetAltitude; }; + virtual void setTargetSpeed(double newTargetSpeed); + virtual void setTargetAltitude(double newTargetAltitude); protected: - double targetSpeed = 150; - double targetAltitude = 5000; + double targetSpeed = 300 / 1.94384; + double targetAltitude = 20000 * 0.3048; }; \ No newline at end of file diff --git a/src/core/include/airunit.h b/src/core/include/airunit.h index 968a7ec9..550bbccf 100644 --- a/src/core/include/airunit.h +++ b/src/core/include/airunit.h @@ -15,8 +15,8 @@ public: virtual wstring getCategory() = 0; virtual void changeSpeed(wstring change) {}; virtual void changeAltitude(wstring change) {}; - virtual void setTargetSpeed(double newTargetSpeed); - virtual void setTargetAltitude(double newTargetAltitude); + virtual void setTargetSpeed(double newTargetSpeed) {}; + virtual void setTargetAltitude(double newTargetAltitude) {}; protected: virtual void AIloop(); diff --git a/src/core/include/helicopter.h b/src/core/include/helicopter.h index 9573f9b4..50a0e9c2 100644 --- a/src/core/include/helicopter.h +++ b/src/core/include/helicopter.h @@ -12,8 +12,10 @@ public: virtual void changeAltitude(wstring change); virtual double getTargetSpeed() { return targetSpeed; }; virtual double getTargetAltitude() { return targetAltitude; }; + virtual void setTargetSpeed(double newTargetSpeed); + virtual void setTargetAltitude(double newTargetAltitude); protected: - double targetSpeed = 50; - double targetAltitude = 1000; + double targetSpeed = 100 / 1.94384; + double targetAltitude = 5000 * 0.3048; }; \ No newline at end of file diff --git a/src/core/include/unitsFactory.h b/src/core/include/unitsFactory.h index b1f7d978..07b0d354 100644 --- a/src/core/include/unitsFactory.h +++ b/src/core/include/unitsFactory.h @@ -14,6 +14,7 @@ public: void updateExportData(lua_State* L); void updateMissionData(json::value missionData); void updateAnswer(json::value& answer); + void deleteUnit(int ID); private: map units; diff --git a/src/core/src/Commands.cpp b/src/core/src/Commands.cpp index 7f697dc2..ef5a7535 100644 --- a/src/core/src/Commands.cpp +++ b/src/core/src/Commands.cpp @@ -69,7 +69,19 @@ wstring Clone::getString(lua_State* L) { std::wostringstream commandSS; commandSS.precision(10); - commandSS << "Olympus.clone, " + commandSS << "Olympus.clone, " + << ID << ", " + << location.lat << ", " + << location.lng; + return commandSS.str(); +} + +/* Delete unit command */ +wstring Delete::getString(lua_State* L) +{ + std::wostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.delete, " << ID; return commandSS.str(); } diff --git a/src/core/src/Scheduler.cpp b/src/core/src/Scheduler.cpp index f6909d01..254137e1 100644 --- a/src/core/src/Scheduler.cpp +++ b/src/core/src/Scheduler.cpp @@ -182,7 +182,10 @@ void Scheduler::handleRequest(wstring key, json::value value) else if (key.compare(L"cloneUnit") == 0) { int ID = value[L"ID"].as_integer(); - command = dynamic_cast(new Clone(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; + command = dynamic_cast(new Clone(ID, loc)); log(L"Cloning unit " + to_wstring(ID)); } else if (key.compare(L"setLeader") == 0) @@ -219,6 +222,34 @@ void Scheduler::handleRequest(wstring key, json::value value) wstring formation = value[L"formation"].as_string(); unit->setFormation(formation); } + else if (key.compare(L"setROE") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsFactory->getUnit(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 = unitsFactory->getUnit(ID); + wstring reactionToThreat = value[L"reactionToThreat"].as_string(); + unit->setReactionToThreat(reactionToThreat); + } + else if (key.compare(L"landAt") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsFactory->getUnit(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->landAt(loc); + } + else if (key.compare(L"deleteUnit") == 0) + { + int ID = value[L"ID"].as_integer(); + unitsFactory->deleteUnit(ID); + } else { log(L"Unknown command: " + key); diff --git a/src/core/src/Unit.cpp b/src/core/src/Unit.cpp index bf6d7d2e..8f8f502d 100644 --- a/src/core/src/Unit.cpp +++ b/src/core/src/Unit.cpp @@ -118,9 +118,11 @@ json::value Unit::json() json[L"fuel"] = fuel; json[L"ammo"] = ammo; json[L"targets"] = targets; - json[L"targetSpeed"] = targetSpeed; - json[L"targetAltitude"] = targetAltitude; + json[L"targetSpeed"] = getTargetSpeed(); + json[L"targetAltitude"] = getTargetAltitude(); json[L"hasTask"] = hasTask; + json[L"ROE"] = json::value::string(ROE); + json[L"reactionToThreat"] = json::value::string(reactionToThreat); int i = 0; for (auto itr = wingmen.begin(); itr != wingmen.end(); itr++) @@ -221,4 +223,48 @@ void Unit::setFormationOffset(Offset newFormationOffset) { formationOffset = newFormationOffset; resetTask(); +} + +void Unit::setROE(wstring newROE) { + ROE = newROE; + int ROEEnum; + if (newROE.compare(L"Free") == 0) + ROEEnum = ROE::WEAPON_FREE; + else if (newROE.compare(L"Designated free") == 0) + ROEEnum = ROE::OPEN_FIRE_WEAPON_FREE; + else if (newROE.compare(L"Designated") == 0) + ROEEnum = ROE::OPEN_FIRE; + else if (newROE.compare(L"Return") == 0) + ROEEnum = ROE::RETURN_FIRE; + else if (newROE.compare(L"Hold") == 0) + ROEEnum = ROE::WEAPON_HOLD; + else + return; + Command* command = dynamic_cast(new SetOption(ID, SetCommandType::ROE, ROEEnum)); + scheduler->appendCommand(command); +} + +void Unit::setReactionToThreat(wstring newReactionToThreat) { + reactionToThreat = newReactionToThreat; + int reactionToThreatEnum; + if (newReactionToThreat.compare(L"None") == 0) + reactionToThreatEnum = ReactionToThreat::NO_REACTION; + else if (newReactionToThreat.compare(L"Passive") == 0) + reactionToThreatEnum = ReactionToThreat::PASSIVE_DEFENCE; + else if (newReactionToThreat.compare(L"Evade") == 0) + reactionToThreatEnum = ReactionToThreat::EVADE_FIRE; + else if (newReactionToThreat.compare(L"Escape") == 0) + reactionToThreatEnum = ReactionToThreat::BYPASS_AND_ESCAPE; + else if (newReactionToThreat.compare(L"Abort") == 0) + reactionToThreatEnum = ReactionToThreat::ALLOW_ABORT_MISSION; + else + return; + Command* command = dynamic_cast(new SetOption(ID, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum)); + scheduler->appendCommand(command); +} + +void Unit::landAt(Coords loc) { + activePath.clear(); + activePath.push_back(loc); + setState(State::LAND); } \ No newline at end of file diff --git a/src/core/src/aircraft.cpp b/src/core/src/aircraft.cpp index 492cf4a9..b50d8abf 100644 --- a/src/core/src/aircraft.cpp +++ b/src/core/src/aircraft.cpp @@ -48,11 +48,21 @@ void Aircraft::changeAltitude(wstring change) { if (targetAltitude > 5000) targetAltitude += 2500 / 3.28084; - else if (targetAltitude > 0) + else if (targetAltitude >= 0) targetAltitude += 500 / 3.28084; } if (targetAltitude < 0) targetAltitude = 0; goToDestination(); /* Send the command to reach the destination */ +} + +void Aircraft::setTargetSpeed(double newTargetSpeed) { + targetSpeed = newTargetSpeed; + goToDestination(); +} + +void Aircraft::setTargetAltitude(double newTargetAltitude) { + targetAltitude = newTargetAltitude; + goToDestination(); } \ No newline at end of file diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index 62d10cd1..cb7ac8db 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -41,6 +41,9 @@ void AirUnit::setState(int newState) return; break; } + case State::LAND: { + break; + } default: break; } @@ -72,6 +75,10 @@ void AirUnit::setState(int newState) resetActiveDestination(); break; } + case State::LAND: { + resetActiveDestination(); + break; + } default: break; } @@ -242,6 +249,22 @@ void AirUnit::AIloop() break; } + + case State::LAND: { + wstring enrouteTask = L"{" "id = 'land' }"; + currentTask = L"Landing"; + + if (activeDestination == NULL) + { + setActiveDestination(); + goToDestination(enrouteTask); + } + + if (isLeader) + taskWingmen(); + + break; + } case State::ATTACK: { /* If the target is not alive (either not set or was succesfully destroyed) go back to REACH_DESTINATION */ if (!isTargetAlive()) { @@ -319,13 +342,3 @@ void AirUnit::AIloop() break; } } - -void AirUnit::setTargetSpeed(double newTargetSpeed) { - targetSpeed = newTargetSpeed; - goToDestination(); -} - -void AirUnit::setTargetAltitude(double newTargetAltitude) { - targetAltitude = newTargetAltitude; - goToDestination(); -} \ No newline at end of file diff --git a/src/core/src/helicopter.cpp b/src/core/src/helicopter.cpp index 9d64e921..7350e659 100644 --- a/src/core/src/helicopter.cpp +++ b/src/core/src/helicopter.cpp @@ -48,7 +48,7 @@ void Helicopter::changeAltitude(wstring change) { if (targetAltitude > 100) targetAltitude += 100 / 3.28084; - else if (targetAltitude > 0) + else if (targetAltitude >= 0) targetAltitude += 10 / 3.28084; } if (targetAltitude < 0) @@ -56,3 +56,14 @@ void Helicopter::changeAltitude(wstring change) goToDestination(); /* Send the command to reach the destination */ } + + +void Helicopter::setTargetSpeed(double newTargetSpeed) { + targetSpeed = newTargetSpeed; + goToDestination(); +} + +void Helicopter::setTargetAltitude(double newTargetAltitude) { + targetAltitude = newTargetAltitude; + goToDestination(); +} \ No newline at end of file diff --git a/src/core/src/unitsFactory.cpp b/src/core/src/unitsFactory.cpp index 554aeb1b..6a3104bf 100644 --- a/src/core/src/unitsFactory.cpp +++ b/src/core/src/unitsFactory.cpp @@ -7,6 +7,10 @@ #include "groundunit.h" #include "navyunit.h" #include "weapon.h" +#include "commands.h" +#include "scheduler.h" + +extern Scheduler* scheduler; UnitsFactory::UnitsFactory(lua_State* L) { @@ -116,3 +120,12 @@ void UnitsFactory::updateAnswer(json::value& answer) answer[L"units"] = unitsJson; } +void UnitsFactory::deleteUnit(int ID) +{ + if (getUnit(ID) != nullptr) + { + Command* command = dynamic_cast(new Delete(ID)); + scheduler->appendCommand(command); + } +} +