Merge pull request #377 from Pax1601/370-write-better-method-of-determining-if-a-command-has-been-completed

370 write better method of determining if a command has been completed
This commit is contained in:
Pax1601 2023-09-07 13:03:05 +02:00 committed by GitHub
commit 860414b7b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 548 additions and 358 deletions

View File

@ -112,6 +112,8 @@ class DemoDataGenerator {
app.get('/demo/bullseyes', (req, res) => this.bullseyes(req, res));
app.get('/demo/airbases', (req, res) => this.airbases(req, res));
app.get('/demo/mission', (req, res) => this.mission(req, res));
app.get('/demo/commands', (req, res) => this.command(req, res));
app.put('/demo', (req, res) => this.put(req, res));
app.use('/demo', basicAuth({
users: {
@ -457,7 +459,17 @@ class DemoDataGenerator {
}
res.send(JSON.stringify(ret));
}
command(req, res) {
var ret = {commandExecuted: Math.random() > 0.5};
res.send(JSON.stringify(ret));
}
put(req, res) {
var ret = {commandHash: Math.random().toString(36).slice(2, 19)}
res.send(JSON.stringify(ret));
}
}
module.exports = DemoDataGenerator;

View File

@ -44,4 +44,9 @@ interface LogData {
logs: {[key: string]: string},
sessionHash: string;
time: number;
}
interface ServerRequestOptions {
time?: number;
commandHash?: string;
}

View File

@ -1,5 +1,13 @@
import { LatLng, LatLngBounds } from "leaflet";
export const UNITS_URI = "units";
export const WEAPONS_URI = "weapons";
export const LOGS_URI = "logs";
export const AIRBASES_URI = "airbases";
export const BULLSEYE_URI = "bullseyes";
export const MISSION_URI = "mission";
export const COMMANDS_URI = "commands";
export const NONE = "None";
export const GAME_MASTER = "Game master";
export const BLUE_COMMANDER = "Blue commander";

View File

@ -2,6 +2,7 @@ import { getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from "
import { GAME_MASTER } from "../constants/constants";
import { Airbase } from "../mission/airbase";
import { dataPointMap } from "../other/utils";
import { Unit } from "../unit/unit";
import { ContextMenu } from "./contextmenu";
/** This context menu is shown to the user when the airbase marker is right clicked on the map.
@ -38,7 +39,7 @@ export class AirbaseContextMenu extends ContextMenu {
this.#setProperties(this.#airbase.getProperties());
this.#setParkings(this.#airbase.getParkings());
this.#setCoalition(this.#airbase.getCoalition());
this.#showLandButton(getUnitsManager().getSelectedUnitsTypes().length == 1 && ["Aircraft", "Helicopter"].includes(getUnitsManager().getSelectedUnitsTypes()[0]) && (getUnitsManager().getSelectedUnitsCoalition() === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral"))
this.#showLandButton(getUnitsManager().getSelectedUnitsTypes().length == 1 && ["Aircraft", "Helicopter"].includes(getUnitsManager().getSelectedUnitsTypes()[0]) && (getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral"))
this.#showSpawnButton(getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getMissionHandler().getCommandedCoalition());
this.#setAirbaseData();

View File

@ -412,10 +412,13 @@ export class AircraftSpawnMenu extends UnitSpawnMenu {
for (let i = 1; i < unitsCount + 1; i++) {
units.push(unitTable);
}
if (getUnitsManager().spawnUnits("Aircraft", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country)) {
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition());
getMap().getMapContextMenu().hide();
}
getUnitsManager().spawnUnits("Aircraft", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
if (res.commandHash !== undefined)
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
});
getMap().getMapContextMenu().hide();
}
}
}
@ -447,10 +450,13 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu {
for (let i = 1; i < unitsCount + 1; i++) {
units.push(unitTable);
}
if (getUnitsManager().spawnUnits("Helicopter", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country)) {
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition());
getMap().getMapContextMenu().hide();
}
getUnitsManager().spawnUnits("Helicopter", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
if (res.commandHash !== undefined)
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
});
getMap().getMapContextMenu().hide();
}
}
}
@ -481,10 +487,13 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu {
units.push(JSON.parse(JSON.stringify(unitTable)));
unitTable.location.lat += 0.0001;
}
if (getUnitsManager().spawnUnits("GroundUnit", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country)) {
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition());
getMap().getMapContextMenu().hide();
}
getUnitsManager().spawnUnits("GroundUnit", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
if (res.commandHash !== undefined)
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
});
getMap().getMapContextMenu().hide();
}
}
}
@ -515,10 +524,13 @@ export class NavyUnitSpawnMenu extends UnitSpawnMenu {
units.push(JSON.parse(JSON.stringify(unitTable)));
unitTable.location.lat += 0.0001;
}
if (getUnitsManager().spawnUnits("NavyUnit", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country)) {
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition());
getMap().getMapContextMenu().hide();
}
getUnitsManager().spawnUnits("NavyUnit", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
if (res.commandHash !== undefined)
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
});
getMap().getMapContextMenu().hide();
}
}
}

View File

@ -425,29 +425,11 @@ export class Map extends L.Map {
}
}
addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string) {
var marker = new TemporaryUnitMarker(latlng, name, coalition);
addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string, commandHash?: string) {
var marker = new TemporaryUnitMarker(latlng, name, coalition, commandHash);
marker.addTo(this);
this.#temporaryMarkers.push(marker);
}
removeTemporaryMarker(latlng: L.LatLng) {
// TODO something more refined than this
var dist: number | null = null;
var closest: L.Marker | null = null;
var i: number = 0;
this.#temporaryMarkers.forEach((marker: L.Marker, idx: number) => {
var t = latlng.distanceTo(marker.getLatLng());
if (dist == null || t < dist) {
dist = t;
closest = marker;
i = idx;
}
});
if (closest && dist != null && dist < 100) {
this.removeLayer(closest);
this.#temporaryMarkers.splice(i, 1);
}
return marker;
}
getSelectedCoalitionArea() {

View File

@ -2,15 +2,37 @@ import { CustomMarker } from "./custommarker";
import { DivIcon, LatLng } from "leaflet";
import { SVGInjector } from "@tanem/svg-injector";
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../other/utils";
import { isCommandExecuted } from "../server/server";
import { getMap } from "..";
export class TemporaryUnitMarker extends CustomMarker {
#name: string;
#coalition: string;
#commandHash: string|undefined = undefined;
#timer: number = 0;
constructor(latlng: LatLng, name: string, coalition: string) {
constructor(latlng: LatLng, name: string, coalition: string, commandHash?: string) {
super(latlng, {interactive: false});
this.#name = name;
this.#coalition = coalition;
this.#commandHash = commandHash;
if (commandHash !== undefined)
this.setCommandHash(commandHash)
}
setCommandHash(commandHash: string) {
this.#commandHash = commandHash;
this.#timer = window.setInterval(() => {
if (this.#commandHash !== undefined) {
isCommandExecuted((res: any) => {
if (res.commandExecuted) {
this.removeFrom(getMap());
window.clearInterval(this.#timer);
}
}, this.#commandHash)
}
}, 1000);
}
createIcon() {

View File

@ -1,19 +1,13 @@
import { LatLng } from 'leaflet';
import { getConnectionStatusPanel, getInfoPopup, getLogPanel, getMissionHandler, getServerStatusPanel, getUnitsManager, getWeaponsManager, setLoginStatus } from '..';
import { GeneralSettings, Radio, TACAN } from '../@types/unit';
import { NONE, ROEs, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
import { AIRBASES_URI, BULLSEYE_URI, COMMANDS_URI, LOGS_URI, MISSION_URI, NONE, ROEs, UNITS_URI, WEAPONS_URI, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
var connected: boolean = false;
var paused: boolean = false;
var REST_ADDRESS = "http://localhost:30000/olympus";
var DEMO_ADDRESS = window.location.href + "demo";
const UNITS_URI = "units";
const WEAPONS_URI = "weapons";
const LOGS_URI = "logs";
const AIRBASES_URI = "airbases";
const BULLSEYE_URI = "bullseyes";
const MISSION_URI = "mission";
var username = "";
var password = "";
@ -38,13 +32,15 @@ export function setCredentials(newUsername: string, newPassword: string) {
password = newPassword;
}
export function GET(callback: CallableFunction, uri: string, options?: { time?: number }, responseType?: string) {
export function GET(callback: CallableFunction, uri: string, options?: ServerRequestOptions, responseType?: string) {
var xmlHttp = new XMLHttpRequest();
/* Assemble the request options string */
var optionsString = '';
if (options?.time != undefined)
optionsString = `time=${options.time}`;
if (options?.commandHash != undefined)
optionsString = `commandHash=${options.commandHash}`;
/* On the connection */
xmlHttp.open("GET", `${demoEnabled ? DEMO_ADDRESS : REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true);
@ -92,8 +88,9 @@ export function POST(request: object, callback: CallableFunction) {
xmlHttp.setRequestHeader("Content-Type", "application/json");
if (username && password)
xmlHttp.setRequestHeader("Authorization", "Basic " + btoa(`${username}:${password}`));
xmlHttp.onreadystatechange = () => {
callback();
xmlHttp.onload = (res: any) => {
var res = JSON.parse(xmlHttp.responseText);
callback(res);
};
xmlHttp.send(JSON.stringify(request));
}
@ -140,185 +137,189 @@ export function getWeapons(callback: CallableFunction, refresh: boolean = false)
GET(callback, WEAPONS_URI, { time: refresh ? 0 : lastUpdateTimes[WEAPONS_URI] }, 'arraybuffer');
}
export function addDestination(ID: number, path: any) {
export function isCommandExecuted(callback: CallableFunction, commandHash: string) {
GET(callback, COMMANDS_URI, { commandHash: commandHash});
}
export function addDestination(ID: number, path: any, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "path": path }
var data = { "setPath": command }
POST(data, () => { });
POST(data, callback);
}
export function spawnSmoke(color: string, latlng: LatLng) {
export function spawnSmoke(color: string, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "color": color, "location": latlng };
var data = { "smoke": command }
POST(data, () => { });
POST(data, callback);
}
export function spawnExplosion(intensity: number, latlng: LatLng) {
export function spawnExplosion(intensity: number, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "intensity": intensity, "location": latlng };
var data = { "explosion": command }
POST(data, () => { });
POST(data, callback);
}
export function spawnAircrafts(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number) {
export function spawnAircrafts(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };
var data = { "spawnAircrafts": command }
POST(data, () => { });
POST(data, callback);
}
export function spawnHelicopters(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number) {
export function spawnHelicopters(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };
var data = { "spawnHelicopters": command }
POST(data, () => { });
POST(data, callback);
}
export function spawnGroundUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number) {
export function spawnGroundUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
var command = { "units": units, "coalition": coalition, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };;
var data = { "spawnGroundUnits": command }
POST(data, () => { });
POST(data, callback);
}
export function spawnNavyUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number) {
export function spawnNavyUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
var command = { "units": units, "coalition": coalition, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };
var data = { "spawnNavyUnits": command }
POST(data, () => { });
POST(data, callback);
}
export function attackUnit(ID: number, targetID: number) {
export function attackUnit(ID: number, targetID: number, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "targetID": targetID };
var data = { "attackUnit": command }
POST(data, () => { });
POST(data, callback);
}
export function followUnit(ID: number, targetID: number, offset: { "x": number, "y": number, "z": number }) {
export function followUnit(ID: number, targetID: number, offset: { "x": number, "y": number, "z": number }, callback: CallableFunction = () => {}) {
// X: front-rear, positive front
// Y: top-bottom, positive bottom
// Z: left-right, positive right
var command = { "ID": ID, "targetID": targetID, "offsetX": offset["x"], "offsetY": offset["y"], "offsetZ": offset["z"] };
var data = { "followUnit": command }
POST(data, () => { });
POST(data, callback);
}
export function cloneUnit(ID: number, latlng: LatLng) {
var command = { "ID": ID, "location": latlng };
var data = { "cloneUnit": command }
POST(data, () => { });
export function cloneUnits(units: {ID: number, location: LatLng}[], deleteOriginal: boolean, callback: CallableFunction = () => {}) {
var command = { "units": units, "deleteOriginal": deleteOriginal };
var data = { "cloneUnits": command }
POST(data, callback);
}
export function deleteUnit(ID: number, explosion: boolean, immediate: boolean) {
export function deleteUnit(ID: number, explosion: boolean, immediate: boolean, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "explosion": explosion, "immediate": immediate };
var data = { "deleteUnit": command }
POST(data, () => { });
POST(data, callback);
}
export function landAt(ID: number, latlng: LatLng) {
export function landAt(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "location": latlng };
var data = { "landAt": command }
POST(data, () => { });
POST(data, callback);
}
export function changeSpeed(ID: number, speedChange: string) {
export function changeSpeed(ID: number, speedChange: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "change": speedChange }
var data = { "changeSpeed": command }
POST(data, () => { });
POST(data, callback);
}
export function setSpeed(ID: number, speed: number) {
export function setSpeed(ID: number, speed: number, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "speed": speed }
var data = { "setSpeed": command }
POST(data, () => { });
POST(data, callback);
}
export function setSpeedType(ID: number, speedType: string) {
export function setSpeedType(ID: number, speedType: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "speedType": speedType }
var data = { "setSpeedType": command }
POST(data, () => { });
POST(data, callback);
}
export function changeAltitude(ID: number, altitudeChange: string) {
export function changeAltitude(ID: number, altitudeChange: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "change": altitudeChange }
var data = { "changeAltitude": command }
POST(data, () => { });
POST(data, callback);
}
export function setAltitudeType(ID: number, altitudeType: string) {
export function setAltitudeType(ID: number, altitudeType: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "altitudeType": altitudeType }
var data = { "setAltitudeType": command }
POST(data, () => { });
POST(data, callback);
}
export function setAltitude(ID: number, altitude: number) {
export function setAltitude(ID: number, altitude: number, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "altitude": altitude }
var data = { "setAltitude": command }
POST(data, () => { });
POST(data, callback);
}
export function createFormation(ID: number, isLeader: boolean, wingmenIDs: number[]) {
export function createFormation(ID: number, isLeader: boolean, wingmenIDs: number[], callback: CallableFunction = () => {}) {
var command = { "ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader }
var data = { "setLeader": command }
POST(data, () => { });
POST(data, callback);
}
export function setROE(ID: number, ROE: string) {
export function setROE(ID: number, ROE: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "ROE": ROEs.indexOf(ROE) }
var data = { "setROE": command }
POST(data, () => { });
POST(data, callback);
}
export function setReactionToThreat(ID: number, reactionToThreat: string) {
export function setReactionToThreat(ID: number, reactionToThreat: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "reactionToThreat": reactionsToThreat.indexOf(reactionToThreat) }
var data = { "setReactionToThreat": command }
POST(data, () => { });
POST(data, callback);
}
export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string) {
export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "emissionsCountermeasures": emissionsCountermeasures.indexOf(emissionCountermeasure) }
var data = { "setEmissionsCountermeasures": command }
POST(data, () => { });
POST(data, callback);
}
export function setOnOff(ID: number, onOff: boolean) {
export function setOnOff(ID: number, onOff: boolean, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "onOff": onOff }
var data = { "setOnOff": command }
POST(data, () => { });
POST(data, callback);
}
export function setFollowRoads(ID: number, followRoads: boolean) {
export function setFollowRoads(ID: number, followRoads: boolean, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "followRoads": followRoads }
var data = { "setFollowRoads": command }
POST(data, () => { });
POST(data, callback);
}
export function refuel(ID: number) {
export function refuel(ID: number, callback: CallableFunction = () => {}) {
var command = { "ID": ID };
var data = { "refuel": command }
POST(data, () => { });
POST(data, callback);
}
export function bombPoint(ID: number, latlng: LatLng) {
export function bombPoint(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "location": latlng }
var data = { "bombPoint": command }
POST(data, () => { });
POST(data, callback);
}
export function carpetBomb(ID: number, latlng: LatLng) {
export function carpetBomb(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "location": latlng }
var data = { "carpetBomb": command }
POST(data, () => { });
POST(data, callback);
}
export function bombBuilding(ID: number, latlng: LatLng) {
export function bombBuilding(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "location": latlng }
var data = { "bombBuilding": command }
POST(data, () => { });
POST(data, callback);
}
export function fireAtArea(ID: number, latlng: LatLng) {
export function fireAtArea(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "location": latlng }
var data = { "fireAtArea": command }
POST(data, () => { });
POST(data, callback);
}
export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) {
export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback: CallableFunction = () => {}) {
var command = {
"ID": ID,
"isTanker": isTanker,
@ -329,10 +330,10 @@ export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolea
};
var data = { "setAdvancedOptions": command };
POST(data, () => { });
POST(data, callback);
}
export function setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: {blue: number, red: number}, eras: string[], setupTime: number) {
export function setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: {blue: number, red: number}, eras: string[], setupTime: number, callback: CallableFunction = () => {}) {
var command = {
"restrictSpawns": restrictSpawns,
"restrictToCoalition": restrictToCoalition,
@ -342,7 +343,7 @@ export function setCommandModeOptions(restrictSpawns: boolean, restrictToCoaliti
};
var data = { "setCommandModeOptions": command };
POST(data, () => { });
POST(data, callback);
}
export function startUpdate() {

View File

@ -715,8 +715,6 @@ export class Unit extends CustomMarker {
/***********************************************/
onAdd(map: Map): this {
super.onAdd(map);
/* If this is the first time adding this unit to the map, remove the temporary marker */
getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng));
return this;
}

View File

@ -1,7 +1,7 @@
import { LatLng, LatLngBounds } from "leaflet";
import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitsManager, getWeaponsManager } from "..";
import { Unit } from "./unit";
import { cloneUnit, deleteUnit, refreshAll, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server";
import { cloneUnits, deleteUnit, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server";
import { bearingAndDistanceToLatLng, deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils";
import { CoalitionArea } from "../map/coalitionarea";
import { groundUnitDatabase } from "./groundunitdatabase";
@ -12,6 +12,7 @@ import { citiesDatabase } from "./citiesDatabase";
import { aircraftDatabase } from "./aircraftdatabase";
import { helicopterDatabase } from "./helicopterdatabase";
import { navyUnitDatabase } from "./navyunitdatabase";
import { TemporaryUnitMarker } from "../map/temporaryunitmarker";
export class UnitsManager {
#units: { [ID: number]: Unit };
@ -34,10 +35,10 @@ export class UnitsManager {
document.addEventListener('keyup', (event) => this.#onKeyUp(event));
document.addEventListener('exportToFile', () => this.exportToFile());
document.addEventListener('importFromFile', () => this.importFromFile());
document.addEventListener('contactsUpdated', (e: CustomEvent) => {this.#requestDetectionUpdate = true});
document.addEventListener('commandModeOptionsChanged', () => {Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility())});
document.addEventListener('selectedUnitsChangeSpeed', (e: any) => {this.selectedUnitsChangeSpeed(e.detail.type)});
document.addEventListener('selectedUnitsChangeAltitude', (e: any) => {this.selectedUnitsChangeAltitude(e.detail.type)});
document.addEventListener('contactsUpdated', (e: CustomEvent) => { this.#requestDetectionUpdate = true });
document.addEventListener('commandModeOptionsChanged', () => { Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility()) });
document.addEventListener('selectedUnitsChangeSpeed', (e: any) => { this.selectedUnitsChangeSpeed(e.detail.type) });
document.addEventListener('selectedUnitsChangeAltitude', (e: any) => { this.selectedUnitsChangeAltitude(e.detail.type) });
}
getSelectableAircraft() {
@ -66,7 +67,7 @@ export class UnitsManager {
}
addUnit(ID: number, category: string) {
if (category){
if (category) {
/* The name of the unit category is exactly the same as the constructor name */
var constructor = Unit.getConstructor(category);
if (constructor != undefined) {
@ -91,21 +92,21 @@ export class UnitsManager {
return updateTime;
}
}
this.#units[ID]?.setData(dataExtractor);
this.#units[ID]?.setData(dataExtractor);
}
if (this.#requestDetectionUpdate && getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) {
/* Create a dictionary of empty detection methods arrays */
var detectionMethods: {[key: string]: number[]} = {};
for (let ID in this.#units)
var detectionMethods: { [key: string]: number[] } = {};
for (let ID in this.#units)
detectionMethods[ID] = [];
for (let ID in getWeaponsManager().getWeapons())
for (let ID in getWeaponsManager().getWeapons())
detectionMethods[ID] = [];
/* Fill the array with the detection methods */
for (let ID in this.#units) {
const unit = this.#units[ID];
if (unit.getAlive() && unit.belongsToCommandedCoalition()){
if (unit.getAlive() && unit.belongsToCommandedCoalition()) {
const contacts = unit.getContacts();
contacts.forEach((contact: Contact) => {
const contactID = contact.ID;
@ -196,11 +197,11 @@ export class UnitsManager {
}
}
deselectUnit( ID:number ) {
if ( this.#units.hasOwnProperty( ID ) ) {
deselectUnit(ID: number) {
if (this.#units.hasOwnProperty(ID)) {
this.#units[ID].setSelected(false);
} else {
console.error( `deselectUnit(): no unit found with ID "${ID}".` );
console.error(`deselectUnit(): no unit found with ID "${ID}".`);
}
}
@ -209,38 +210,33 @@ export class UnitsManager {
this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setSelected(true))
}
getSelectedUnitsTypes() {
const selectedUnits = this.getSelectedUnits();
if (selectedUnits.length == 0)
getUnitsTypes(units: Unit[]) {
if (units.length == 0)
return [];
return selectedUnits.map((unit: Unit) => {
return units.map((unit: Unit) => {
return unit.getCategory();
})?.filter((value: any, index: any, array: string[]) => {
return array.indexOf(value) === index;
});
};
}
/* Gets the value of a variable from the selected units. If all the units have the same value, returns the value, else returns undefined */
getSelectedUnitsVariable(variableGetter: CallableFunction) {
const selectedUnits = this.getSelectedUnits();
if (selectedUnits.length == 0)
/* Gets the value of a variable from the units. If all the units have the same value, returns the value, else returns undefined */
getUnitsVariable(variableGetter: CallableFunction, units: Unit[]) {
if (units.length == 0)
return undefined;
return selectedUnits.map((unit: Unit) => {
return units.map((unit: Unit) => {
return variableGetter(unit);
})?.reduce((a: any, b: any) => {
return a === b ? a : undefined
});
};
getSelectedUnitsCoalition() {
const selectedUnits = this.getSelectedUnits();
if (selectedUnits.length == 0)
return undefined;
return selectedUnits.map((unit: Unit) => {
return unit.getCoalition()
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
getSelectedUnitsTypes() {
return this.getUnitsTypes(this.getSelectedUnits());
};
getSelectedUnitsVariable(variableGetter: CallableFunction) {
return this.getUnitsVariable(variableGetter, this.getSelectedUnits());
};
getByType(type: string) {
@ -252,10 +248,9 @@ export class UnitsManager {
getUnitDetectedMethods(unit: Unit) {
var detectionMethods: number[] = [];
for (let idx in this.#units) {
if (this.#units[idx].getAlive() && this.#units[idx].getIsLeader() && this.#units[idx].getCoalition() !== "neutral" && this.#units[idx].getCoalition() != unit.getCoalition())
{
if (this.#units[idx].getAlive() && this.#units[idx].getIsLeader() && 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 (contact.ID == unit.ID && !detectionMethods.includes(contact.detectionMethod))
detectionMethods.push(contact.detectionMethod);
});
}
@ -414,18 +409,18 @@ export class UnitsManager {
selectedUnitsDelete(explosion: boolean = false) {
var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
const selectionContainsAHuman = selectedUnits.some( ( unit:Unit ) => {
const selectionContainsAHuman = selectedUnits.some((unit: Unit) => {
return unit.getHuman() === true;
});
if (selectionContainsAHuman && !confirm( "Your selection includes a human player. Deleting humans causes their vehicle to crash.\n\nAre you sure you want to do this?" ) ) {
if (selectionContainsAHuman && !confirm("Your selection includes a human player. Deleting humans causes their vehicle to crash.\n\nAre you sure you want to do this?")) {
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, immediate);
}
@ -560,30 +555,28 @@ export class UnitsManager {
this.#showActionMessage(selectedUnits, `unit bombing point`);
}
// TODO handle from lua
selectedUnitsCreateGroup() {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: false });
var units = [];
var coalition = "neutral";
var units: { ID: number, location: LatLng }[] = [];
for (let idx in selectedUnits) {
var unit = selectedUnits[idx];
coalition = unit.getCoalition();
deleteUnit(unit.ID, false, true);
units.push({unitType: unit.getName(), location: unit.getPosition()});
units.push({ ID: unit.ID, location: unit.getPosition() });
}
const category = this.getSelectedUnitsTypes()[0];
this.spawnUnits(category, units, coalition, true);
cloneUnits(units, true, () => {
units.forEach((unit: any) => {
deleteUnit(unit.ID, false, false);
});
});
}
/***********************************************/
copyUnits() {
this.#copiedUnits = JSON.parse(JSON.stringify(this.getSelectedUnits().map((unit: Unit) => {return unit.getData()}))); /* Can be applied to humans too */
this.#copiedUnits = JSON.parse(JSON.stringify(this.getSelectedUnits().map((unit: Unit) => { return unit.getData() }))); /* Can be applied to humans too */
getInfoPopup().setText(`${this.#copiedUnits.length} units copied`);
}
// TODO handle from lua
pasteUnits() {
if (!this.#pasteDisabled && getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER) {
if (this.#copiedUnits.length > 0 && !this.#pasteDisabled && getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER) {
/* Compute the position of the center of the copied units */
var nUnits = this.#copiedUnits.length;
var avgLat = 0;
@ -595,44 +588,44 @@ export class UnitsManager {
}
/* Organize the copied units in groups */
var groups: {[key: string]: any} = {};
var groups: { [key: string]: any } = {};
this.#copiedUnits.forEach((unit: any) => {
if (!(unit.groupName in groups))
groups[unit.groupName] = [];
groups[unit.groupName].push(unit);
});
/* Clone the units in groups */
for (let groupName in groups) {
/* Paste the units as groups. Only for ground and navy units because of loadouts, TODO: find a better solution so it works for them too*/
if (!["Aircraft", "Helicopter"].includes(groups[groupName][0].category)) {
var units = groups[groupName].map((unit: any) => {
var position = new LatLng(getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getMap().getMouseCoordinates().lng + unit.position.lng - avgLng);
getMap().addTemporaryMarker(position, unit.name, unit.coalition);
return {unitType: unit.name, location: position, liveryID: ""};
});
this.spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true);
}
else {
groups[groupName].forEach((unit: any) => {
var position = new LatLng(getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getMap().getMouseCoordinates().lng + unit.position.lng - avgLng);
getMap().addTemporaryMarker(position, unit.name, unit.coalition);
cloneUnit(unit.ID, position);
});
}
var units: { ID: number, location: LatLng }[] = [];
let markers: TemporaryUnitMarker[] = [];
groups[groupName].forEach((unit: any) => {
var position = new LatLng(getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getMap().getMouseCoordinates().lng + unit.position.lng - avgLng);
markers.push(getMap().addTemporaryMarker(position, unit.name, unit.coalition));
units.push({ ID: unit.ID, location: position });
});
cloneUnits(units, false, (res: any) => {
if (res.commandHash !== undefined) {
markers.forEach((marker: TemporaryUnitMarker) => {
marker.setCommandHash(res.commandHash);
})
}
});
}
getInfoPopup().setText(`${this.#copiedUnits.length - 1} units pasted`);
getInfoPopup().setText(`${this.#copiedUnits.length} units pasted`);
}
else {
getInfoPopup().setText(`Unit cloning is disabled in ${getMissionHandler().getCommandModeOptions().commandMode} mode`);
}
}
createIADS(coalitionArea: CoalitionArea, types: {[key: string]: boolean}, eras: {[key: string]: boolean}, ranges: {[key: string]: boolean}, density: number, distribution: number) {
const activeTypes = Object.keys(types).filter((key: string) => { return types[key]; });
const activeEras = Object.keys(eras).filter((key: string) => { return eras[key]; });
const activeRanges = Object.keys(ranges).filter((key: string) => { return ranges[key]; });
createIADS(coalitionArea: CoalitionArea, types: { [key: string]: boolean }, eras: { [key: string]: boolean }, ranges: { [key: string]: boolean }, density: number, distribution: number) {
const activeTypes = Object.keys(types).filter((key: string) => { return types[key]; });
const activeEras = Object.keys(eras).filter((key: string) => { return eras[key]; });
const activeRanges = Object.keys(ranges).filter((key: string) => { return ranges[key]; });
citiesDatabase.forEach((city: {lat: number, lng: number, pop: number}) => {
citiesDatabase.forEach((city: { lat: number, lng: number, pop: number }) => {
if (polyContains(new LatLng(city.lat, city.lng), coalitionArea)) {
var pointsNumber = 2 + Math.pow(city.pop, 0.2) * density / 100;
for (let i = 0; i < pointsNumber; i++) {
@ -642,10 +635,9 @@ export class UnitsManager {
if (polyContains(latlng, coalitionArea)) {
const type = activeTypes[Math.floor(Math.random() * activeTypes.length)];
if (Math.random() < IADSDensities[type]) {
const unitBlueprint = randomUnitBlueprint(groundUnitDatabase, {type: type, eras: activeEras, ranges: activeRanges});
const unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { type: type, eras: activeEras, ranges: activeRanges });
if (unitBlueprint) {
this.spawnUnits("GroundUnit", [{unitType: unitBlueprint.name, location: latlng, liveryID: ""}], coalitionArea.getCoalition(), true);
getMap().addTemporaryMarker(latlng, unitBlueprint.name, coalitionArea.getCoalition());
this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "" }], coalitionArea.getCoalition(), true);
}
}
}
@ -655,19 +647,19 @@ export class UnitsManager {
}
exportToFile() {
var unitsToExport: {[key: string]: any} = {};
var unitsToExport: { [key: string]: any } = {};
for (let ID in this.#units) {
var unit = this.#units[ID];
if (!["Aircraft", "Helicopter"].includes(unit.getCategory())) {
var data: any = unit.getData();
if (unit.getGroupName() in unitsToExport)
unitsToExport[unit.getGroupName()].push(data);
else
else
unitsToExport[unit.getGroupName()] = [data];
}
}
var a = document.createElement("a");
var file = new Blob([JSON.stringify(unitsToExport)], {type: 'text/plain'});
var file = new Blob([JSON.stringify(unitsToExport)], { type: 'text/plain' });
a.href = URL.createObjectURL(file);
a.download = 'export.json';
a.click();
@ -682,12 +674,12 @@ export class UnitsManager {
return;
}
var reader = new FileReader();
reader.onload = function(e: any) {
reader.onload = function (e: any) {
var contents = e.target.result;
var groups = JSON.parse(contents);
for (let groupName in groups) {
if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: any) => {return unit.category == "GroundUnit";}) || groups[groupName].every((unit: any) => {return unit.category == "NavyUnit";}))) {
var aliveUnits = groups[groupName].filter((unit: any) => {return unit.alive});
if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: any) => { return unit.category == "GroundUnit"; }) || groups[groupName].every((unit: any) => { return unit.category == "NavyUnit"; }))) {
var aliveUnits = groups[groupName].filter((unit: any) => { return unit.alive });
var units = aliveUnits.map((unit: any) => {
return { unitType: unit.name, location: unit.position, liveryID: "" }
});
@ -700,40 +692,46 @@ export class UnitsManager {
input.click();
}
spawnUnits(category: string, units: any, coalition: string = "blue", immediate: boolean = true, airbase: string = "", country: string = "") {
spawnUnits(category: string, units: any, coalition: string = "blue", immediate: boolean = true, airbase: string = "", country: string = "", callback: CallableFunction = () => {}) {
var spawnPoints = 0;
var spawnFunction = () => {};
var spawnsRestricted = getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER;
if (category === "Aircraft") {
if (airbase == "" && getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
if (airbase == "" && spawnsRestricted) {
getInfoPopup().setText("Aircrafts can be air spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: any) => {return points + aircraftDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints);
spawnFunction = () => spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints, callback);
} else if (category === "Helicopter") {
if (airbase == "" && getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
if (airbase == "" && spawnsRestricted) {
getInfoPopup().setText("Helicopters can be air spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: any) => {return points + helicopterDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints);
spawnFunction = () => spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints, callback);
} else if (category === "GroundUnit") {
if (getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
if (spawnsRestricted) {
getInfoPopup().setText("Ground units can be spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: any) => {return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnGroundUnits(units, coalition, country, immediate, spawnPoints);
spawnFunction = () => spawnGroundUnits(units, coalition, country, immediate, spawnPoints, callback);
} else if (category === "NavyUnit") {
if (getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
if (spawnsRestricted) {
getInfoPopup().setText("Navy units can be spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: any) => {return points + navyUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnNavyUnits(units, coalition, country, immediate, spawnPoints);
spawnFunction = () => spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback);
}
if (spawnPoints <= getMissionHandler().getAvailableSpawnPoints()) {
getMissionHandler().setSpentSpawnPoints(spawnPoints);
spawnFunction();
return true;
} else {
getInfoPopup().setText("Not enough spawn points available!");
@ -747,7 +745,7 @@ export class UnitsManager {
if (event.key === "Delete")
this.selectedUnitsDelete();
else if (event.key === "a" && event.ctrlKey)
Object.values(this.getUnits()).filter((unit: Unit) => {return !unit.getHidden()}).forEach((unit: Unit) => unit.setSelected(true));
Object.values(this.getUnits()).filter((unit: Unit) => { return !unit.getHidden() }).forEach((unit: Unit) => unit.setSelected(true));
}
}

View File

@ -214,8 +214,6 @@ export class Weapon extends CustomMarker {
/***********************************************/
onAdd(map: Map): this {
super.onAdd(map);
/* If this is the first time adding this unit to the map, remove the temporary marker */
getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng));
return this;
}

View File

@ -1,39 +1,50 @@
local version = "v0.4.4-alpha"
local debug = false
local debug = true -- True enables debug printing using DCS messages
-- .dll related variables
Olympus.OlympusDLL = nil
Olympus.DLLsloaded = false
Olympus.OlympusModPath = os.getenv('DCSOLYMPUS_PATH')..'\\bin\\'
-- Logger reference
Olympus.log = mist.Logger:new("Olympus", 'info')
Olympus.unitCounter = 1
Olympus.payloadRegistry = {}
-- Data structures for transfer to .dll
Olympus.missionData = {}
Olympus.unitsData = {}
Olympus.weaponsData = {}
Olympus.unitIndex = 0
Olympus.unitStep = 50
Olympus.units = {}
-- Units data structures
Olympus.unitCounter = 1 -- Counter to generate unique names
Olympus.spawnDatabase = {} -- Database of spawn options, used for units cloning
Olympus.unitIndex = 0 -- Counter used to spread the computational load of data retrievial from DCS
Olympus.unitStep = 50 -- Max number of units that get updated each cycle
Olympus.units = {} -- Table holding references to all the currently existing units
Olympus.weaponIndex = 0
Olympus.weaponStep = 50
Olympus.weapons = {}
Olympus.weaponIndex = 0 -- Counter used to spread the computational load of data retrievial from DCS
Olympus.weaponStep = 50 -- Max number of weapons that get updated each cycle
Olympus.weapons = {} -- Table holding references to all the currently existing weapons
-- Miscellaneous initializations
Olympus.missionStartTime = DCS.getRealTime()
------------------------------------------------------------------------------------------------------
-- Olympus functions
------------------------------------------------------------------------------------------------------
-- Print a debug message if the debug option is true
function Olympus.debug(message, displayFor)
if debug == true then
trigger.action.outText(message, displayFor)
end
end
-- Print a notify message
function Olympus.notify(message, displayFor)
trigger.action.outText(message, displayFor)
end
-- Loads the olympus .dll
function Olympus.loadDLLs()
-- Add the .dll paths
package.cpath = package.cpath..';'..Olympus.OlympusModPath..'?.dll;'
@ -52,6 +63,7 @@ function Olympus.getUnitByID(ID)
return Olympus.units[ID];
end
-- Gets the ID of the first country that belongs to a coalition
function Olympus.getCountryIDByCoalition(coalitionString)
for countryName, countryId in pairs(country.id) do
if coalition.getCountryCoalition(countryId) == Olympus.getCoalitionIDByCoalition(coalitionString) then
@ -61,6 +73,7 @@ function Olympus.getCountryIDByCoalition(coalitionString)
return 0
end
-- Gets the coalition ID of a coalition
function Olympus.getCoalitionIDByCoalition(coalitionString)
local coalitionID = 0
if coalitionString == "red" then
@ -71,6 +84,7 @@ function Olympus.getCoalitionIDByCoalition(coalitionString)
return coalitionID
end
-- Gets the coalition name from the coalition ID
function Olympus.getCoalitionByCoalitionID(coalitionID)
local coalitionString = "neutral"
if coalitionID == 1 then
@ -81,7 +95,7 @@ function Olympus.getCoalitionByCoalitionID(coalitionID)
return coalitionString
end
-- Builds a valid task depending on the provided options
-- Builds a valid enroute task depending on the provided options
function Olympus.buildEnrouteTask(options)
local task = nil
-- Engage specific target by ID. Checks if target exists.
@ -111,11 +125,13 @@ function Olympus.buildEnrouteTask(options)
return task
end
-- Builds a valid task depending on the provided options
-- Builds a valid main task depending on the provided options
function Olympus.buildTask(groupName, options)
local task = nil
local group = Group.getByName(groupName)
-- Combo tasks require nested tables
if (Olympus.isArray(options)) then
local tasks = {}
for idx, subOptions in pairs(options) do
@ -129,6 +145,7 @@ function Olympus.buildTask(groupName, options)
}
Olympus.debug(Olympus.serializeTable(task), 30)
else
-- Follow a unit in formation with a given offset
if options['id'] == 'FollowUnit' and options['leaderID'] and options['offset'] then
local leader = Olympus.getUnitByID(options['leaderID'])
if leader and leader:isExist() then
@ -142,11 +159,13 @@ function Olympus.buildTask(groupName, options)
}
}
end
-- Go refuel to the nearest tanker. If the unit can't refuel it will RTB
elseif options['id'] == 'Refuel' then
task = {
id = 'Refueling',
params = {}
}
-- Orbit in place at a given altitude and with a given pattern
elseif options['id'] == 'Orbit' then
task = {
id = 'Orbit',
@ -173,6 +192,7 @@ function Olympus.buildTask(groupName, options)
if options['speed'] then
task['params']['speed'] = options['speed']
end
-- Bomb a specific location
elseif options['id'] == 'Bombing' and options['lat'] and options['lng'] then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
task = {
@ -182,6 +202,7 @@ function Olympus.buildTask(groupName, options)
attackQty = 1
}
}
-- Perform carpet bombing at a specific location
elseif options['id'] == 'CarpetBombing' and options['lat'] and options['lng'] then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
task = {
@ -196,14 +217,7 @@ function Olympus.buildTask(groupName, options)
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}
}
}
-- Fire at a specific point
elseif options['id'] == 'FireAtPoint' and options['lat'] and options['lng'] and options['radius'] then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
task = {
@ -226,22 +240,17 @@ function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedT
if category == "Aircraft" then
local startPoint = mist.getLeadPos(group)
local endPoint = coord.LLtoLO(lat, lng, 0)
-- 'AGL' mode does not appear to work in the buildWP function. This is a crude approximation
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, turningPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, landing, speed, 0, 'AGL')
}
else
path = {
[1] = mist.fixedWing.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
}
end
-- Create the path
local path = {
[1] = mist.fixedWing.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
}
-- If a task exists assign it to the controller
if taskOptions then
@ -270,22 +279,16 @@ function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedT
local startPoint = mist.getLeadPos(group)
local endPoint = coord.LLtoLO(lat, lng, 0)
-- 'AGL' mode does not appear to work in the buildWP function. This is a crude approximation
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
-- Create the path
local path = {
[1] = mist.heli.buildWP(startPoint, turningPoint, speed, altitude, 'BARO'),
[2] = mist.heli.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
}
-- If a task exists assign it to the controller
if taskOptions then
@ -315,13 +318,12 @@ function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedT
local endPoint = coord.LLtoLO(lat, lng, 0)
local bearing = math.atan2(endPoint.z - startPoint.z, endPoint.x - startPoint.x)
vars =
{
group = group,
point = endPoint,
heading = bearing,
speed = speed
}
vars = {
group = group,
point = endPoint,
heading = bearing,
speed = speed
}
if taskOptions and taskOptions['id'] == 'FollowRoads' and taskOptions['value'] == true then
vars["disableRoads"] = false
@ -331,21 +333,20 @@ function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedT
end
mist.groupToRandomPoint(vars)
Olympus.debug("Olympus.move executed succesfully on GroundUnit", 2)
Olympus.debug("Olympus.move executed successfully on GroundUnit", 2)
elseif category == "NavyUnit" then
local startPoint = mist.getLeadPos(group)
local endPoint = coord.LLtoLO(lat, lng, 0)
local bearing = math.atan2(endPoint.z - startPoint.z, endPoint.x - startPoint.x)
vars =
{
group = group,
point = endPoint,
heading = bearing,
speed = speed
}
vars = {
group = group,
point = endPoint,
heading = bearing,
speed = speed
}
mist.groupToRandomPoint(vars)
Olympus.debug("Olympus.move executed succesfully on NavyUnit", 2)
Olympus.debug("Olympus.move executed successfully on NavyUnit", 2)
else
Olympus.debug("Olympus.move not implemented yet for " .. category, 2)
end
@ -379,31 +380,44 @@ function Olympus.explosion(intensity, lat, lng)
end
-- Spawns a new unit or group
-- Spawn table contains the following parameters
-- category: (string), either Aircraft, Helicopter, GroundUnit or NavyUnit
-- coalition: (string)
-- country: (string)
-- airbaseName: (string, optional) only for air units
-- units: (array) Array of units to spawn. All units will be in the same group. Each unit element must contain:
-- unitType: (string) DCS Name of the unit
-- lat: (number)
-- lng: (number)
-- alt: (number, optional) only for air units
-- loadout: (string, optional) only for air units, must be one of the loadouts defined in unitPayloads.lua
-- payload: (table, optional) overrides loadout, specifies directly the loadout of the unit
-- liveryID: (string, optional)
function Olympus.spawnUnits(spawnTable)
Olympus.debug("Olympus.spawnUnits " .. Olympus.serializeTable(spawnTable), 2)
local unitTable = nil
local unitsTable = nil
local route = nil
local category = nil
-- Generate the units table and rout as per DCS requirements
if spawnTable.category == 'Aircraft' then
unitTable = Olympus.generateAirUnitsTable(spawnTable.units)
unitsTable = Olympus.generateAirUnitsTable(spawnTable.units)
route = Olympus.generateAirUnitsRoute(spawnTable)
category = 'plane'
elseif spawnTable.category == 'Helicopter' then
unitTable = Olympus.generateAirUnitsTable(spawnTable.units)
unitsTable = Olympus.generateAirUnitsTable(spawnTable.units)
route = Olympus.generateAirUnitsRoute(spawnTable)
category = 'helicopter'
elseif spawnTable.category == 'GroundUnit' then
unitTable = Olympus.generateGroundUnitsTable(spawnTable.units)
unitsTable = Olympus.generateGroundUnitsTable(spawnTable.units)
category = 'vehicle'
elseif spawnTable.category == 'NavyUnit' then
unitTable = Olympus.generateNavyUnitsTable(spawnTable.units)
unitsTable = Olympus.generateNavyUnitsTable(spawnTable.units)
category = 'ship'
end
Olympus.debug(Olympus.serializeTable(unitTable), 5)
-- It the unit country is not specified, get a country that belongs to the coalition
local countryID = 0
if spawnTable.country == nil or spawnTable.country == "" then
countryID = Olympus.getCountryIDByCoalition(spawnTable.coalition)
@ -411,9 +425,15 @@ function Olympus.spawnUnits(spawnTable)
countryID = country.id[spawnTable.country]
end
-- Save the units in the database, for cloning
for idx, unitTable in pairs(unitsTable) do
Olympus.addToDatabase(unitTable)
end
-- Spawn the new group
local vars =
{
units = unitTable,
units = unitsTable,
country = countryID,
category = category,
route = route,
@ -426,9 +446,9 @@ function Olympus.spawnUnits(spawnTable)
Olympus.debug("Olympus.spawnUnits completed succesfully", 2)
end
-- Generates unit table for a air unit.
-- Generates unit table for air units
function Olympus.generateAirUnitsTable(units)
local unitTable = {}
local unitsTable = {}
for idx, unit in pairs(units) do
local loadout = unit.loadout -- loadout: a string, one of the names defined in unitPayloads.lua. Must be compatible with the unitType
local payload = unit.payload -- payload: a table, if present the unit will receive this specific payload. Overrides loadout
@ -437,6 +457,7 @@ function Olympus.generateAirUnitsTable(units)
unit.heading = 0
end
-- Define the loadout
if payload == nil then
if loadout and loadout ~= "" and Olympus.unitPayloads[unit.unitType][loadout] then
payload = Olympus.unitPayloads[unit.unitType][loadout]
@ -445,8 +466,9 @@ function Olympus.generateAirUnitsTable(units)
end
end
-- Generate the unit table
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(unit.lat, unit.lng, 0))
unitTable[#unitTable + 1] =
unitsTable[#unitsTable + 1] =
{
["type"] = unit.unitType,
["x"] = spawnLocation.x,
@ -456,15 +478,12 @@ function Olympus.generateAirUnitsTable(units)
["skill"] = "Excellent",
["payload"] = { ["pylons"] = payload, ["fuel"] = 999999, ["flare"] = 60, ["ammo_type"] = 1, ["chaff"] = 60, ["gun"] = 100, },
["heading"] = unit.heading,
["callsign"] = { [1] = 1, [2] = 1, [3] = 1, ["name"] = "Olympus" .. Olympus.unitCounter.. "-" .. #unitTable + 1 },
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitTable + 1,
["callsign"] = { [1] = 1, [2] = 1, [3] = 1, ["name"] = "Olympus" .. Olympus.unitCounter.. "-" .. #unitsTable + 1 },
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1,
["livery_id"] = unit.liveryID
}
-- Add the payload to the registry, used for unit cloning
Olympus.payloadRegistry[unitTable[#unitTable].name] = payload
end
return unitTable
return unitsTable
end
function Olympus.generateAirUnitsRoute(spawnTable)
@ -525,102 +544,192 @@ end
-- Generates ground units table, either single or from template
function Olympus.generateGroundUnitsTable(units)
local unitTable = {}
local unitsTable = {}
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] =
unitsTable[#unitsTable + 1] =
{
["type"] = value.name,
["x"] = spawnLocation.x + value.dx,
["y"] = spawnLocation.z + value.dy,
["heading"] = 0,
["skill"] = "High",
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitTable + 1
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1
}
end
else
unitTable[#unitTable + 1] =
unitsTable[#unitsTable + 1] =
{
["type"] = unit.unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["heading"] = unit.heading,
["skill"] = "High",
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitTable + 1,
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1,
["livery_id"] = unit.liveryID
}
end
end
return unitTable
return unitsTable
end
-- Generates navy units table, either single or from template
function Olympus.generateNavyUnitsTable(units)
local unitTable = {}
local unitsTable = {}
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] =
unitsTable[#unitsTable + 1] =
{
["type"] = value.name,
["x"] = spawnLocation.x + value.dx,
["y"] = spawnLocation.z + value.dy,
["heading"] = 0,
["skill"] = "High",
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitTable + 1,
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1,
["transportable"] = { ["randomTransportable"] = false }
}
end
else
unitTable[#unitTable + 1] =
unitsTable[#unitsTable + 1] =
{
["type"] = unit.unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["heading"] = unit.heading,
["skill"] = "High",
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitTable + 1,
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1,
["transportable"] = { ["randomTransportable"] = false },
["livery_id"] = unit.liveryID
}
end
end
return unitTable
return unitsTable
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)
local unit = Olympus.getUnitByID(ID)
if unit then
local position = unit:getPosition()
local heading = math.atan2( position.x.z, position.x.x )
-- TODO: understand category in this script
local spawnTable = {
coalition = Olympus.getCoalitionByCoalitionID(unit:getCoalition()),
category = category,
units = {
[1] = {
lat = lat,
lng = lng,
alt = unit:getPoint().y,
heading = heading,
unitType = unit:getTypeName(),
payload = Olympus.payloadRegistry[unit:getName()]
}
}
}
Olympus.spawnUnits(spawnTable)
-- Add the unit data to the database, used for unit cloning
function Olympus.addToDatabase(unitTable)
Olympus.spawnDatabase[unitTable.name] = 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 (TO BE VERIFIED).
-- cloneTable is an array of element, each of which contains
-- ID: (number) ID of the unit to clone
-- lat: (number)
-- lng: (number)
function Olympus.clone(cloneTable, deleteOriginal)
Olympus.debug("Olympus.clone " .. Olympus.serializeTable(cloneTable), 2)
local unitsTable = {}
local countryID = nil
local category = nil
local route = {}
-- All the units in the table will be cloned in a single group
for idx, cloneData in pairs(cloneTable) do
local ID = cloneData.ID
local unit = Olympus.getUnitByID(ID)
if unit then
local position = unit:getPosition()
local heading = math.atan2( position.x.z, position.x.x )
-- Update the data of the cloned unit
local unitTable = mist.utils.deepCopy(Olympus.spawnDatabase[unit:getName()])
local point = coord.LLtoLO(cloneData['lat'], cloneData['lng'], 0)
if unitTable then
unitTable["x"] = point.x
unitTable["y"] = point.z
unitTable["alt"] = unit:getPoint().y
unitTable["heading"] = heading
unitTable["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1
end
if countryID == nil and category == nil then
countryID = unit:getCountry()
if unit:getDesc().category == Unit.Category.AIRPLANE then
category = 'plane'
route = {
["points"] =
{
[1] =
{
["alt"] = alt,
["alt_type"] = "BARO",
["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"] = point.x,
["y"] = point.z,
},
},
}
elseif unit:getDesc().category == Unit.Category.HELICOPTER then
category = 'helicopter'
route = {
["points"] =
{
[1] =
{
["alt"] = alt,
["alt_type"] = "BARO",
["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"] = point.x,
["y"] = point.z,
},
},
}
elseif unit:getDesc().category == Unit.Category.GROUND_UNIT then
category = 'vehicle'
elseif unit:getDesc().category == Unit.Category.SHIP then
category = 'ship'
end
end
unitsTable[#unitsTable + 1] = mist.utils.deepCopy(unitTable)
end
if deleteOriginal then
Olympus.delete(ID, false)
end
end
local vars =
{
units = unitsTable,
country = countryID,
category = category,
route = route,
name = "Olympus-" .. Olympus.unitCounter,
task = 'CAP'
}
Olympus.debug(Olympus.serializeTable(vars), 1)
-- Save the units in the database, for cloning
for idx, unitTable in pairs(unitsTable) do
Olympus.addToDatabase(unitTable)
end
mist.dynAdd(vars)
Olympus.unitCounter = Olympus.unitCounter + 1
Olympus.debug("Olympus.clone completed successfully", 2)
end
-- Delete a unit by ID, optionally use an explosion
function Olympus.delete(ID, explosion)
Olympus.debug("Olympus.delete " .. ID .. " " .. tostring(explosion), 2)
local unit = Olympus.getUnitByID(ID)
@ -635,6 +744,7 @@ function Olympus.delete(ID, explosion)
end
end
-- Set a DCS main task to a group
function Olympus.setTask(groupName, taskOptions)
Olympus.debug("Olympus.setTask " .. groupName .. " " .. Olympus.serializeTable(taskOptions), 2)
local group = Group.getByName(groupName)
@ -648,6 +758,7 @@ function Olympus.setTask(groupName, taskOptions)
end
end
-- Reset the dask of a group
function Olympus.resetTask(groupName)
Olympus.debug("Olympus.resetTask " .. groupName, 2)
local group = Group.getByName(groupName)
@ -657,6 +768,7 @@ function Olympus.resetTask(groupName)
end
end
-- Give a group a command
function Olympus.setCommand(groupName, command)
Olympus.debug("Olympus.setCommand " .. groupName .. " " .. Olympus.serializeTable(command), 2)
local group = Group.getByName(groupName)
@ -666,6 +778,7 @@ function Olympus.setCommand(groupName, command)
end
end
-- Set an option of a group
function Olympus.setOption(groupName, optionID, optionValue)
Olympus.debug("Olympus.setOption " .. groupName .. " " .. optionID .. " " .. tostring(optionValue), 2)
local group = Group.getByName(groupName)
@ -675,6 +788,7 @@ function Olympus.setOption(groupName, optionID, optionValue)
end
end
-- Disable the AI of a group on or off entirely
function Olympus.setOnOff(groupName, onOff)
Olympus.debug("Olympus.setOnOff " .. groupName .. " " .. tostring(onOff), 2)
local group = Group.getByName(groupName)
@ -684,6 +798,7 @@ function Olympus.setOnOff(groupName, onOff)
end
end
-- This function is periodically called to collect the data of all the existing units in the mission to be transmitted to the olympus.dll
function Olympus.setUnitsData(arg, time)
-- Units data
local units = {}
@ -693,9 +808,11 @@ function Olympus.setUnitsData(arg, time)
local index = 0
for ID, unit in pairs(Olympus.units) do
index = index + 1
-- Only the indexes between startIndex and endIndex are handled. This is a simple way to spread the update load over many cycles
if index > startIndex then
if unit ~= nil then
local table = {}
local table = {}
-- Get the object category in Olympus name
local objectCategory = unit:getCategory()
if objectCategory == Object.Category.UNIT then
@ -765,6 +882,7 @@ function Olympus.setUnitsData(arg, time)
end
end
else
-- If the unit reference is nil it means the unit no longer exits
units[ID] = {isAlive = false}
Olympus.units[ID] = nil
end
@ -773,6 +891,8 @@ function Olympus.setUnitsData(arg, time)
break
end
end
-- Reset the counter
if index ~= endIndex then
Olympus.unitIndex = 0
else
@ -786,6 +906,7 @@ function Olympus.setUnitsData(arg, time)
return time + 0.05
end
-- This function is periodically called to collect the data of all the existing weapons in the mission to be transmitted to the olympus.dll
function Olympus.setWeaponsData(arg, time)
-- Weapons data
local weapons = {}
@ -795,6 +916,8 @@ function Olympus.setWeaponsData(arg, time)
local index = 0
for ID, weapon in pairs(Olympus.weapons) do
index = index + 1
-- Only the indexes between startIndex and endIndex are handled. This is a simple way to spread the update load over many cycles
if index > startIndex then
if weapon ~= nil then
local table = {}
@ -835,6 +958,7 @@ function Olympus.setWeaponsData(arg, time)
weapons[ID] = table
end
else
-- If the weapon reference is nil it means the unit no longer exits
weapons[ID] = {isAlive = false}
Olympus.weapons[ID] = nil
end
@ -843,6 +967,8 @@ function Olympus.setWeaponsData(arg, time)
break
end
end
-- Reset the counter
if index ~= endIndex then
Olympus.weaponIndex = 0
else
@ -910,13 +1036,14 @@ function Olympus.setMissionData(arg, time)
Olympus.missionData["mission"] = mission
Olympus.OlympusDLL.setMissionData()
return time + 1
return time + 1 -- For perfomance reasons weapons are updated once every second
end
-- Initializes the units table with all the existing ME units
function Olympus.initializeUnits()
if mist and mist.DBs and mist.DBs.MEunitsById then
for id, unitTable in pairs(mist.DBs.MEunitsById) do
local unit = Unit.getByName(unitTable["unitName"])
for id, unitsTable in pairs(mist.DBs.MEunitsById) do
local unit = Unit.getByName(unitsTable["unitName"])
if unit then
Olympus.units[unit["id_"]] = unit
end
@ -928,6 +1055,9 @@ function Olympus.initializeUnits()
end
end
------------------------------------------------------------------------------------------------------
-- Olympus utility functions
------------------------------------------------------------------------------------------------------
function Olympus.serializeTable(val, name, skipnewlines, depth)
skipnewlines = skipnewlines or false
depth = depth or 0

View File

@ -98,9 +98,11 @@ public:
unsigned int getPriority() { return priority; }
virtual string getString() = 0;
virtual unsigned int getLoad() = 0;
const string getHash() { return hash; }
protected:
unsigned int priority = CommandPriority::LOW;
const string hash = random_string(16);
};
/* Simple low priority move command (from user click) */
@ -248,9 +250,9 @@ private:
class Clone : public Command
{
public:
Clone(unsigned int ID, Coords location) :
ID(ID),
location(location)
Clone(vector<CloneOptions> cloneOptions, bool deleteOriginal) :
cloneOptions(cloneOptions),
deleteOriginal(deleteOriginal)
{
priority = CommandPriority::LOW;
};
@ -258,8 +260,8 @@ public:
virtual unsigned int getLoad() { return 30; }
private:
const unsigned int ID;
const Coords location;
const vector<CloneOptions> cloneOptions;
const bool deleteOriginal;
};
/* Delete unit command */

View File

@ -120,4 +120,9 @@ struct SpawnOptions {
Coords location;
string loadout;
string liveryID;
};
struct CloneOptions {
unsigned int ID;
Coords location;
};

View File

@ -11,9 +11,10 @@ public:
void appendCommand(Command* command);
void execute(lua_State* L);
void handleRequest(string key, json::value value, string username);
void handleRequest(string key, json::value value, string username, json::value& answer);
bool checkSpawnPoints(int spawnPoints, string coalition);
bool isCommandExecuted(string commandHash) { return (find(executedCommandsHashes.begin(), executedCommandsHashes.end(), commandHash) != executedCommandsHashes.end()); }
void setFrameRate(double newFrameRate) { frameRate = newFrameRate; }
void setRestrictSpawns(bool newRestrictSpawns) { restrictSpawns = newRestrictSpawns; }
void setRestrictToCoalition(bool newRestrictToCoalition) { restrictToCoalition = newRestrictToCoalition; }
@ -35,6 +36,7 @@ public:
private:
list<Command*> commands;
list<string> executedCommandsHashes;
unsigned int load;
double frameRate;

View File

@ -140,22 +140,22 @@ string SpawnHelicopters::getString()
/* Clone unit command */
string Clone::getString()
{
Unit* unit = unitsManager->getUnit(ID);
if (unit != nullptr)
{
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.clone, "
<< ID << ", "
<< location.lat << ", "
<< location.lng << ", "
<< "\"" << unit->getCategory() << "\"";
return commandSS.str();
}
else
{
return "";
std::ostringstream unitsSS;
unitsSS.precision(10);
for (int i = 0; i < cloneOptions.size(); i++) {
unitsSS << "[" << i + 1 << "] = {"
<< "ID = " << cloneOptions[i].ID << ", "
<< "lat = " << cloneOptions[i].location.lat << ", "
<< "lng = " << cloneOptions[i].location.lng << " }, ";
}
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.clone, "
<< "{" << unitsSS.str() << "}" << ", "
<< (deleteOriginal ? "true" : "false");
return commandSS.str();
}
/* Delete unit command */

View File

@ -60,6 +60,7 @@ void Scheduler::execute(lua_State* L)
log("Command '" + commandString + "' executed correctly, current load " + to_string(getLoad()));
load = command->getLoad();
commands.remove(command);
executedCommandsHashes.push_back(command->getHash());
delete command;
return;
}
@ -134,7 +135,7 @@ bool Scheduler::checkSpawnPoints(int spawnPoints, string coalition)
}
}
void Scheduler::handleRequest(string key, json::value value, string username)
void Scheduler::handleRequest(string key, json::value value, string username, json::value& answer)
{
Command* command = nullptr;
@ -376,13 +377,21 @@ void Scheduler::handleRequest(string key, json::value value, string username)
if (unit != nullptr)
unit->setDesiredAltitudeType(to_string(value[L"altitudeType"]));
}
else if (key.compare("cloneUnit") == 0)
else if (key.compare("cloneUnits") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
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<Command*>(new Clone(ID, loc));
vector<CloneOptions> cloneOptions;
bool deleteOriginal = value[L"deleteOriginal"].as_bool();
for (auto unit : value[L"units"].as_array()) {
unsigned int ID = unit[L"ID"].as_integer();
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;
cloneOptions.push_back({ ID, location });
}
command = dynamic_cast<Command*>(new Clone(cloneOptions, deleteOriginal));
}
else if (key.compare("setROE") == 0)
{
@ -565,6 +574,7 @@ void Scheduler::handleRequest(string key, json::value value, string username)
{
appendCommand(command);
log("New command appended correctly to stack. Current server load: " + to_string(getLoad()));
answer[L"commandHash"] = json::value(to_wstring(command->getHash()));
}
}

View File

@ -142,6 +142,9 @@ void Server::handle_get(http_request request)
else
answer[L"mission"][L"commandModeOptions"][L"commandMode"] = json::value(L"Observer");
}
else if (URI.compare(COMMANDS_URI) == 0 && query.find(L"commandHash") != query.end()) {
answer[L"commandExecuted"] = json::value(scheduler->isCommandExecuted(to_string(query[L"commandHash"])));
}
/* Common data */
answer[L"time"] = json::value::string(to_wstring(ms.count()));
@ -222,7 +225,7 @@ void Server::handle_put(http_request request)
std::exception_ptr eptr;
try {
scheduler->handleRequest(to_string(key), value, username);
scheduler->handleRequest(to_string(key), value, username, answer);
}
catch (...) {
eptr = std::current_exception(); // capture

View File

@ -10,5 +10,6 @@
#define AIRBASES_URI "airbases"
#define BULLSEYE_URI "bullseyes"
#define MISSION_URI "mission"
#define COMMANDS_URI "commands"
#define FRAMERATE_TIME_INTERVAL 0.05