From a949a9bf22c91b8f8d56ff453a5b3f64d19ca35d Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 12 Jul 2023 17:01:03 +0200 Subject: [PATCH] Tweaks and implemented workaround to MIST bug --- client/package-lock.json | 4 +- client/package.json | 2 +- client/src/controls/mapcontextmenu.ts | 15 ++- client/src/map/map.ts | 5 + client/src/server/server.ts | 6 +- client/src/units/aircraftdatabase.ts | 4 + client/src/units/groundunitdatabase.ts | 4 + client/src/units/helicopterdatabase.ts | 4 + client/src/units/navyunitdatabase.ts | 8 +- client/src/units/unit.ts | 4 +- client/src/units/unitdatabase.ts | 19 +++ client/src/units/unitsmanager.ts | 23 +++- client/views/other/contextmenus.ejs | 19 +-- client/views/other/dialogs.ejs | 2 +- client/views/panels/navbar.ejs | 2 +- installer/olympus.iss | 2 +- mod/entry.lua | 2 +- scripts/OlympusCommand.lua | 173 ++++++++++++++++++++----- scripts/OlympusHook.lua | 2 +- src/core/include/commands.h | 64 ++++++++- src/core/include/scheduler.h | 1 + src/core/include/unitsmanager.h | 2 +- src/core/src/commands.cpp | 52 ++++++++ src/core/src/core.cpp | 56 ++++---- src/core/src/scheduler.cpp | 104 +++++++++++---- src/core/src/unitsmanager.cpp | 4 +- src/shared/include/defines.h | 5 +- 27 files changed, 453 insertions(+), 135 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index f773cef7..2e496915 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "DCSOlympus", - "version": "v0.3.0-alpha", + "version": "v0.4.0-alpha", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "DCSOlympus", - "version": "v0.3.0-alpha", + "version": "v0.4.0-alpha", "dependencies": { "@turf/turf": "^6.5.0", "@types/formatcoords": "^1.1.0", diff --git a/client/package.json b/client/package.json index 9c451789..7c591b58 100644 --- a/client/package.json +++ b/client/package.json @@ -2,7 +2,7 @@ "name": "DCSOlympus", "node-main": "./bin/www", "main": "http://localhost:3000", - "version": "v0.3.0-alpha", + "version": "v0.4.0-alpha", "private": true, "scripts": { "copy": "copy.bat", diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts index c90445e0..b2cf335c 100644 --- a/client/src/controls/mapcontextmenu.ts +++ b/client/src/controls/mapcontextmenu.ts @@ -31,7 +31,7 @@ export class MapContextMenu extends ContextMenu { #navyUnitTypeDropdown: Dropdown; #navyUnitNameDropdown: Dropdown; #navyUnitCountDropdown: Dropdown; - #spawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), coalition: "blue", loadout: "", airbaseName: "", altitude: ftToM(20000), count: 1 }; + #spawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), coalition: "blue", loadout: "", airbaseName: "", altitude: 0, count: 1 }; #coalitionArea: CoalitionArea | null = null; constructor(id: string) { @@ -89,7 +89,7 @@ export class MapContextMenu extends ContextMenu { this.hideSubMenus(e.detail.type); }); - document.addEventListener("contextMenuDeployAircraft", () => { + document.addEventListener("contextMenuDeployAircrafts", () => { this.hide(); this.#spawnOptions.coalition = getActiveCoalition(); if (this.#spawnOptions) { @@ -103,7 +103,7 @@ export class MapContextMenu extends ContextMenu { } }); - document.addEventListener("contextMenuDeployHelicopter", () => { + document.addEventListener("contextMenuDeployHelicopters", () => { this.hide(); this.#spawnOptions.coalition = getActiveCoalition(); if (this.#spawnOptions) { @@ -117,7 +117,7 @@ export class MapContextMenu extends ContextMenu { } }); - document.addEventListener("contextMenuDeployGroundUnit", () => { + document.addEventListener("contextMenuDeployGroundUnits", () => { this.hide(); this.#spawnOptions.coalition = getActiveCoalition(); if (this.#spawnOptions) { @@ -222,6 +222,13 @@ export class MapContextMenu extends ContextMenu { this.#groundUnitCountDropdown.setValue("1"); this.clip(); + if (type === "aircraft") { + this.#spawnOptions.altitude = ftToM(this.#aircraftSpawnAltitudeSlider.getValue()); + } + else if (type === "helicopter") { + this.#spawnOptions.altitude = ftToM(this.#helicopterSpawnAltitudeSlider.getValue()); + } + this.setVisibleSubMenu(type); } diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 6c50858a..1d3053e5 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -17,6 +17,8 @@ import { TargetMarker } from "./targetmarker"; import { CoalitionArea } from "./coalitionarea"; import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu"; import { DrawingCursor } from "./drawingcursor"; +import { aircraftDatabase } from "../units/aircraftdatabase"; +import { groundUnitDatabase } from "../units/groundunitdatabase"; L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); @@ -422,6 +424,9 @@ export class Map extends L.Map { #onDoubleClick(e: any) { this.deselectAllCoalitionAreas(); + + var db = groundUnitDatabase; + db.generateTestGrid(this.getMouseCoordinates()) } #onContextMenu(e: any) { diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 52f64b87..26841dfb 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -153,7 +153,7 @@ export function spawnAircrafts(units: any, coalition: string, airbaseName: strin export function spawnHelicopters(units: any, coalition: string, airbaseName: string, immediate: boolean) { var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "immediate": immediate }; - var data = { "spawnHelicopter": command } + var data = { "spawnHelicopters": command } POST(data, () => { }); } @@ -191,8 +191,8 @@ export function cloneUnit(ID: number, latlng: LatLng) { POST(data, () => { }); } -export function deleteUnit(ID: number, explosion: boolean) { - var command = { "ID": ID, "explosion": explosion }; +export function deleteUnit(ID: number, explosion: boolean, immediate: boolean) { + var command = { "ID": ID, "explosion": explosion, "immediate": immediate }; var data = { "deleteUnit": command } POST(data, () => { }); } diff --git a/client/src/units/aircraftdatabase.ts b/client/src/units/aircraftdatabase.ts index a798d841..b7044f8f 100644 --- a/client/src/units/aircraftdatabase.ts +++ b/client/src/units/aircraftdatabase.ts @@ -3906,6 +3906,10 @@ export class AircraftDatabase extends UnitDatabase { } } } + + getCategory() { + return "Aircraft"; + } } export var aircraftDatabase = new AircraftDatabase(); diff --git a/client/src/units/groundunitdatabase.ts b/client/src/units/groundunitdatabase.ts index b6e94fe6..dd100eee 100644 --- a/client/src/units/groundunitdatabase.ts +++ b/client/src/units/groundunitdatabase.ts @@ -1537,6 +1537,10 @@ export class GroundUnitDatabase extends UnitDatabase { } } } + + getCategory() { + return "GroundUnit"; + } } export var groundUnitDatabase = new GroundUnitDatabase(); diff --git a/client/src/units/helicopterdatabase.ts b/client/src/units/helicopterdatabase.ts index 04b4dda9..e00ac1f5 100644 --- a/client/src/units/helicopterdatabase.ts +++ b/client/src/units/helicopterdatabase.ts @@ -578,6 +578,10 @@ export class HelicopterDatabase extends UnitDatabase { } } } + + getCategory() { + return "Helicopter"; + } } export var helicopterDatabase = new HelicopterDatabase(); diff --git a/client/src/units/navyunitdatabase.ts b/client/src/units/navyunitdatabase.ts index b157f5d2..57d3cb1c 100644 --- a/client/src/units/navyunitdatabase.ts +++ b/client/src/units/navyunitdatabase.ts @@ -631,8 +631,8 @@ export class NavyUnitDatabase extends UnitDatabase { "range": "", "filename": "" }, - "Ticonderoga": { - "name": "Ticonderoga", + "TICONDEROG": { + "name": "TICONDEROG", "type": "Cruiser", "era": [ "Late Cold War" @@ -941,6 +941,10 @@ export class NavyUnitDatabase extends UnitDatabase { } } } + + getCategory() { + return "NavyUnit"; + } } export var navyUnitDatabase = new NavyUnitDatabase(); diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index ca4d66a9..fc5f13a8 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -646,8 +646,8 @@ export class Unit extends CustomMarker { setFollowRoads(this.ID, followRoads); } - delete(explosion: boolean) { - deleteUnit(this.ID, explosion); + delete(explosion: boolean, immediate: boolean) { + deleteUnit(this.ID, explosion, immediate); } refuel() { diff --git a/client/src/units/unitdatabase.ts b/client/src/units/unitdatabase.ts index 68fc7120..cb00b912 100644 --- a/client/src/units/unitdatabase.ts +++ b/client/src/units/unitdatabase.ts @@ -1,3 +1,6 @@ +import { LatLng } from "leaflet"; +import { getUnitsManager } from ".."; + export class UnitDatabase { blueprints: { [key: string]: UnitBlueprint } = {}; @@ -5,6 +8,10 @@ export class UnitDatabase { } + getCategory() { + return ""; + } + getBlueprints() { return this.blueprints; } @@ -143,4 +150,16 @@ export class UnitDatabase { } return null; } + + generateTestGrid(initialPosition: LatLng) { + const step = 0.01; + var nUnits = Object.values(this.blueprints).length; + var gridSize = Math.ceil(Math.sqrt(nUnits)); + Object.values(this.blueprints).forEach((unitBlueprint: UnitBlueprint, idx: number) => { + var row = Math.floor(idx / gridSize); + var col = idx - row * gridSize; + var location = new LatLng(initialPosition.lat + col * step, initialPosition.lng + row * step) + getUnitsManager().spawnUnit(this.getCategory(), [{unitType: unitBlueprint.name, location: location, altitude: 1000, loadout: ""}]); + }) + } } \ No newline at end of file diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index bd35acbf..0041aa0d 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -1,15 +1,14 @@ import { LatLng, LatLngBounds } from "leaflet"; -import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler } from ".."; +import { getHotgroupPanel, getInfoPopup, getMap } from ".."; import { Unit } from "./unit"; -import { cloneUnit, setLastUpdateTime, spawnGroundUnits } from "../server/server"; +import { cloneUnit, setLastUpdateTime, spawnAircrafts, spawnGroundUnits } from "../server/server"; import { bearingAndDistanceToLatLng, deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils"; import { CoalitionArea } from "../map/coalitionarea"; -import { Airbase } from "../missionhandler/airbase"; import { groundUnitDatabase } from "./groundunitdatabase"; import { DataIndexes, HIDE_ALL, IADSDensities, IDLE, MOVE_UNIT } from "../constants/constants"; import { DataExtractor } from "./dataextractor"; import { Contact } from "../@types/unit"; -import { citiesDatabase } from "./citiesDatabase"; +import { citiesDatabase } from "./citiesdatabase"; export class UnitsManager { #units: { [ID: number]: Unit }; @@ -378,8 +377,12 @@ export class UnitsManager { return; } + var immediate = false; + if (selectedUnits.length > 20) + immediate = confirm(`You are trying to delete ${selectedUnits.length} units, do you want to delete them immediately? This may cause lag for players.`) + for (let idx in selectedUnits) { - selectedUnits[idx].delete(explosion); + selectedUnits[idx].delete(explosion, immediate); } this.#showActionMessage(selectedUnits, `deleted`); } @@ -518,7 +521,7 @@ export class UnitsManager { if (this.#units[idx].getCoalition() !== "neutral" && this.#units[idx].getCoalition() != unit.getCoalition()) { this.#units[idx].getContacts().forEach((contact: Contact) => { - if (contact.ID == unit.ID && !detectionMethods.includes(contact.detectionMethod)) + if (this.#units[idx].getAlive() && contact.ID == unit.ID && !detectionMethods.includes(contact.detectionMethod)) detectionMethods.push(contact.detectionMethod); }); } @@ -616,6 +619,14 @@ export class UnitsManager { input.click(); } + spawnUnit(category: string, units: any, coalition: string = "blue", immediate: boolean = true) { + if (category === "Aircraft") { + spawnAircrafts(units, coalition, "", immediate); + } else if (category === "GroundUnit") { + spawnGroundUnits(units, coalition, immediate); + } + } + /***********************************************/ #onKeyUp(event: KeyboardEvent) { if (!keyEventWasInInput(event) && event.key === "Delete" ) { diff --git a/client/views/other/contextmenus.ejs b/client/views/other/contextmenus.ejs index 197cbb63..9bb8e91b 100644 --- a/client/views/other/contextmenus.ejs +++ b/client/views/other/contextmenus.ejs @@ -4,8 +4,8 @@
- + +
@@ -129,7 +130,7 @@
- +
@@ -158,7 +159,7 @@
- + - +
diff --git a/client/views/other/dialogs.ejs b/client/views/other/dialogs.ejs index 49e25a05..7a1be4f0 100644 --- a/client/views/other/dialogs.ejs +++ b/client/views/other/dialogs.ejs @@ -3,7 +3,7 @@

DCS Olympus

Dynamic Unit Command

-
Version v0.3.0
+
Version v0.4.0-alpha
diff --git a/client/views/panels/navbar.ejs b/client/views/panels/navbar.ejs index e6108501..4ec2db37 100644 --- a/client/views/panels/navbar.ejs +++ b/client/views/panels/navbar.ejs @@ -6,7 +6,7 @@

DCS Olympus

-
version v0.3.0
+
version v0.4.0-alpha
Discord diff --git a/installer/olympus.iss b/installer/olympus.iss index 55664142..8a770524 100644 --- a/installer/olympus.iss +++ b/installer/olympus.iss @@ -1,5 +1,5 @@ #define nwjsFolder "C:\Users\dpass\Documents\nwjs\" -#define version "v0.3.0-alpha" +#define version "v0.4.0-alpha" [Setup] AppName=DCS Olympus diff --git a/mod/entry.lua b/mod/entry.lua index b2a04abe..a28956fd 100644 --- a/mod/entry.lua +++ b/mod/entry.lua @@ -15,7 +15,7 @@ declare_plugin(self_ID, shortName = "Olympus", fileMenuName = "Olympus", - version = "0.1.1-alpha", + version = "v0.4.0-alpha", state = "installed", developerName= "DCS Refugees 767 squadron", info = _("DCS Olympus is a mod for DCS World. It allows users to spawn, control, task, group, and remove units from a DCS World server using a real-time map interface, similarly to Real Time Strategy games. The user interface also provides useful informations units, like loadouts, fuel, tasking, and so on. In the future, more features for DCS World GCI and JTAC will be available."), diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 1c3ecd59..3517b31d 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,6 +1,6 @@ -local version = "v0.3.0-alpha" +local version = "v0.4.0-alpha" -local debug = false +local debug = true Olympus.unitCounter = 1 Olympus.payloadRegistry = {} @@ -10,6 +10,7 @@ Olympus.groupStep = 40 Olympus.OlympusDLL = nil Olympus.DLLsloaded = false Olympus.OlympusModPath = os.getenv('DCSOLYMPUS_PATH')..'\\bin\\' +Olympus.log = mist.Logger:new("Olympus", 'info') function Olympus.debug(message, displayFor) if debug == true then @@ -252,7 +253,51 @@ function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedT if groupCon then groupCon:setTask(missionTask) end - Olympus.debug("Olympus.move executed successfully on a Aircraft", 2) + Olympus.debug("Olympus.move executed successfully on Aircraft", 2) + elseif category == "Helicopter" then + 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.heli.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'), + [2] = mist.heli.buildWP(endPoint, landing, speed, 0, 'AGL') + } + else + path = { + [1] = mist.heli.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'), + [2] = mist.heli.buildWP(endPoint, turningPoint, speed, altitude, 'BARO') + } + end + + -- If a task exists assign it to the controller + if taskOptions then + local task = Olympus.buildEnrouteTask(taskOptions) + if task then + path[1].task = task + path[2].task = task + end + end + + -- Assign the mission task to the controller + local missionTask = { + id = 'Mission', + params = { + route = { + points = mist.utils.deepCopy(path), + }, + }, + } + local groupCon = group:getController() + if groupCon then + groupCon:setTask(missionTask) + end + Olympus.debug("Olympus.move executed successfully on Helicopter", 2) elseif category == "GroundUnit" then vars = { @@ -270,7 +315,17 @@ function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedT end mist.groupToRandomPoint(vars) - Olympus.debug("Olympus.move executed succesfully on a ground unit", 2) + Olympus.debug("Olympus.move executed succesfully on GroundUnit", 2) + elseif category == "NavyUnit" then + vars = + { + group = group, + point = coord.LLtoLO(lat, lng, 0), + heading = 0, + speed = speed + } + mist.groupToRandomPoint(vars) + Olympus.debug("Olympus.move executed succesfully on NavyUnit", 2) else Olympus.debug("Olympus.move not implemented yet for " .. category, 2) end @@ -314,12 +369,21 @@ function Olympus.spawnUnits(spawnTable) if spawnTable.category == 'Aircraft' then unitTable = Olympus.generateAirUnitsTable(spawnTable.units) route = Olympus.generateAirUnitsRoute(spawnTable) - category = 'airplane' + category = 'plane' + elseif spawnTable.category == 'Helicopter' then + unitTable = Olympus.generateAirUnitsTable(spawnTable.units) + route = Olympus.generateAirUnitsRoute(spawnTable) + category = 'helicopter' elseif spawnTable.category == 'GroundUnit' then unitTable = Olympus.generateGroundUnitsTable(spawnTable.units) category = 'vehicle' + elseif spawnTable.category == 'NavyUnit' then + unitTable = Olympus.generateNavyUnitsTable(spawnTable.units) + category = 'ship' end + Olympus.debug(Olympus.serializeTable(unitTable), 5) + local countryID = Olympus.getCountryIDByCoalition(spawnTable.coalition) local vars = { @@ -336,37 +400,6 @@ function Olympus.spawnUnits(spawnTable) Olympus.debug("Olympus.spawnUnits completed succesfully", 2) end --- Generates ground units table, either single or from template -function Olympus.generateGroundUnitsTable(units) - local unitTable = {} - for idx, unit in pairs(units) do - local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0)) - if Olympus.hasKey(templates, unit.unitType) then - for idx, value in pairs(templates[unit.unitType].units) do - unitTable[#unitTable + 1] = - { - ["type"] = value.name, - ["x"] = spawnLocation.x + value.dx, - ["y"] = spawnLocation.z + value.dy, - ["heading"] = 0, - ["skill"] = "High" - } - end - else - unitTable[#unitTable + 1] = - { - ["type"] = unit.unitType, - ["x"] = spawnLocation.x, - ["y"] = spawnLocation.z, - ["heading"] = 0, - ["skill"] = "High" - } - end - end - - return unitTable -end - -- Generates unit table for a air unit. function Olympus.generateAirUnitsTable(units) local unitTable = {} @@ -456,6 +489,74 @@ function Olympus.generateAirUnitsRoute(spawnTable) return route end +-- Generates ground units table, either single or from template +function Olympus.generateGroundUnitsTable(units) + local unitTable = {} + for idx, unit in pairs(units) do + local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0)) + if Olympus.hasKey(templates, unit.unitType) then + for idx, value in pairs(templates[unit.unitType].units) do + unitTable[#unitTable + 1] = + { + ["type"] = value.name, + ["x"] = spawnLocation.x + value.dx, + ["y"] = spawnLocation.z + value.dy, + ["heading"] = 0, + ["skill"] = "High", + ["name"] = "GroundUnit-" .. Olympus.unitCounter .. "-" .. #unitTable + 1 + } + end + else + unitTable[#unitTable + 1] = + { + ["type"] = unit.unitType, + ["x"] = spawnLocation.x, + ["y"] = spawnLocation.z, + ["heading"] = 0, + ["skill"] = "High", + ["name"] = "GroundUnit-" .. Olympus.unitCounter .. "-" .. #unitTable + 1 + } + end + end + + return unitTable +end + +-- Generates navy units table, either single or from template +function Olympus.generateNavyUnitsTable(units) + local unitTable = {} + for idx, unit in pairs(units) do + local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0)) + if Olympus.hasKey(templates, unit.unitType) then + for idx, value in pairs(templates[unit.unitType].units) do + unitTable[#unitTable + 1] = + { + ["type"] = value.name, + ["x"] = spawnLocation.x + value.dx, + ["y"] = spawnLocation.z + value.dy, + ["heading"] = 0, + ["skill"] = "High", + ["name"] = "NavyUnit-" .. Olympus.unitCounter .. "-" .. #unitTable + 1, + ["transportable"] = { ["randomTransportable"] = false } + } + end + else + unitTable[#unitTable + 1] = + { + ["type"] = unit.unitType, + ["x"] = spawnLocation.x, + ["y"] = spawnLocation.z, + ["heading"] = 0, + ["skill"] = "High", + ["name"] = "NavyUnit-" .. Olympus.unitCounter .. "-" .. #unitTable + 1, + ["transportable"] = { ["randomTransportable"] = false } + } + end + end + + return unitTable +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, lat, lng, category) Olympus.debug("Olympus.clone " .. ID .. ", " .. category, 2) diff --git a/scripts/OlympusHook.lua b/scripts/OlympusHook.lua index b87c8926..3310a8c4 100644 --- a/scripts/OlympusHook.lua +++ b/scripts/OlympusHook.lua @@ -1,4 +1,4 @@ -local version = 'v0.3.0-alpha' +local version = 'v0.4.0-alpha' Olympus = {} Olympus.OlympusDLL = nil diff --git a/src/core/include/commands.h b/src/core/include/commands.h index bd819edb..4fddd907 100644 --- a/src/core/include/commands.h +++ b/src/core/include/commands.h @@ -163,7 +163,7 @@ public: priority = immediate? CommandPriority::IMMEDIATE: CommandPriority::LOW; }; virtual string getString(lua_State* L); - virtual unsigned int getLoad() { return 100 * !immediate; } + virtual unsigned int getLoad() { return immediate? 1: 100; } private: const string coalition; @@ -172,7 +172,29 @@ private: const bool immediate; }; -/* Spawn air unit command */ +/* Spawn navy unit command */ +class SpawnNavyUnits : public Command +{ +public: + SpawnNavyUnits(string coalition, vector unitTypes, vector locations, bool immediate) : + coalition(coalition), + unitTypes(unitTypes), + locations(locations), + immediate(immediate) + { + priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW; + }; + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return immediate ? 1 : 100; } + +private: + const string coalition; + const vector unitTypes; + const vector locations; + const bool immediate; +}; + +/* Spawn aircraft command */ class SpawnAircrafts : public Command { public: @@ -187,7 +209,34 @@ public: priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW; }; virtual string getString(lua_State* L); - virtual unsigned int getLoad() { return 100 * !immediate; } + virtual unsigned int getLoad() { return immediate ? 1 : 100; } + +private: + const string coalition; + const vector unitTypes; + const vector locations; + const vector loadouts; + const string airbaseName; + const bool immediate; +}; + + +/* Spawn helicopter command */ +class SpawnHelicopters : public Command +{ +public: + SpawnHelicopters(string coalition, vector unitTypes, vector locations, vector loadouts, string airbaseName, bool immediate) : + coalition(coalition), + unitTypes(unitTypes), + locations(locations), + loadouts(loadouts), + airbaseName(airbaseName), + immediate(immediate) + { + priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW; + }; + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return immediate ? 1 : 100; } private: const string coalition; @@ -220,18 +269,21 @@ private: class Delete : public Command { public: - Delete(unsigned int ID, bool explosion) : + Delete(unsigned int ID, bool explosion, bool immediate ) : ID(ID), - explosion(explosion) + explosion(explosion), + immediate(immediate) { priority = CommandPriority::HIGH; + immediate = immediate; }; virtual string getString(lua_State* L); - virtual unsigned int getLoad() { return 20; } + virtual unsigned int getLoad() { return immediate? 1: 20; } private: const unsigned int ID; const bool explosion; + const bool immediate; }; /* SetTask command */ diff --git a/src/core/include/scheduler.h b/src/core/include/scheduler.h index 0b931e1c..7468a563 100644 --- a/src/core/include/scheduler.h +++ b/src/core/include/scheduler.h @@ -10,6 +10,7 @@ public: ~Scheduler(); void appendCommand(Command* command); + int getCurrentLoad(); void execute(lua_State* L); void handleRequest(string key, json::value value); diff --git a/src/core/include/unitsmanager.h b/src/core/include/unitsmanager.h index 71d5f247..71ea874d 100644 --- a/src/core/include/unitsmanager.h +++ b/src/core/include/unitsmanager.h @@ -21,7 +21,7 @@ public: void updateMissionData(json::value missionData); void runAILoop(); string getUnitData(stringstream &ss, unsigned long long time); - void deleteUnit(unsigned int ID, bool explosion); + void deleteUnit(unsigned int ID, bool explosion, bool immediate); void acquireControl(unsigned int ID); private: diff --git a/src/core/src/commands.cpp b/src/core/src/commands.cpp index 2d9bbb9a..25d234c4 100644 --- a/src/core/src/commands.cpp +++ b/src/core/src/commands.cpp @@ -60,6 +60,30 @@ string SpawnGroundUnits::getString(lua_State* L) return commandSS.str(); } + +/* Spawn ground units command */ +string SpawnNavyUnits::getString(lua_State* L) +{ + if (unitTypes.size() != locations.size()) return ""; + + std::ostringstream unitsSS; + unitsSS.precision(10); + for (int i = 0; i < unitTypes.size(); i++) { + unitsSS << "[" << i + 1 << "] = {" + << "unitType = " << "\"" << unitTypes[i] << "\"" << ", " + << "lat = " << locations[i].lat << ", " + << "lng = " << locations[i].lng << "},"; + } + + std::ostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.spawnUnits, {" + << "category = " << "\"" << "NavyUnit" << "\"" << ", " + << "coalition = " << "\"" << coalition << "\"" << ", " + << "units = " << "{" << unitsSS.str() << "}" << "}"; + return commandSS.str(); +} + /* Spawn aircrafts command */ string SpawnAircrafts::getString(lua_State* L) { @@ -86,6 +110,34 @@ string SpawnAircrafts::getString(lua_State* L) return commandSS.str(); } + +/* Spawn helicopters command */ +string SpawnHelicopters::getString(lua_State* L) +{ + if (unitTypes.size() != locations.size() || unitTypes.size() != loadouts.size()) return ""; + + std::ostringstream unitsSS; + unitsSS.precision(10); + for (int i = 0; i < unitTypes.size(); i++) { + unitsSS << "[" << i + 1 << "] = {" + << "unitType = " << "\"" << unitTypes[i] << "\"" << ", " + << "lat = " << locations[i].lat << ", " + << "lng = " << locations[i].lng << ", " + << "alt = " << locations[i].alt << ", " + << "loadout = \"" << loadouts[i] << "\"" << "},"; + } + + std::ostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.spawnUnits, {" + << "category = " << "\"" << "Helicopter" << "\"" << ", " + << "coalition = " << "\"" << coalition << "\"" << ", " + << "airbaseName = \"" << airbaseName << "\", " + << "units = " << "{" << unitsSS.str() << "}" << "}"; + return commandSS.str(); +} + + /* Clone unit command */ string Clone::getString(lua_State* L) { diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index b2e7263d..1d1436e7 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -9,7 +9,8 @@ #include using namespace std::chrono; -auto before = std::chrono::system_clock::now(); +auto lastUpdate = std::chrono::system_clock::now(); +auto lastExecution = std::chrono::system_clock::now(); /* Singleton objects */ UnitsManager* unitsManager = nullptr; @@ -72,27 +73,33 @@ extern "C" DllExport int coreFrame(lua_State* L) frameCounter++; /* Slow down the update rate if the frameRate is very low since it means DCS is struggling to keep up */ - const std::chrono::duration duration = std::chrono::system_clock::now() - before; - if (duration.count() > UPDATE_TIME_INTERVAL * (60.0 / frameRate)) + const std::chrono::duration updateDuration = std::chrono::system_clock::now() - lastUpdate; + double updateTimeInterval = max(UPDATE_TIME_INTERVAL, UPDATE_TIME_INTERVAL * (60.0 / frameRate)); + if (updateDuration.count() > updateTimeInterval) { /* Lock for thread safety */ lock_guard guard(mutexLock); milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); - if (duration.count() > 0) - frameRate = frameCounter / duration.count(); + if (updateDuration.count() > 0) + frameRate = frameCounter / updateDuration.count(); frameCounter = 0; if (unitsManager != nullptr) { - unitsManager->updateExportData(L, duration.count()); + unitsManager->updateExportData(L, updateDuration.count()); unitsManager->runAILoop(); } - before = std::chrono::system_clock::now(); + lastUpdate = std::chrono::system_clock::now(); } - if (scheduler != nullptr) - scheduler->execute(L); + const std::chrono::duration executionDuration = std::chrono::system_clock::now() - lastExecution; + double executionTimeInterval = max(EXECUTION_TIME_INTERVAL, EXECUTION_TIME_INTERVAL * (60.0 / frameRate)); + if (executionDuration.count() > executionTimeInterval) { + if (scheduler != nullptr) + scheduler->execute(L); + lastExecution = std::chrono::system_clock::now(); + } return(0); } @@ -104,28 +111,19 @@ extern "C" DllExport int coreMissionData(lua_State * L) /* Lock for thread safety */ lock_guard guard(mutexLock); + lua_getglobal(L, "Olympus"); + lua_getfield(L, -1, "missionData"); + json::value missionData = luaTableToJSON(L, -1); - try - { - lua_getglobal(L, "Olympus"); - lua_getfield(L, -1, "missionData"); - json::value missionData = luaTableToJSON(L, -1); - - if (missionData.has_object_field(L"unitsData")) - unitsManager->updateMissionData(missionData[L"unitsData"]); - if (missionData.has_object_field(L"airbases")) - airbases = missionData[L"airbases"]; - if (missionData.has_object_field(L"bullseyes")) - bullseyes = missionData[L"bullseyes"]; - if (missionData.has_object_field(L"mission")) - mission = missionData[L"mission"]; + if (missionData.has_object_field(L"unitsData")) { + unitsManager->updateMissionData(missionData[L"unitsData"]); } - catch (exception const& e) - { - log(e.what()); - } - - + if (missionData.has_object_field(L"airbases")) + airbases = missionData[L"airbases"]; + if (missionData.has_object_field(L"bullseyes")) + bullseyes = missionData[L"bullseyes"]; + if (missionData.has_object_field(L"mission")) + mission = missionData[L"mission"]; return(0); } diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index 739a21eb..a4131a3c 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -23,6 +23,15 @@ void Scheduler::appendCommand(Command* command) commands.push_back(command); } +int Scheduler::getCurrentLoad() +{ + int currentLoad = 0; + for (auto command : commands) { + currentLoad += command->getLoad(); + } + return currentLoad; +} + void Scheduler::execute(lua_State* L) { /* Decrease the active computation load. New commands can be sent only if the load has reached 0. @@ -42,7 +51,7 @@ void Scheduler::execute(lua_State* L) if (dostring_in(L, "server", (commandString))) log("Error executing command " + commandString); else - log("Command '" + commandString + "' executed correctly, current load " + to_string(load)); + log("Command '" + commandString + "' executed correctly, current load " + to_string(getCurrentLoad())); load = command->getLoad(); commands.remove(command); return; @@ -92,25 +101,6 @@ void Scheduler::handleRequest(string key, json::value value) Coords loc; loc.lat = lat; loc.lng = lng; command = dynamic_cast(new Smoke(color, loc)); } - else if (key.compare("spawnGroundUnits") == 0) - { - bool immediate = value[L"immediate"].as_bool(); - string coalition = to_string(value[L"coalition"]); - - vector unitTypes; - vector locations; - for (auto unit : value[L"units"].as_array()) { - string unitType = to_string(unit[L"unitType"]); - double lat = unit[L"location"][L"lat"].as_double(); - double lng = unit[L"location"][L"lng"].as_double(); - Coords location; location.lat = lat; location.lng = lng; - log("Spawning " + coalition + " ground unit of type " + unitType + " at (" + to_string(lat) + ", " + to_string(lng) + ")"); - unitTypes.push_back(unitType); - locations.push_back(location); - } - - command = dynamic_cast(new SpawnGroundUnits(coalition, unitTypes, locations, immediate)); - } else if (key.compare("spawnAircrafts") == 0) { bool immediate = value[L"immediate"].as_bool(); @@ -127,15 +117,78 @@ void Scheduler::handleRequest(string key, json::value value) double alt = unit[L"altitude"].as_double(); Coords location; location.lat = lat; location.lng = lng; location.alt = alt; string loadout = to_string(unit[L"loadout"]); - - log("Spawning " + coalition + " air unit unit of type " + unitType + " at (" + to_string(lat) + ", " + to_string(lng) + ")"); + + log("Spawning " + coalition + " aircraft of type " + unitType + " at (" + to_string(lat) + ", " + to_string(lng) + ")"); unitTypes.push_back(unitType); locations.push_back(location); loadouts.push_back(loadout); } - + command = dynamic_cast(new SpawnAircrafts(coalition, unitTypes, locations, loadouts, airbaseName, immediate)); } + else if (key.compare("spawnHelicopters") == 0) + { + bool immediate = value[L"immediate"].as_bool(); + string coalition = to_string(value[L"coalition"]); + string airbaseName = to_string(value[L"airbaseName"]); + + vector unitTypes; + vector locations; + vector loadouts; + for (auto unit : value[L"units"].as_array()) { + string unitType = to_string(unit[L"unitType"]); + double lat = unit[L"location"][L"lat"].as_double(); + double lng = unit[L"location"][L"lng"].as_double(); + double alt = unit[L"altitude"].as_double(); + Coords location; location.lat = lat; location.lng = lng; location.alt = alt; + string loadout = to_string(unit[L"loadout"]); + + log("Spawning " + coalition + " helicopter of type " + unitType + " at (" + to_string(lat) + ", " + to_string(lng) + ")"); + unitTypes.push_back(unitType); + locations.push_back(location); + loadouts.push_back(loadout); + } + + command = dynamic_cast(new SpawnHelicopters(coalition, unitTypes, locations, loadouts, airbaseName, immediate)); + } + else if (key.compare("spawnGroundUnits") == 0) + { + bool immediate = value[L"immediate"].as_bool(); + string coalition = to_string(value[L"coalition"]); + + vector unitTypes; + vector locations; + for (auto unit : value[L"units"].as_array()) { + string unitType = to_string(unit[L"unitType"]); + double lat = unit[L"location"][L"lat"].as_double(); + double lng = unit[L"location"][L"lng"].as_double(); + Coords location; location.lat = lat; location.lng = lng; + log("Spawning " + coalition + " GroundUnit of type " + unitType + " at (" + to_string(lat) + ", " + to_string(lng) + ")"); + unitTypes.push_back(unitType); + locations.push_back(location); + } + + command = dynamic_cast(new SpawnGroundUnits(coalition, unitTypes, locations, immediate)); + } + else if (key.compare("spawnNavyUnits") == 0) + { + bool immediate = value[L"immediate"].as_bool(); + string coalition = to_string(value[L"coalition"]); + + vector unitTypes; + vector locations; + for (auto unit : value[L"units"].as_array()) { + string unitType = to_string(unit[L"unitType"]); + double lat = unit[L"location"][L"lat"].as_double(); + double lng = unit[L"location"][L"lng"].as_double(); + Coords location; location.lat = lat; location.lng = lng; + log("Spawning " + coalition + " NavyUnit of type " + unitType + " at (" + to_string(lat) + ", " + to_string(lng) + ")"); + unitTypes.push_back(unitType); + locations.push_back(location); + } + + command = dynamic_cast(new SpawnNavyUnits(coalition, unitTypes, locations, immediate)); + } else if (key.compare("attackUnit") == 0) { unsigned int ID = value[L"ID"].as_integer(); @@ -287,7 +340,8 @@ void Scheduler::handleRequest(string key, json::value value) { unsigned int ID = value[L"ID"].as_integer(); bool explosion = value[L"explosion"].as_bool(); - unitsManager->deleteUnit(ID, explosion); + bool immediate = value[L"immediate"].as_bool(); + unitsManager->deleteUnit(ID, explosion, immediate); } else if (key.compare("refuel") == 0) { @@ -414,7 +468,7 @@ void Scheduler::handleRequest(string key, json::value value) if (command != nullptr) { appendCommand(command); - log("New command appended correctly to stack. Current server load: " + to_string(load)); + log("New command appended correctly to stack. Current server load: " + to_string(getCurrentLoad())); } } diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index d3b181e0..a49c14b3 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -164,11 +164,11 @@ string UnitsManager::getUnitData(stringstream &ss, unsigned long long time) return to_base64(ss.str()); } -void UnitsManager::deleteUnit(unsigned int ID, bool explosion) +void UnitsManager::deleteUnit(unsigned int ID, bool explosion, bool immediate) { if (getUnit(ID) != nullptr) { - Command* command = dynamic_cast(new Delete(ID, explosion)); + Command* command = dynamic_cast(new Delete(ID, explosion, immediate)); scheduler->appendCommand(command); } } diff --git a/src/shared/include/defines.h b/src/shared/include/defines.h index de0a36ba..dca3df95 100644 --- a/src/shared/include/defines.h +++ b/src/shared/include/defines.h @@ -1,6 +1,6 @@ #pragma once -#define VERSION "v0.2.1" +#define VERSION "v0.4.0-alpha" #define LOG_NAME "Olympus_log.txt" #define REST_ADDRESS "http://localhost:30000" #define REST_URI "olympus" @@ -10,4 +10,5 @@ #define BULLSEYE_URI "bullseyes" #define MISSION_URI "mission" -#define UPDATE_TIME_INTERVAL 0.25 \ No newline at end of file +#define UPDATE_TIME_INTERVAL 0.25 +#define EXECUTION_TIME_INTERVAL 0.05 \ No newline at end of file