Performance optimizations for large unit counts

This commit is contained in:
Pax1601 2023-07-18 21:56:56 +02:00
parent 785647ad24
commit a4db569fbd
43 changed files with 1188 additions and 580 deletions

View File

@ -15,7 +15,7 @@ const DEMO_UNIT_DATA = {
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
contacts: [{ID: 2, detectionMethod: 1}],
contacts: [{ID: 2, detectionMethod: 1}, {ID: 3, detectionMethod: 4}],
activePath: [{lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0}]
},
["2"]:{ category: "Aircraft", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "FA-18C_hornet", unitName: "Cool guy 1-2", groupName: "Cool group 2", state: 1, task: "Being cool",
@ -33,7 +33,7 @@ const DEMO_UNIT_DATA = {
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
contacts: [{ID: 1, detectionMethod: 16}],
activePath: [ ]
}, ["3"]:{ category: "GroundUnit", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "M-60", unitName: "Cool guy 1-3", groupName: "Cool group 3", state: 1, task: "Being cool",
}, ["3"]:{ category: "Missile", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "", unitName: "Cool guy 1-3", groupName: "Cool group 3", state: 1, task: "Being cool",
hasTask: false, position: { lat: 37.1, lng: -116, alt: 1000 }, speed: 200, heading: 315 * Math.PI / 180, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50,
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
formationOffset: { x: 0, y: 0, z: 0 },
@ -343,7 +343,7 @@ class DemoDataGenerator {
};
mission(req, res){
var ret = {mission: {theatre: "Syria"}};
var ret = {mission: {theatre: "Nevada"}};
ret.time = Date.now();
var auth = req.get("Authorization");
if (auth) {

View File

@ -38,7 +38,7 @@
font-size: 12px;
position: absolute;
right: 10px;
width: 180px;
width: 250px;
z-index: 9999;
}

View File

@ -20,6 +20,7 @@
html * {
font-family: 'Open Sans', sans-serif !important;
user-select: none;
}
body {

View File

@ -11,7 +11,7 @@
}
#connection-status-panel[data-is-connected] dt::before {
content: "Connected";
content: "Connected FPS: " attr(data-framerate) " Load: " attr(data-load);
}
#connection-status-panel[data-is-connected] dd::after {

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="50"
height="50"
viewBox="0 0 50 50"
fill="none"
version="1.1"
id="svg8"
sodipodi:docname="groundunit-ewr.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
x="25"
y="2.37241"
width="32"
height="32"
rx="1.1"
transform="rotate(45 25 2.37241)"
stroke="none"
stroke-width="2"
id="rect2" />
<rect
x="25"
y="5.20083"
width="28"
height="28"
transform="rotate(45 25 5.20083)"
fill="none"
stroke="#082E44"
stroke-width="2"
id="rect4" />
<path
stroke="#082E44"
stroke-width="1.5"
d="m 21.956541,27.690987 5.894515,-3.34903 -3.846924,-2.326589 5.759192,-3.618211"
id="path942"
sodipodi:nodetypes="cccc" />
<path
stroke="#082E44"
stroke-width="1.5"
d="m 26.663842,17.477657 3.292749,0.82871 -0.740705,2.943076"
id="path1230" />
<path
stroke="#082E44"
stroke-width="2"
id="path1657"
sodipodi:type="arc"
sodipodi:cx="27.107115"
sodipodi:cy="22.407019"
sodipodi:rx="10.069912"
sodipodi:ry="10.084956"
sodipodi:start="1.0594149"
sodipodi:end="3.5325464"
sodipodi:arc-type="arc"
d="M 32.035153,31.201799 A 10.069912,10.084956 0 0 1 20.42801,29.95437 10.069912,10.084956 0 0 1 17.797018,18.563941"
sodipodi:open="true" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="50"
height="50"
viewBox="0 0 50 50"
fill="none"
version="1.1"
id="svg6"
sodipodi:docname="groundunit-sam-launcher.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<path
fill="#3BB9FF"
stroke="none"
stroke-width="2"
d="M45.7733 41.3423L25.9481 7.63951C25.5228 6.91648 24.4772 6.91646 24.0519 7.63951L4.22671 41.3423C3.79536 42.0756 4.32409 43 5.17484 43H44.8252C45.6759 43 46.2046 42.0756 45.7733 41.3423Z"
id="path2" />
<path
d="M6.74842 41L25 9.97231L43.2516 41H6.74842Z"
fill="none"
stroke="#082E44"
stroke-width="2"
id="path4" />
<path
stroke="#082E44"
stroke-width="2"
id="path849"
sodipodi:type="arc"
sodipodi:cx="25.284166"
sodipodi:cy="43.365532"
sodipodi:rx="8.2439137"
sodipodi:ry="8.2651606"
sodipodi:start="3.8397244"
sodipodi:end="5.5850536"
sodipodi:arc-type="arc"
d="m 18.968962,38.052789 a 8.2439137,8.2651606 0 0 1 6.315204,-2.952418 8.2439137,8.2651606 0 0 1 6.315205,2.952418"
sodipodi:open="true" />
<path
stroke="#082E44"
stroke-width="1.5"
d="M 25.114189,34.887901 V 19.887378"
id="path1085" />
<path
stroke="#082E44"
stroke-width="1.5"
d="m 23.074458,23.244436 1.981982,-3.432894 1.923784,3.332092"
id="path1087" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="50"
height="50"
viewBox="0 0 50 50"
fill="none"
version="1.1"
id="svg6"
sodipodi:docname="groundunit-sam-radar.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<path
d="M45.7733 41.3423L25.9481 7.63951C25.5228 6.91648 24.4772 6.91646 24.0519 7.63951L4.22671 41.3423C3.79536 42.0756 4.32409 43 5.17484 43H44.8252C45.6759 43 46.2046 42.0756 45.7733 41.3423Z"
fill="#3BB9FF"
stroke="none"
stroke-width="2"
id="path2" />
<path
d="M6.74842 41L25 9.97231L43.2516 41H6.74842Z"
fill="none"
stroke="#082E44"
stroke-width="2"
id="path4"
style="fill:none;fill-opacity:1" />
<path
style="fill:none;stroke:#082e44;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="m 23.148216,33.643859 5.192016,-2.949206 -3.388454,-2.048829 5.07282,-3.186251"
id="path942"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#082e44;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="m 27.294508,24.649847 2.900324,0.729775 -0.652429,2.591717"
id="path1230" />
<path
style="fill:none;fill-opacity:1;stroke:#082e44;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="path1657"
sodipodi:type="arc"
sodipodi:cx="27.684952"
sodipodi:cy="28.990719"
sodipodi:rx="8.8697948"
sodipodi:ry="8.8809643"
sodipodi:start="1.0594149"
sodipodi:end="3.5325464"
sodipodi:arc-type="arc"
sodipodi:open="true"
d="M 32.025673,36.735535 A 8.8697948,8.8809643 0 0 1 21.801853,35.63703 8.8697948,8.8809643 0 0 1 19.484418,25.606446" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -19,6 +19,7 @@ interface CustomEventMap {
"mapStateChanged": CustomEvent<string>,
"mapContextMenu": CustomEvent<>,
"visibilityModeChanged": CustomEvent<string>,
"contactsUpdated": CustomEvent<Unit>,
}
declare global {

View File

@ -142,8 +142,8 @@ export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"];
export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
export const IADSTypes = ["AAA", "MANPADS", "SAM Sites", "Radar"];
export const IADSDensities: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Sites": 0.1, "Radar": 0.05};
export const IADSTypes = ["AAA", "MANPADS", "SAM Site", "Radar"];
export const IADSDensities: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Site": 0.1, "Radar": 0.05};
export enum DataIndexes {
startOfData = 0,
@ -184,5 +184,6 @@ export enum DataIndexes {
ammo,
contacts,
activePath,
isLeader,
endOfData = 255
};

View File

@ -67,7 +67,7 @@ export class Map extends L.Map {
constructor(ID: string) {
/* Init the leaflet map */
//@ts-ignore Needed because the boxSelect option is non-standard
super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
super(ID, { preferCanvas: true, doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
this.setView([37.23, -115.8], 10);
this.#ID = ID;
@ -295,6 +295,10 @@ export class Map extends L.Map {
}
}
getCenterUnit() {
return this.#centerUnit;
}
setTheatre(theatre: string) {
var bounds = new L.LatLngBounds([-90, -180], [90, 180]);
var miniMapZoom = 5;
@ -424,9 +428,6 @@ export class Map extends L.Map {
#onDoubleClick(e: any) {
this.deselectAllCoalitionAreas();
var db = groundUnitDatabase;
db.generateTestGrid(this.getMouseCoordinates())
}
#onContextMenu(e: any) {

View File

@ -280,9 +280,17 @@ export function getMarkerCategoryByName(name: string) {
else if (helicopterDatabase.getByName(name) != null)
return "helicopter";
else if (groundUnitDatabase.getByName(name) != null){
// TODO this is very messy
var type = groundUnitDatabase.getByName(name)?.type;
return (type?.includes("SAM")) ? "groundunit-sam" : "groundunit-other";
if (type === "SAM")
return "groundunit-sam";
else if (type === "SAM Search radar" || type === "SAM Track radar" || type === "SAM Search/Track radar")
return "groundunit-sam-radar";
else if (type === "SAM Launcher")
return "groundunit-sam-launcher";
else if (type === "Radar")
return "groundunit-ewr";
else
return "groundunit-other";
}
else
return "groundunit-other"; // TODO add other unit types

View File

@ -8,4 +8,12 @@ export class ConnectionStatusPanel extends Panel {
update(connected: boolean) {
this.getElement().toggleAttribute( "data-is-connected", connected );
}
setMetrics(frameRate: number, load: number) {
const dt = this.getElement().querySelector("dt");
if (dt) {
dt.dataset["framerate"] = String(frameRate);
dt.dataset["load"] = String(load);
}
}
}

View File

@ -141,7 +141,7 @@ export class UnitControlPanel extends Panel {
element.toggleAttribute("data-show-advanced-settings-button", units.length == 1);
/* Flight controls */
var desiredAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()});
var desiredAltitude = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()});
var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()});
var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()});
var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()});

View File

@ -18,7 +18,7 @@ var username = "";
var password = "";
var sessionHash: string | null = null;
var lastUpdateTime = 0;
var lastUpdateTimes: {[key: string]: number} = {}
var demoEnabled = false;
export function toggleDemoEnabled() {
@ -54,9 +54,14 @@ export function GET(callback: CallableFunction, uri: string, options?: { time?:
/* Success */
setConnected(true);
if (xmlHttp.responseType == 'arraybuffer')
callback(xmlHttp.response);
else
callback(JSON.parse(xmlHttp.responseText));
lastUpdateTimes[uri] = callback(xmlHttp.response);
else {
const result = JSON.parse(xmlHttp.responseText);
lastUpdateTimes[uri] = callback(result);
if ("frameRate" in result && "load" in result)
getConnectionStatusPanel().setMetrics(result.frameRate, result.load);
}
} else if (xmlHttp.status == 401) {
/* Bad credentials */
console.error("Incorrect username/password");
@ -103,10 +108,6 @@ export function setAddress(address: string, port: number) {
console.log(`Setting REST address to ${REST_ADDRESS}`)
}
export function setLastUpdateTime(newLastUpdateTime: number) {
lastUpdateTime = newLastUpdateTime;
}
export function getAirbases(callback: CallableFunction) {
GET(callback, AIRBASES_URI);
}
@ -115,8 +116,8 @@ export function getBullseye(callback: CallableFunction) {
GET(callback, BULLSEYE_URI);
}
export function getLogs(callback: CallableFunction) {
GET(callback, LOGS_URI);
export function getLogs(callback: CallableFunction, refresh: boolean = false) {
GET(callback, LOGS_URI, { time: refresh ? 0 : lastUpdateTimes[LOGS_URI]});
}
export function getMission(callback: CallableFunction) {
@ -124,7 +125,7 @@ export function getMission(callback: CallableFunction) {
}
export function getUnits(callback: CallableFunction, refresh: boolean = false) {
GET(callback, `${UNITS_URI}`, { time: refresh ? 0 : lastUpdateTime }, 'arraybuffer');
GET(callback, UNITS_URI, { time: refresh ? 0 : lastUpdateTimes[UNITS_URI] }, 'arraybuffer');
}
export function addDestination(ID: number, path: any) {
@ -327,7 +328,7 @@ export function startUpdate() {
getMissionData()?.update(data);
checkSessionHash(data.sessionHash);
});
getUnits((buffer: ArrayBuffer) => getUnitsManager()?.update(buffer), true /* Does a full refresh */);
getUnits((buffer: ArrayBuffer) => {return getUnitsManager()?.update(buffer), true /* Does a full refresh */});
requestUpdate();
requestRefresh();
@ -336,7 +337,7 @@ export function startUpdate() {
export function requestUpdate() {
/* Main update rate = 250ms is minimum time, equal to server update time. */
if (!getPaused()) {
getUnits((buffer: ArrayBuffer) => { getUnitsManager()?.update(buffer); }, false);
getUnits((buffer: ArrayBuffer) => { return getUnitsManager()?.update(buffer); }, false);
}
window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000);
@ -348,6 +349,13 @@ export function requestRefresh() {
if (!getPaused()) {
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
getBullseye((data: BullseyesData) => getMissionData()?.update(data));
getLogs((data: any) => {
for (let key in data.logs) {
if (key != "requestTime")
console.log(data.logs[key]);
}
return data.time;
});
getMission((data: any) => {
checkSessionHash(data.sessionHash);
getMissionData()?.update(data)

View File

@ -12,7 +12,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "SA-2 SAM Battery",
"range": "Long",
"filename": "",
"type": "SAM Sites"
"type": "SAM Site"
},
"SA-3 SAM Battery": {
"name": "SA-3 SAM Battery",
@ -22,7 +22,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "SA-3 SAM Battery",
"range": "Medium",
"filename": "",
"type": "SAM Sites"
"type": "SAM Site"
},
"SA-6 SAM Battery": {
"name": "SA-6 SAM Battery",
@ -32,7 +32,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "SA-6 SAM Battery",
"range": "Medium",
"filename": "",
"type": "SAM Sites"
"type": "SAM Site"
},
"SA-10 SAM Battery": {
"name": "SA-10 SAM Battery",
@ -42,7 +42,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "SA-10 SAM Battery",
"range": "Long",
"filename": "",
"type": "SAM Sites"
"type": "SAM Site"
},
"SA-11 SAM Battery": {
"name": "SA-11 SAM Battery",
@ -52,7 +52,17 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "SA-11 SAM Battery",
"range": "Medium",
"filename": "",
"type": "SAM Sites"
"type": "SAM Site"
},
"SA-5 SAM Battery": {
"name": "SA-5 SAM Battery",
"coalition": "Red",
"era": "Mid Cold War",
"label": "SA-5 SAM Battery",
"shortLabel": "SA-5 SAM Battery",
"range": "Long",
"filename": "",
"type": "SAM Site"
},
"Patriot site": {
"name": "Patriot site",
@ -62,7 +72,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "Patriot site",
"range": "Long",
"filename": "",
"type": "SAM Sites"
"type": "SAM Site"
},
"Hawk SAM Battery": {
"name": "Hawk SAM Battery",
@ -72,7 +82,25 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "Hawk SAM Battery",
"range": "Medium",
"filename": "",
"type": "SAM Sites"
"type": "SAM Site"
},
"SNR_75V": {
"name": "SNR_75V",
"coalition": "Red",
"era": "Early Cold War",
"label": "SA-2 Fan Song",
"shortLabel": "SNR 75V",
"filename": "",
"type": "SAM Track radar"
},
"S_75M_Volhov": {
"name": "S_75M_Volhov",
"coalition": "Red",
"era": "Early Cold War",
"label": "SA-2 Launcher",
"shortLabel": "S75M Volhov",
"filename": "",
"type": "SAM Launcher"
},
"2B11 mortar": {
"name": "2B11 mortar",
@ -457,61 +485,61 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Kub 2P25 ln",
"coalition": "Red",
"era": "Late Cold War",
"label": "SA-6 Kub 2P25 ln",
"label": "SA-6 Launcher",
"shortLabel": "Kub 2P25 ln",
"range": "Medium",
"filename": "",
"type": "SAM"
"type": "SAM Launcher"
},
"5p73 s-125 ln": {
"name": "5p73 s-125 ln",
"coalition": "Red",
"era": "Early Cold War",
"label": "SA-3 5p73 s-125 ln",
"label": "SA-3 Launcher",
"shortLabel": "5p73 s-125 ln",
"range": "Medium",
"filename": "",
"type": "SAM"
"type": "SAM Launcher"
},
"S-300PS 5P85C ln": {
"name": "S-300PS 5P85C ln",
"coalition": "Red",
"era": "Late Cold War",
"label": "SA-10 S-300PS 5P85C ln",
"label": "SA-10 Launcher (5P85C)",
"shortLabel": "S-300PS 5P85C ln",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Launcher"
},
"S-300PS 5P85D ln": {
"name": "S-300PS 5P85D ln",
"coalition": "Red",
"era": "Late Cold War",
"label": "SA-10 S-300PS 5P85D ln",
"label": "SA-10 Launcher (5P85D)",
"shortLabel": "S-300PS 5P85D ln",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Launcher"
},
"SA-11 Buk LN 9A310M1": {
"name": "SA-11 Buk LN 9A310M1",
"coalition": "Red",
"era": "Late Cold War",
"label": "SA-11 Buk LN 9A310M1",
"label": "SA-11 Launcher",
"shortLabel": "SA-11 Buk LN 9A310M1",
"range": "Medium",
"filename": "",
"type": "SAM"
"type": "SAM Launcher"
},
"Osa 9A33 ln": {
"name": "Osa 9A33 ln",
"coalition": "Red",
"era": "Mid Cold War",
"label": "SA-8 Osa 9A33 ln",
"label": "SA-8 Launcher",
"shortLabel": "Osa 9A33 ln",
"range": "Short",
"filename": "",
"type": "SAM"
"type": "SAM Launcher"
},
"Tor 9A331": {
"name": "Tor 9A331",
@ -547,11 +575,11 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "SA-11 Buk CC 9S470M1",
"coalition": "Red",
"era": "Late Cold War",
"label": "SA-11 Buk CC 9S470M1",
"label": "SA-11 Command Post",
"shortLabel": "SA-11 Buk CC 9S470M1",
"range": "Medium",
"filename": "",
"type": "SAM"
"type": "SAM Support vehicle"
},
"SA-8 Osa LD 9T217": {
"name": "SA-8 Osa LD 9T217",
@ -567,21 +595,21 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Patriot AMG",
"coalition": "Blue",
"era": "Modern",
"label": "Patriot AMG",
"label": "Patriot Antenna Mast Group",
"shortLabel": "Patriot AMG",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Support vehicle"
},
"Patriot ECS": {
"name": "Patriot ECS",
"coalition": "Blue",
"era": "Modern",
"label": "Patriot ECS",
"label": "Patriot Engagement Control Station",
"shortLabel": "Patriot ECS",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Support vehicle"
},
"Gepard": {
"name": "Gepard",
@ -596,11 +624,11 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Hawk pcp",
"coalition": "Blue",
"era": "Late Cold War",
"label": "Hawk pcp",
"label": "Hawk Platoon Command Post",
"shortLabel": "Hawk pcp",
"range": "Medium",
"filename": "",
"type": "SAM"
"type": "SAM Support vehicle"
},
"SA-18 Igla manpad": {
"name": "SA-18 Igla manpad",
@ -632,6 +660,36 @@ export class GroundUnitDatabase extends UnitDatabase {
"filename": "",
"type": "MANPADS"
},
"RPC_5N62V": {
"name": "RPC_5N62V",
"coalition": "Red",
"era": "Mid Cold War",
"label": "SA-5 Square Pair",
"shortLabel": "RPC 5N62V",
"range": "Long",
"filename": "",
"type": "SAM Track radar"
},
"RLS_19J6": {
"name": "RLS_19J6",
"coalition": "Red",
"era": "Mid Cold War",
"label": "SA-5 Thin Shield",
"shortLabel": "RLS 19J6",
"range": "Long",
"filename": "",
"type": "SAM Search radar"
},
"S-200_Launcher": {
"name": "S-200_Launcher",
"coalition": "Red",
"era": "Mid Cold War",
"label": "SA-5 Launcher",
"shortLabel": "S-200 Launcher",
"range": "Long",
"filename": "",
"type": "SAM Launcher"
},
"Vulcan": {
"name": "Vulcan",
"coalition": "Blue",
@ -645,10 +703,10 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Hawk ln",
"coalition": "Blue",
"era": "Late Cold War",
"label": "Hawk ln",
"label": "Hawk Launcher",
"shortLabel": "Hawk ln",
"filename": "",
"type": "SAM"
"type": "SAM Launcher"
},
"M48 Chaparral": {
"name": "M48 Chaparral",
@ -672,11 +730,11 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Patriot ln",
"coalition": "Blue",
"era": "Late Cold War",
"label": "Patriot ln",
"label": "Patriot Launcher",
"shortLabel": "Patriot ln",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Launcher"
},
"M1097 Avenger": {
"name": "M1097 Avenger",
@ -691,21 +749,21 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Patriot EPP",
"coalition": "Blue",
"era": "Late Cold War",
"label": "Patriot EPP",
"label": "Patriot Electric Power Plant",
"shortLabel": "Patriot EPP",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Support vehicle"
},
"Patriot cp": {
"name": "Patriot cp",
"coalition": "Blue",
"era": "Late Cold War",
"label": "Patriot cp",
"label": "Patriot Command Post",
"shortLabel": "Patriot cp",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Support vehicle"
},
"Roland ADS": {
"name": "Roland ADS",
@ -720,11 +778,11 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "S-300PS 54K6 cp",
"coalition": "Red",
"era": "Late Cold War",
"label": "SA-10 S-300PS 54K6 cp",
"label": "SA-10 Command Post",
"shortLabel": "S-300PS 54K6 cp",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Support vehicle"
},
"Stinger manpad GRG": {
"name": "Stinger manpad GRG",
@ -734,7 +792,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "Stinger manpad GRG",
"range": "Short",
"filename": "",
"type": "SAM"
"type": "MANPADS"
},
"Stinger manpad dsr": {
"name": "Stinger manpad dsr",
@ -754,7 +812,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "Stinger comm dsr",
"range": "Short",
"filename": "",
"type": "SAM"
"type": "MANPADS"
},
"Stinger manpad": {
"name": "Stinger manpad",
@ -774,7 +832,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"shortLabel": "Stinger comm",
"range": "Short",
"filename": "",
"type": "SAM"
"type": "MANPADS"
},
"ZSU-23-4 Shilka": {
"name": "ZSU-23-4 Shilka",
@ -843,7 +901,7 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "1L13 EWR",
"coalition": "Red",
"era": "Late Cold War",
"label": "1L13 EWR",
"label": "Box Spring",
"shortLabel": "1L13 EWR",
"filename": "",
"type": "Radar"
@ -852,37 +910,37 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "Kub 1S91 str",
"coalition": "Red",
"era": "Mid Cold War",
"label": "SA-6 Kub 1S91 str",
"label": "SA-6 Straight flush",
"shortLabel": "Kub 1S91 str",
"range": "Medium",
"filename": "",
"type": "SAM"
"type": "SAM Search/Track radar"
},
"S-300PS 40B6M tr": {
"name": "S-300PS 40B6M tr",
"coalition": "Red",
"era": "Late Cold War",
"label": "SA-10 S-300PS 40B6M tr",
"label": "SA-10 Tin Shield",
"shortLabel": "S-300PS 40B6M tr",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Track radar"
},
"S-300PS 40B6MD sr": {
"name": "S-300PS 40B6MD sr",
"coalition": "Red",
"era": "Late Cold War",
"label": "SA-10 S-300PS 40B6MD sr",
"label": "SA-10 Clam Shell",
"shortLabel": "S-300PS 40B6MD sr",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Search radar"
},
"55G6 EWR": {
"name": "55G6 EWR",
"coalition": "Red",
"era": "Early Cold War",
"label": "55G6 EWR",
"label": "Tall Rack",
"shortLabel": "55G6 EWR",
"filename": "",
"type": "Radar"
@ -891,98 +949,98 @@ export class GroundUnitDatabase extends UnitDatabase {
"name": "S-300PS 64H6E sr",
"coalition": "Red",
"era": "Late Cold War",
"label": "SA-10 S-300PS 64H6E sr",
"label": "SA-10 Big Bird",
"shortLabel": "S-300PS 64H6E sr",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Search radar"
},
"SA-11 Buk SR 9S18M1": {
"name": "SA-11 Buk SR 9S18M1",
"coalition": "Red",
"era": "Mid Cold War",
"label": "SA-11 Buk SR 9S18M1",
"label": "SA-11 Snown Drift",
"shortLabel": "SA-11 Buk SR 9S18M1",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Search radar"
},
"Dog Ear radar": {
"name": "Dog Ear radar",
"coalition": "Red",
"era": "Mid Cold War",
"label": "Dog Ear radar",
"label": "Dog Ear",
"shortLabel": "Dog Ear radar",
"filename": "",
"type": "SAM"
"type": "SAM Search radar"
},
"Hawk tr": {
"name": "Hawk tr",
"coalition": "Blue",
"era": "Early Cold War",
"label": "Hawk tr",
"label": "Hawk Track radar",
"shortLabel": "Hawk tr",
"range": "Medium",
"filename": "",
"type": "SAM"
"type": "SAM Track radar"
},
"Hawk sr": {
"name": "Hawk sr",
"coalition": "Blue",
"era": "Early Cold War",
"label": "Hawk sr",
"label": "Hawk Search radar",
"shortLabel": "Hawk sr",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Search radar"
},
"Patriot str": {
"name": "Patriot str",
"coalition": "Blue",
"era": "Late Cold War",
"label": "Patriot str",
"label": "Patriot Search/Track radar",
"shortLabel": "Patriot str",
"range": "Medium",
"filename": "",
"type": "SAM"
"type": "SAM Search/Track radar"
},
"Hawk cwar": {
"name": "Hawk cwar",
"coalition": "Blue",
"era": "Early Cold War",
"label": "Hawk cwar",
"label": "Hawk Continous Wave Acquisition Radar",
"shortLabel": "Hawk cwar",
"range": "Long",
"filename": "",
"type": "SAM"
"type": "SAM Track radar"
},
"p-19 s-125 sr": {
"name": "p-19 s-125 sr",
"coalition": "Red",
"era": "Mid Cold War",
"label": "SA-3 p-19 s-125 sr",
"shortLabel": "p-19 s-125 sr",
"label": "SA-3 Flat Face B",
"shortLabel": "Flat Face B",
"filename": "",
"type": "SAM"
"type": "SAM Search radar"
},
"Roland Radar": {
"name": "Roland Radar",
"coalition": "Blue",
"era": "Mid Cold War",
"label": "Roland Radar",
"label": "Roland Search radar",
"shortLabel": "Roland Radar",
"filename": "",
"type": "SAM"
"type": "SAM Search radar"
},
"snr s-125 tr": {
"name": "snr s-125 tr",
"coalition": "Red",
"era": "Early Cold War",
"label": "SA-3 snr s-125 tr",
"label": "SA-3 Low Blow",
"shortLabel": "snr s-125 tr",
"range": "Medium",
"filename": "",
"type": "SAM"
"type": "SAM Track radar"
},
"house1arm": {
"name": "house1arm",

View File

@ -9,6 +9,8 @@ import { TargetMarker } from '../map/targetmarker';
import { BLUE_COMMANDER, BOMBING, CARPET_BOMBING, DLINK, DataIndexes, FIRE_AT_AREA, GAME_MASTER, HIDE_ALL, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, RED_COMMANDER, ROEs, RWR, VISUAL, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, UnitIconOptions } from '../@types/unit';
import { DataExtractor } from './dataextractor';
import { groundUnitDatabase } from './groundunitdatabase';
import { navyUnitDatabase } from './navyunitdatabase';
var pathIcon = new Icon({
iconUrl: '/resources/theme/images/markers/marker-icon.png',
@ -74,6 +76,7 @@ export class Unit extends CustomMarker {
#ammo: Ammo[] = [];
#contacts: Contact[] = [];
#activePath: LatLng[] = [];
#isLeader: boolean = false;
#selectable: boolean;
#selected: boolean = false;
@ -126,6 +129,7 @@ export class Unit extends CustomMarker {
getAmmo() {return this.#ammo};
getContacts() {return this.#contacts};
getActivePath() {return this.#activePath};
getIsLeader() {return this.#isLeader};
static getConstructor(type: string) {
if (type === "GroundUnit") return GroundUnit;
@ -162,6 +166,8 @@ export class Unit extends CustomMarker {
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
});
getMap().on("zoomend", () => {this.#onZoom();})
}
getCategory() {
@ -212,8 +218,9 @@ export class Unit extends CustomMarker {
case DataIndexes.radio: this.#radio = dataExtractor.extractRadio(); break;
case DataIndexes.generalSettings: this.#generalSettings = dataExtractor.extractGeneralSettings(); break;
case DataIndexes.ammo: this.#ammo = dataExtractor.extractAmmo(); break;
case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); break;
case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); document.dispatchEvent(new CustomEvent("contactsUpdated", {detail: this})); break;
case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break;
case DataIndexes.isLeader: this.#isLeader = dataExtractor.extractBool(); break;
}
}
@ -223,25 +230,20 @@ export class Unit extends CustomMarker {
if (updateMarker)
this.#updateMarker();
document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
if (this.getSelected() || getMap().getCenterUnit() === this)
document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
}
drawLines() {
// TODO dont delete the polylines of the detected units
this.#clearContacts();
if (this.getSelected()) {
this.#drawPath();
this.#drawContacts();
this.#drawTarget();
}
else {
this.#clearPath();
this.#clearTarget();
}
this.#drawPath();
this.#drawContacts();
this.#drawTarget();
}
getData() {
return {
category: this.getCategory(),
ID: this.ID,
alive: this.#alive,
human: this.#human,
controlled: this.#controlled,
@ -277,7 +279,8 @@ export class Unit extends CustomMarker {
generalSettings: this.#generalSettings,
ammo: this.#ammo,
contacts: this.#contacts,
activePath: this.#activePath
activePath: this.#activePath,
isLeader: this.#isLeader
}
}
@ -315,7 +318,7 @@ export class Unit extends CustomMarker {
/* Only alive units can be selected. Some units are not selectable (weapons) */
if ((this.#alive || !selected) && this.getSelectable() && this.getSelected() != selected && this.belongsToCommandedCoalition()) {
this.#selected = selected;
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected);
if (selected) {
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
this.#updateMarker();
@ -326,6 +329,15 @@ export class Unit extends CustomMarker {
this.#clearPath();
this.#clearTarget();
}
this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected);
if (getMap().getZoom() < 13) {
if (this.#isLeader)
this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected));
else
this.#updateMarker();
}
}
}
@ -376,6 +388,10 @@ export class Unit extends CustomMarker {
return true;
}
getType() {
return "";
}
/********************** Icon *************************/
createIcon(): void {
/* Set the icon */
@ -478,21 +494,16 @@ export class Unit extends CustomMarker {
/********************** Visibility *************************/
updateVisibility() {
var hidden = false;
const hiddenUnits = getUnitsManager().getHiddenTypes();
if (this.#human && hiddenUnits.includes("human"))
hidden = true;
if (this.#controlled == false && hiddenUnits.includes("dcs"))
hidden = true;
if (hiddenUnits.includes(this.getMarkerCategory()))
hidden = true;
if (hiddenUnits.includes(this.#coalition))
hidden = true;
if (getUnitsManager().getCommandMode() === HIDE_ALL)
hidden = true;
if (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0) {
hidden = true;
}
var hidden = ((this.#human && hiddenUnits.includes("human")) ||
(this.#controlled == false && hiddenUnits.includes("dcs")) ||
(hiddenUnits.includes(this.getMarkerCategory())) ||
(hiddenUnits.includes(this.#coalition)) ||
(getUnitsManager().getCommandMode() === HIDE_ALL) ||
(!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0) ||
(!this.#isLeader && this.getCategory() == "GroundUnit" && getMap().getZoom() < 13)) &&
!(this.getSelected());
this.setHidden(hidden || !this.#alive);
}
@ -718,7 +729,7 @@ export class Unit extends CustomMarker {
}
else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) {
if (this.getCategory() == "Aircraft") {
options["refuel"] = { text: "Air to air refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB." }; // TODO Add some way of knowing which aircraft can AAR
options["refuel"] = { text: "Air to air refuel", tooltip: "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB." }; // TODO Add some way of knowing which aircraft can AAR
}
}
@ -730,10 +741,13 @@ export class Unit extends CustomMarker {
}
if ((selectedUnits.length === 0 && this.getCategory() == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) {
if (selectedUnits.concat([this]).every((unit: Unit) => { return unit.canFulfillRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"]) }))
if (selectedUnits.concat([this]).every((unit: Unit) => { return ["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"].includes(this.getType()) }))
options["fire-at-area"] = { text: "Fire at area", tooltip: "Fire at a large area" };
}
if (selectedUnitTypes.length === 1 && ["NavyUnit", "GroundUnit"].includes(selectedUnitTypes[0]) && getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) !== undefined)
options["group"] = { text: "Create group", tooltip: "Create a group from the selected units." };
if (Object.keys(options).length > 0) {
getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
getMap().getUnitContextMenu().setOptions(options, (option: string) => {
@ -750,6 +764,8 @@ export class Unit extends CustomMarker {
getUnitsManager().selectedUnitsAttackUnit(this.ID);
else if (action === "refuel")
getUnitsManager().selectedUnitsRefuel();
else if (action === "group")
getUnitsManager().selectedUnitsCreateGroup();
else if (action === "follow")
this.#showFollowOptions(e);
else if (action === "bomb")
@ -758,6 +774,7 @@ export class Unit extends CustomMarker {
getMap().setState(CARPET_BOMBING);
else if (action === "fire-at-area")
getMap().setState(FIRE_AT_AREA);
}
#showFollowOptions(e: any) {
@ -949,10 +966,11 @@ export class Unit extends CustomMarker {
}
#drawContacts() {
this.#clearContacts();
for (let index in this.#contacts) {
var contactData = this.#contacts[index];
var contact = getUnitsManager().getUnitByID(contactData.ID)
if (contact != null) {
if (contact != null && contact.getAlive()) {
var startLatLng = new LatLng(this.#position.lat, this.#position.lng);
var endLatLng: LatLng;
if (contactData.detectionMethod === RWR) {
@ -1017,6 +1035,10 @@ export class Unit extends CustomMarker {
if (getMap().hasLayer(this.#targetPositionPolyline))
this.#targetPositionPolyline.removeFrom(getMap());
}
#onZoom() {
this.#updateMarker();
}
}
export class AirUnit extends Unit {
@ -1079,6 +1101,11 @@ export class GroundUnit extends Unit {
getCategory() {
return "GroundUnit";
}
getType() {
var blueprint = groundUnitDatabase.getByName(this.getName());
return blueprint?.type? blueprint.type: "";
}
}
export class NavyUnit extends Unit {
@ -1108,6 +1135,11 @@ export class NavyUnit extends Unit {
getCategory() {
return "NavyUnit";
}
getType() {
var blueprint = navyUnitDatabase.getByName(this.getName());
return blueprint?.type? blueprint.type: "";
}
}
export class Weapon extends Unit {
@ -1115,21 +1147,6 @@ export class Weapon extends Unit {
super(ID);
this.setSelectable(false);
}
getIconOptions() {
return {
showState: false,
showVvi: false,
showHotgroup: false,
showUnitIcon: true,
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: false,
showCallsign: false,
rotateToHeading: true
};
}
}
export class Missile extends Weapon {
@ -1142,7 +1159,25 @@ export class Missile extends Weapon {
}
getMarkerCategory() {
return "missile";
if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC))
return "missile";
else
return "aircraft";
}
getIconOptions() {
return {
showState: false,
showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
showHotgroup: false,
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
showCallsign: false,
rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)
};
}
}
@ -1156,6 +1191,24 @@ export class Bomb extends Weapon {
}
getMarkerCategory() {
return "bomb";
if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC))
return "bomb";
else
return "aircraft";
}
getIconOptions() {
return {
showState: false,
showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
showHotgroup: false,
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
showCallsign: false,
rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)
};
}
}

View File

@ -1,7 +1,7 @@
import { LatLng, LatLngBounds } from "leaflet";
import { getHotgroupPanel, getInfoPopup, getMap } from "..";
import { Unit } from "./unit";
import { cloneUnit, setLastUpdateTime, spawnAircrafts, spawnGroundUnits } from "../server/server";
import { cloneUnit, deleteUnit, 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 { groundUnitDatabase } from "./groundunitdatabase";
@ -12,11 +12,12 @@ import { citiesDatabase } from "./citiesdatabase";
export class UnitsManager {
#units: { [ID: number]: Unit };
#copiedUnits: Unit[];
#copiedUnits: any[];
#selectionEventDisabled: boolean = false;
#pasteDisabled: boolean = false;
#hiddenTypes: string[] = [];
#commandMode: string = HIDE_ALL;
#requestDetectionUpdate: boolean = false;
constructor() {
this.#units = {};
@ -31,6 +32,7 @@ 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});
}
getSelectableAircraft() {
@ -91,17 +93,21 @@ export class UnitsManager {
this.#units[ID]?.setData(dataExtractor);
}
for (let ID in this.#units) {
var unit = this.#units[ID];
if (!unit.belongsToCommandedCoalition())
unit.setDetectionMethods(this.getUnitDetectedMethods(unit));
if (this.#requestDetectionUpdate) {
for (let ID in this.#units) {
var unit = this.#units[ID];
if (!unit.belongsToCommandedCoalition())
unit.setDetectionMethods(this.getUnitDetectedMethods(unit));
}
this.#requestDetectionUpdate = false;
}
setLastUpdateTime(updateTime);
for (let ID in this.#units) {
this.#units[ID].drawLines();
if (this.#units[ID].getSelected())
this.#units[ID].drawLines();
};
return updateTime;
}
setHiddenType(key: string, value: boolean) {
@ -188,36 +194,59 @@ export class UnitsManager {
}
getSelectedUnitsTypes() {
if (this.getSelectedUnits().length == 0)
const selectedUnits = this.getSelectedUnits();
if (selectedUnits.length == 0)
return [];
return this.getSelectedUnits().map((unit: Unit) => {
return unit.constructor.name
return selectedUnits.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) {
if (this.getSelectedUnits().length == 0)
const selectedUnits = this.getSelectedUnits();
if (selectedUnits.length == 0)
return undefined;
return this.getSelectedUnits().map((unit: Unit) => {
return selectedUnits.map((unit: Unit) => {
return variableGetter(unit);
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
return a === b ? a : undefined
});
};
getSelectedUnitsCoalition() {
if (this.getSelectedUnits().length == 0)
const selectedUnits = this.getSelectedUnits();
if (selectedUnits.length == 0)
return undefined;
return this.getSelectedUnits().map((unit: Unit) => {
return selectedUnits.map((unit: Unit) => {
return unit.getCoalition()
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
};
getByType(type: string) {
Object.values(this.getUnits()).filter((unit: Unit) => {
return unit.getType() === type;
})
}
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())
{
this.#units[idx].getContacts().forEach((contact: Contact) => {
if (contact.ID == unit.ID && !detectionMethods.includes(contact.detectionMethod))
detectionMethods.push(contact.detectionMethod);
});
}
}
return detectionMethods;
}
/*********************** Actions on selected units ************************/
selectedUnitsAddDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
@ -227,7 +256,7 @@ export class UnitsManager {
if (mantainRelativePosition)
unitDestinations = this.selectedUnitsComputeGroupDestination(latlng, rotation);
else
selectedUnits.forEach((unit: Unit) => { unitDestinations[unit.ID] = latlng });
selectedUnits.forEach((unit: Unit) => { unitDestinations[unit.ID] = latlng; });
for (let idx in selectedUnits) {
const unit = selectedUnits[idx];
@ -515,36 +544,66 @@ export class UnitsManager {
this.#showActionMessage(selectedUnits, `unit bombing point`);
}
getUnitDetectedMethods(unit: Unit) {
var detectionMethods: number[] = [];
for (let idx in this.#units) {
if (this.#units[idx].getCoalition() !== "neutral" && this.#units[idx].getCoalition() != unit.getCoalition())
{
this.#units[idx].getContacts().forEach((contact: Contact) => {
if (this.#units[idx].getAlive() && contact.ID == unit.ID && !detectionMethods.includes(contact.detectionMethod))
detectionMethods.push(contact.detectionMethod);
});
}
// TODO add undo group
selectedUnitsCreateGroup() {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: false });
var units = [];
var coalition = "neutral";
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()});
}
return detectionMethods;
const category = this.getSelectedUnitsTypes()[0];
this.spawnUnit(category, units, coalition, true);
}
/***********************************************/
copyUnits() {
this.#copiedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
this.#showActionMessage(this.#copiedUnits, `copied`);
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`);
}
pasteUnits() {
if (!this.#pasteDisabled) {
/* Compute the position of the center of the copied units */
var nUnits = this.#copiedUnits.length;
var avgLat = 0;
var avgLng = 0;
for (let idx in this.#copiedUnits) {
var unit = this.#copiedUnits[idx];
//getMap().addTemporaryMarker(getMap().getMouseCoordinates());
cloneUnit(unit.ID, getMap().getMouseCoordinates());
this.#showActionMessage(this.#copiedUnits, `pasted`);
avgLat += unit.position.lat / nUnits;
avgLng += unit.position.lng / nUnits;
}
this.#pasteDisabled = true;
window.setTimeout(() => this.#pasteDisabled = false, 250);
/* Organize the copied units in groups */
var groups: {[key: string]: any} = {};
this.#copiedUnits.forEach((unit: any) => {
if (!(unit.groupName in groups))
groups[unit.groupName] = [];
groups[unit.groupName].push(unit);
});
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};
});
this.spawnUnit(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);
});
}
}
getInfoPopup().setText(`${this.#copiedUnits.length - 1} units pasted`);
}
}
@ -581,7 +640,6 @@ export class UnitsManager {
var unit = this.#units[ID];
if (!["Aircraft", "Helicopter"].includes(unit.getCategory())) {
var data: any = unit.getData();
data.category = unit.getCategory();
if (unit.getGroupName() in unitsToExport)
unitsToExport[unit.getGroupName()].push(data);
else
@ -609,7 +667,7 @@ export class UnitsManager {
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";})) {
var units = groups[groupName].map((unit: any) => {return {unitType: unit.name, location: unit.position}});
var units = groups[groupName].filter((unit: any) => {return unit.alive}).map((unit: any) => {return {unitType: unit.name, location: unit.position}});
spawnGroundUnits(units, groups[groupName][0].coalition, true);
}
}
@ -629,8 +687,11 @@ export class UnitsManager {
/***********************************************/
#onKeyUp(event: KeyboardEvent) {
if (!keyEventWasInInput(event) && event.key === "Delete" ) {
this.selectedUnitsDelete();
if (!keyEventWasInInput(event)) {
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));
}
}
@ -661,7 +722,7 @@ export class UnitsManager {
document.dispatchEvent(new CustomEvent("unitsDeselection", { detail: this.getSelectedUnits() }));
}
#showActionMessage(units: Unit[], message: string) {
#showActionMessage(units: any[], message: string) {
if (units.length == 1)
getInfoPopup().setText(`${units[0].getUnitName()} ${message}`);
else if (units.length > 1)

View File

@ -15,10 +15,10 @@
</div>
<div id="more-options-button-bar" class="upper-bar ol-panel hide">
<div id="coalition-switch" class="ol-switch ol-coalition-switch"></div>
<!--
<button data-coalition="blue" id="navyunit-spawn-button" title="Spawn navy unit" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "navyunit" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/navyunit.svg" inject-svg></button>
-->
<button data-coalition="blue" id="smoke-spawn-button" title="Spawn smoke" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "smoke" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/smoke.svg" inject-svg></button>
<button data-coalition="blue" id="explosion-spawn-button" title="Explosion" data-on-click="mapContextMenuShow"

View File

@ -5,13 +5,16 @@ local debug = true
Olympus.unitCounter = 1
Olympus.payloadRegistry = {}
Olympus.groupIndex = 0
Olympus.groupStep = 40
Olympus.groupStep = 5
Olympus.OlympusDLL = nil
Olympus.DLLsloaded = false
Olympus.OlympusModPath = os.getenv('DCSOLYMPUS_PATH')..'\\bin\\'
Olympus.log = mist.Logger:new("Olympus", 'info')
Olympus.missionData = {}
Olympus.unitsData = {}
function Olympus.debug(message, displayFor)
if debug == true then
trigger.action.outText(message, displayFor)
@ -706,22 +709,9 @@ function Olympus.hasKey(tab, key)
return false
end
function Olympus.setMissionData(arg, time)
local missionData = {}
-- Bullseye data
local bullseyes = {}
for i = 0, 2 do
local bullseyeVec3 = coalition.getMainRefPoint(i)
local bullseyeLatitude, bullseyeLongitude, bullseyeAltitude = coord.LOtoLL(bullseyeVec3)
bullseyes[i] = {}
bullseyes[i]["latitude"] = bullseyeLatitude
bullseyes[i]["longitude"] = bullseyeLongitude
bullseyes[i]["coalition"] = Olympus.getCoalitionByCoalitionID(i)
end
-- Units tactical data
local unitsData = {}
function Olympus.setUnitsData(arg, time)
-- Units data
local units = {}
local startIndex = Olympus.groupIndex
local endIndex = startIndex + Olympus.groupStep
@ -736,31 +726,73 @@ function Olympus.setMissionData(arg, time)
-- Get the targets detected by the group controller
local controller = group:getController()
local controllerTargets = controller:getDetectedTargets()
local contacts = {}
for i, target in ipairs(controllerTargets) do
for det, enum in pairs(Controller.Detection) do
if target.object ~= nil then
target["detectionMethod"] = det
contacts[#contacts + 1] = target
end
end
end
-- Update the units position
for index, unit in pairs(group:getUnits()) do
local unitController = unit:getController()
local table = {}
table["contacts"] = {}
for i, target in ipairs(controllerTargets) do
for det, enum in pairs(Controller.Detection) do
if target.object ~= nil then
local detected = unitController:isTargetDetected(target.object, enum)
if detected then
target["detectionMethod"] = det
table["contacts"][#table["contacts"] + 1] = target
end
end
table["category"] = "None"
-- Get the object category in Olympus name
local objectCategory = unit:getCategory()
if objectCategory == Object.Category.UNIT then
if unit:getDesc().category == Unit.Category.AIRPLANE then
table["category"] = "Aircraft"
elseif unit:getDesc().category == Unit.Category.HELICOPTER then
table["category"] = "Helicopter"
elseif unit:getDesc().category == Unit.Category.GROUND_UNIT then
table["category"] = "GroundUnit"
elseif unit:getDesc().category == Unit.Category.SHIP then
table["category"] = "NavyUnit"
end
elseif objectCategory == Object.Category.WEAPON then
if unit:getDesc().category == Weapon.Category.MISSILE then
table["category"] = "Missile"
elseif unit:getDesc().category == Weapon.Category.ROCKET then
table["category"] = "Missile"
elseif unit:getDesc().category == Unit.Category.BOMB then
table["category"] = "Bomb"
end
end
table["hasTask"] = controller:hasTask()
table["ammo"] = unit:getAmmo()
table["fuel"] = unit:getFuel()
table["life"] = unit:getLife() / unit:getLife0()
unitsData[unit:getObjectID()] = table
-- If the category is handled by Olympus, get the data
if table["category"] ~= "None" then
-- Compute unit position and heading
local lat, lng, alt = coord.LOtoLL(unit:getPoint())
local position = unit:getPosition()
local heading = math.atan2( position.x.z, position.x.x )
-- Fill the data table
table["name"] = unit:getTypeName()
table["unitName"] = unit:getName()
table["groupName"] = group:getName()
table["isHuman"] = (unit:getPlayerName() ~= nil)
table["coalitionID"] = unit:getCoalition()
table["hasTask"] = controller:hasTask()
table["ammo"] = unit:getAmmo() --TODO remove a lot of stuff we don't really need
table["fuel"] = unit:getFuel()
table["life"] = unit:getLife() / unit:getLife0()
table["isAlive"] = (unit:getLife() > 1) and unit:isExist()
table["contacts"] = contacts
table["position"] = {}
table["position"]["lat"] = lat
table["position"]["lng"] = lng
table["position"]["alt"] = alt
table["speed"] = mist.vec.mag(unit:getVelocity())
table["heading"] = heading
table["country"] = unit:getCountry()
units[unit:getObjectID()] = table
end
end
end
end
@ -776,6 +808,25 @@ function Olympus.setMissionData(arg, time)
end
end
-- Assemble unitsData table
Olympus.unitsData["units"] = units
Olympus.OlympusDLL.setUnitsData()
return time + 0.05
end
function Olympus.setMissionData(arg, time)
-- Bullseye data
local bullseyes = {}
for i = 0, 2 do
local bullseyeVec3 = coalition.getMainRefPoint(i)
local bullseyeLatitude, bullseyeLongitude, bullseyeAltitude = coord.LOtoLL(bullseyeVec3)
bullseyes[i] = {}
bullseyes[i]["latitude"] = bullseyeLatitude
bullseyes[i]["longitude"] = bullseyeLongitude
bullseyes[i]["coalition"] = Olympus.getCoalitionByCoalitionID(i)
end
-- Airbases data
local base = world.getAirbases()
local airbases = {}
@ -792,6 +843,7 @@ function Olympus.setMissionData(arg, time)
airbases[i] = info
end
-- Mission
local mission = {}
mission.theatre = env.mission.theatre
mission.elapsedTime = DCS.getRealTime()
@ -799,13 +851,11 @@ function Olympus.setMissionData(arg, time)
mission.startTime = env.mission.start_time
mission.date = env.mission.date
-- Assemble missionData table
missionData["bullseyes"] = bullseyes
missionData["unitsData"] = unitsData
missionData["airbases"] = airbases
missionData["mission"] = mission
-- Assemble table
Olympus.missionData["bullseyes"] = bullseyes
Olympus.missionData["airbases"] = airbases
Olympus.missionData["mission"] = mission
Olympus.missionData = missionData
Olympus.OlympusDLL.setMissionData()
return time + 1
end
@ -819,6 +869,7 @@ else
Olympus.notify('Failed to load '..OlympusName, 20)
end
timer.scheduleFunction(Olympus.setUnitsData, {}, timer.getTime() + 0.05)
timer.scheduleFunction(Olympus.setMissionData, {}, timer.getTime() + 1)
Olympus.notify("OlympusCommand script " .. version .. " loaded successfully", 2, true)

View File

@ -1351,4 +1351,141 @@ templates =
}, -- end of [18]
}, -- end of ["units"]
}, -- end of ["SA-10 SAM Battery"]
["SA-5 SAM Battery"] =
{
["type"] = "vehicle",
["name"] = "SA-5 SAM Battery",
["country"] = 0,
["units"] =
{
[1] =
{
["dx"] = 0,
["dy"] = 0,
["name"] = "RPC_5N62V",
["skill"] = "High",
["heading"] = 4.7123889803847,
}, -- end of [1]
[2] =
{
["dx"] = 0.69314285699511,
["dy"] = 127.97571428004,
["name"] = "RLS_19J6",
["skill"] = "High",
["heading"] = 0,
},
[3] =
{
["dx"] = 83.349983285123,
["dy"] = -1.3806866992963,
["name"] = "S-200_Launcher",
["skill"] = "High",
["heading"] = 3.1415926535898,
}, -- end of [5]
[4] =
{
["dx"] = 82.498640577192,
["dy"] = 16.104647497996,
["name"] = "S-200_Launcher",
["skill"] = "High",
["heading"] = 3.3161255787892,
}, -- end of [6]
[5] =
{
["dx"] = 82.547616217693,
["dy"] = -18.227276489837,
["name"] = "S-200_Launcher",
["skill"] = "High",
["heading"] = 2.9670597283904,
}, -- end of [7]
[6] =
{
["dx"] = -82.640406328603,
["dy"] = -0.41562629467808,
["name"] = "S-200_Launcher",
["skill"] = "High",
["heading"] = 0,
}, -- end of [8]
[7] =
{
["dx"] = -81.939684967569,
["dy"] = 17.115632734494,
["name"] = "S-200_Launcher",
["skill"] = "High",
["heading"] = 6.1086523819802,
}, -- end of [9]
[8] =
{
["dx"] = -81.939684967569,
["dy"] = -17.99454369233,
["name"] = "S-200_Launcher",
["skill"] = "High",
["heading"] = 0.17453292519943,
}, -- end of [10]
[9] =
{
["dx"] = -9.0858776818495,
["dy"] = 187.67713509151,
["name"] = "generator_5i57",
["skill"] = "High",
["heading"] = 1.5707963267949,
}, -- end of [11]
[10] =
{
["dx"] = 0.83760223048739,
["dy"] = 187.51811292395,
["name"] = "generator_5i57",
["skill"] = "High",
["heading"] = 1.5707963267949,
}, -- end of [12]
[11] =
{
["dx"] = -59.823818980018,
["dy"] = 168.63468487991,
["name"] = "ATZ-5",
["skill"] = "High",
["heading"] = 0,
}, -- end of [13]
[12] =
{
["dx"] = -59.823818980018,
["dy"] = 179.2654343833,
["name"] = "ATZ-5",
["skill"] = "High",
["heading"] = 0,
}, -- end of [14]
[13] =
{
["dx"] = 20.947679329896,
["dy"] = -62.811427216162,
["name"] = "GAZ-66",
["skill"] = "High",
["heading"] = 1.5707963267949,
}, -- end of [15]
[14] =
{
["dx"] = 66.751355714747,
["dy"] = 151.35592090525,
["name"] = "ATZ-60_Maz",
["skill"] = "High",
["heading"] = 3.9269908169872,
}, -- end of [16]
[15] =
{
["dx"] = 59.63926918729,
["dy"] = 158.46800743265,
["name"] = "ATZ-60_Maz",
["skill"] = "High",
["heading"] = 3.9269908169872,
}, -- end of [17]
[16] =
{
["dx"] = -16.327227612433,
["dy"] = -62.472874663305,
["name"] = "KAMAZ Truck",
["skill"] = "High",
["heading"] = 1.5707963267949,
}, -- end of [18]
}, -- end of ["units"]
} -- end of ["SA-5 SAM Battery"]
} -- end of templates

View File

@ -12,6 +12,7 @@ class AirUnit : public Unit
public:
AirUnit(json::value json, unsigned int ID);
virtual void setDefaults(bool force = false);
virtual void setState(unsigned char newState);
virtual void changeSpeed(string change) = 0;

View File

@ -119,7 +119,7 @@ public:
priority = CommandPriority::HIGH;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return 5; }
virtual unsigned int getLoad() { return 2; }
private:
const string groupName;
@ -143,7 +143,7 @@ public:
priority = CommandPriority::LOW;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return 5; }
virtual unsigned int getLoad() { return 2; }
private:
const string color;
@ -163,7 +163,7 @@ public:
priority = immediate? CommandPriority::IMMEDIATE: CommandPriority::LOW;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return immediate? 1: 100; }
virtual unsigned int getLoad() { return immediate? 1: 30; }
private:
const string coalition;
@ -185,7 +185,7 @@ public:
priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return immediate ? 1 : 100; }
virtual unsigned int getLoad() { return immediate ? 1 : 30; }
private:
const string coalition;
@ -209,7 +209,7 @@ public:
priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return immediate ? 1 : 100; }
virtual unsigned int getLoad() { return immediate ? 1 : 30; }
private:
const string coalition;
@ -236,7 +236,7 @@ public:
priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return immediate ? 1 : 100; }
virtual unsigned int getLoad() { return immediate ? 1 : 30; }
private:
const string coalition;
@ -258,7 +258,7 @@ public:
priority = CommandPriority::LOW;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return 100; }
virtual unsigned int getLoad() { return 30; }
private:
const unsigned int ID;
@ -278,7 +278,7 @@ public:
immediate = immediate;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return immediate? 1: 20; }
virtual unsigned int getLoad() { return immediate? 1: 5; }
private:
const unsigned int ID;
@ -297,7 +297,7 @@ public:
priority = CommandPriority::MEDIUM;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return 2; }
virtual unsigned int getLoad() { return 1; }
private:
const string groupName;
@ -314,7 +314,7 @@ public:
priority = CommandPriority::HIGH;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return 2; }
virtual unsigned int getLoad() { return 1; }
private:
const string groupName;
@ -331,7 +331,7 @@ public:
priority = CommandPriority::HIGH;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return 2; }
virtual unsigned int getLoad() { return 1; }
private:
const string groupName;
@ -362,7 +362,7 @@ public:
priority = CommandPriority::HIGH;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return 2; }
virtual unsigned int getLoad() { return 1; }
private:
const string groupName;
@ -383,7 +383,7 @@ public:
priority = CommandPriority::HIGH;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return 2; }
virtual unsigned int getLoad() { return 1; }
private:
const string groupName;
@ -401,7 +401,7 @@ public:
priority = CommandPriority::MEDIUM;
};
virtual string getString(lua_State* L);
virtual unsigned int getLoad() { return 10; }
virtual unsigned int getLoad() { return 4; }
private:
const Coords location;

View File

@ -9,10 +9,11 @@ public:
GroundUnit(json::value json, unsigned int ID);
virtual void setState(unsigned char newState);
virtual void setDefaults(bool force = false);
virtual void changeSpeed(string change);
virtual void setOnOff(bool newOnOff);
virtual void setFollowRoads(bool newFollowRoads);
virtual void setOnOff(bool newOnOff, bool force = false);
virtual void setFollowRoads(bool newFollowRoads, bool force = false);
protected:
virtual void AIloop();

View File

@ -10,12 +10,16 @@ public:
~Scheduler();
void appendCommand(Command* command);
int getCurrentLoad();
void execute(lua_State* L);
void handleRequest(string key, json::value value);
int getLoad();
void setFrameRate(double newFrameRate) { frameRate = newFrameRate; }
int getFrameRate() { return frameRate; };
private:
list<Command*> commands;
unsigned int load;
double frameRate;
};

View File

@ -53,6 +53,7 @@ namespace DataIndex {
ammo,
contacts,
activePath,
isLeader,
lastIndex,
endOfData = 255
};
@ -86,14 +87,12 @@ public:
/********** Methods **********/
void initialize(json::value json);
void setDefaults(bool force = false);
virtual void setDefaults(bool force = false);
void runAILoop();
void updateExportData(json::value json, double dt = 0);
void updateMissionData(json::value json);
void update(json::value json, double dt);
unsigned int getDataPacket(char*& data);
unsigned int getID() { return ID; }
void getData(stringstream& ss, unsigned long long time);
Coords getActiveDestination() { return activeDestination; }
@ -144,8 +143,8 @@ public:
virtual void setHeading(double newValue) { updateValue(heading, newValue, DataIndex::heading); }
virtual void setIsTanker(bool newValue);
virtual void setIsAWACS(bool newValue);
virtual void setOnOff(bool newValue) { updateValue(onOff, newValue, DataIndex::onOff); };
virtual void setFollowRoads(bool newValue) { updateValue(followRoads, newValue, DataIndex::followRoads); };
virtual void setOnOff(bool newValue, bool force = false) { updateValue(onOff, newValue, DataIndex::onOff); };
virtual void setFollowRoads(bool newValue, bool force = false) { updateValue(followRoads, newValue, DataIndex::followRoads); };
virtual void setFuel(unsigned short newValue) { updateValue(fuel, newValue, DataIndex::fuel); }
virtual void setDesiredSpeed(double newValue);
virtual void setDesiredSpeedType(string newValue);
@ -164,6 +163,7 @@ public:
virtual void setAmmo(vector<DataTypes::Ammo> newValue);
virtual void setContacts(vector<DataTypes::Contact> newValue);
virtual void setActivePath(list<Coords> newValue);
virtual void setIsLeader(bool newValue) { updateValue(isLeader, newValue, DataIndex::isLeader); }
/********** Getters **********/
virtual string getCategory() { return category; };
@ -191,7 +191,7 @@ public:
virtual double getDesiredAltitude() { return desiredAltitude; };
virtual bool getDesiredAltitudeType() { return desiredAltitudeType; };
virtual unsigned int getLeaderID() { return leaderID; }
virtual Offset getFormationoffset() { return formationOffset; }
virtual Offset getFormationOffset() { return formationOffset; }
virtual unsigned int getTargetID() { return targetID; }
virtual Coords getTargetPosition() { return targetPosition; }
virtual unsigned char getROE() { return ROE; }
@ -203,6 +203,7 @@ public:
virtual vector<DataTypes::Ammo> getAmmo() { return ammo; }
virtual vector<DataTypes::Contact> getTargets() { return contacts; }
virtual list<Coords> getActivePath() { return activePath; }
virtual bool getIsLeader() { return isLeader; }
protected:
unsigned int ID;
@ -244,13 +245,14 @@ protected:
vector<DataTypes::Ammo> ammo;
vector<DataTypes::Contact> contacts;
list<Coords> activePath;
bool isLeader = false;
Coords activeDestination = Coords(NULL);
/********** Other **********/
unsigned int taskCheckCounter = 0;
Coords activeDestination = Coords(NULL);
double initialFuel = 0;
Coords oldPosition = Coords(0);
map<unsigned char, unsigned long long> updateTimeMap;
unsigned long long lastLoopTime = 0;
/********** Private methods **********/
virtual void AIloop() = 0;

View File

@ -13,14 +13,13 @@ public:
map<unsigned int, Unit*>& getUnits() { return units; };
Unit* getUnit(unsigned int ID);
bool isUnitInGroup(Unit* unit);
bool isUnitGroupLeader(Unit* unit);
bool isUnitGroupLeader(Unit* unit, Unit*& leader);
Unit* getGroupLeader(unsigned int ID);
Unit* getGroupLeader(Unit* unit);
vector<Unit*> getGroupMembers(string groupName);
void updateExportData(lua_State* L, double dt = 0);
void updateMissionData(json::value missionData);
void update(json::value& missionData, double dt);
void runAILoop();
string getUnitData(stringstream &ss, unsigned long long time);
void getUnitData(stringstream &ss, unsigned long long time);
void deleteUnit(unsigned int ID, bool explosion, bool immediate);
void acquireControl(unsigned int ID);

View File

@ -18,6 +18,26 @@ AirUnit::AirUnit(json::value json, unsigned int ID) : Unit(json, ID)
};
void AirUnit::setDefaults(bool force)
{
if (!getAlive() || !getControlled() || getHuman() || !getIsLeader()) return;
/* Set the default IDLE state */
setState(State::IDLE);
/* Set desired altitude to be equal to current altitude so the unit does not climb/descend after spawn */
setDesiredAltitude(position.alt);
/* Set the default options */
setROE(ROE::OPEN_FIRE_WEAPON_FREE, force);
setReactionToThreat(ReactionToThreat::EVADE_FIRE, force);
setEmissionsCountermeasures(EmissionCountermeasure::DEFEND, force);
strcpy_s(TACAN.callsign, 4, "TKR");
setTACAN(TACAN, force);
setRadio(radio, force);
setGeneralSettings(generalSettings, force);
}
void AirUnit::setState(unsigned char newState)
{
/************ Perform any action required when LEAVING a state ************/

View File

@ -18,9 +18,7 @@ Server* server = nullptr;
Scheduler* scheduler = nullptr;
/* Data jsons */
json::value airbases;
json::value bullseyes;
json::value mission;
json::value missionData = json::value::object();
mutex mutexLock;
string sessionHash;
@ -28,7 +26,6 @@ string sessionHash;
bool initialized = false;
unsigned int frameCounter = 0;
double frameRate = 30;
/* Called when DCS simulation stops. All singleton instances are deleted. */
extern "C" DllExport int coreDeinit(lua_State* L)
@ -72,38 +69,46 @@ 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<double> 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<mutex> guard(mutexLock);
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
if (updateDuration.count() > 0)
frameRate = frameCounter / updateDuration.count();
frameCounter = 0;
if (unitsManager != nullptr) {
unitsManager->updateExportData(L, updateDuration.count());
unitsManager->runAILoop();
}
lastUpdate = std::chrono::system_clock::now();
}
const std::chrono::duration<double> 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)
if (executionDuration.count() > EXECUTION_TIME_INTERVAL) {
if (scheduler != nullptr) {
scheduler->execute(L);
lastExecution = std::chrono::system_clock::now();
if (executionDuration.count() > 0) {
scheduler->setFrameRate(frameCounter / executionDuration.count());
frameCounter = 0;
}
lastExecution = std::chrono::system_clock::now();
}
}
return(0);
}
extern "C" DllExport int coreUnitsData(lua_State * L)
{
if (!initialized)
return (0);
/* Lock for thread safety */
json::value unitsData = json::value::object();
lock_guard<mutex> guard(mutexLock);
lua_getglobal(L, "Olympus");
lua_getfield(L, -1, "unitsData");
luaTableToJSON(L, -1, unitsData);
const std::chrono::duration<double> updateDuration = std::chrono::system_clock::now() - lastUpdate;
if (unitsData.has_object_field(L"units")) {
unitsManager->update(unitsData[L"units"], updateDuration.count());
}
lastUpdate = std::chrono::system_clock::now();
return(0);
}
extern "C" DllExport int coreMissionData(lua_State * L)
{
if (!initialized)
@ -113,17 +118,7 @@ extern "C" DllExport int coreMissionData(lua_State * L)
lock_guard<mutex> guard(mutexLock);
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"];
luaTableToJSON(L, -1, missionData);
return(0);
}

View File

@ -21,6 +21,19 @@ GroundUnit::GroundUnit(json::value json, unsigned int ID) : Unit(json, ID)
setDesiredSpeed(10);
};
void GroundUnit::setDefaults(bool force)
{
if (!getAlive() || !getControlled() || getHuman() || !getIsLeader()) return;
/* Set the default IDLE state */
setState(State::IDLE);
/* Set the default options */
setROE(ROE::OPEN_FIRE_WEAPON_FREE, force);
setOnOff(onOff, force);
setFollowRoads(followRoads, force);
}
void GroundUnit::setState(unsigned char newState)
{
/************ Perform any action required when LEAVING a state ************/
@ -61,7 +74,8 @@ void GroundUnit::setState(unsigned char newState)
break;
}
resetTask();
if (newState != state)
resetTask();
log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState));
state = newState;
@ -132,15 +146,19 @@ void GroundUnit::changeSpeed(string change)
setDesiredSpeed(0);
}
void GroundUnit::setOnOff(bool newOnOff)
void GroundUnit::setOnOff(bool newOnOff, bool force)
{
Unit::setOnOff(newOnOff);
Command* command = dynamic_cast<Command*>(new SetOnOff(groupName, onOff));
scheduler->appendCommand(command);
if (newOnOff != onOff || force) {
Unit::setOnOff(newOnOff, force);
Command* command = dynamic_cast<Command*>(new SetOnOff(groupName, onOff));
scheduler->appendCommand(command);
}
}
void GroundUnit::setFollowRoads(bool newFollowRoads)
void GroundUnit::setFollowRoads(bool newFollowRoads, bool force)
{
Unit::setFollowRoads(newFollowRoads);
resetActiveDestination(); /* Reset active destination to apply option*/
if (newFollowRoads != followRoads || force) {
Unit::setFollowRoads(newFollowRoads, force);
resetActiveDestination(); /* Reset active destination to apply option*/
}
}

View File

@ -23,7 +23,7 @@ void Scheduler::appendCommand(Command* command)
commands.push_back(command);
}
int Scheduler::getCurrentLoad()
int Scheduler::getLoad()
{
int currentLoad = 0;
for (auto command : commands) {
@ -51,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(getCurrentLoad()));
log("Command '" + commandString + "' executed correctly, current load " + to_string(getLoad()));
load = command->getLoad();
commands.remove(command);
return;
@ -468,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(getCurrentLoad()));
log("New command appended correctly to stack. Current server load: " + to_string(getLoad()));
}
}

View File

@ -14,9 +14,7 @@ using namespace base64;
extern UnitsManager* unitsManager;
extern Scheduler* scheduler;
extern json::value airbases;
extern json::value bullseyes;
extern json::value mission;
extern json::value missionData;
extern mutex mutexLock;
extern string sessionHash;
@ -70,7 +68,6 @@ void Server::handle_get(http_request request)
lock_guard<mutex> guard(mutexLock);
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
http_response response(status_codes::OK);
string password = extractPassword(request);
@ -81,24 +78,25 @@ void Server::handle_get(http_request request)
auto answer = json::value::object();
auto path = uri::split_path(uri::decode(request.relative_uri().path()));
/* If present, extract the request reference time. This is used for updates, and it specifies the last time that request has been performed */
map<utility::string_t, utility::string_t> query = request.relative_uri().split_query(request.relative_uri().query());
unsigned long long time = 0;
if (query.find(L"time") != query.end())
{
try {
time = stoull((*(query.find(L"time"))).second);
}
catch (const std::exception& e) {
time = 0;
}
}
if (path.size() > 0)
{
string URI = to_string(path[0]);
if (URI.compare(UNITS_URI) == 0)
{
map<utility::string_t, utility::string_t> query = request.relative_uri().split_query(request.relative_uri().query());
long long time = 0;
if (query.find(L"time") != query.end())
{
try {
time = stoll((*(query.find(L"time"))).second);
}
catch (const std::exception& e) {
time = 0;
}
}
unsigned long long updateTime = ms.count();
stringstream ss;
ss.write((char*)&updateTime, sizeof(updateTime));
unitsManager->getUnitData(ss, time);
@ -108,25 +106,29 @@ void Server::handle_get(http_request request)
if (URI.compare(LOGS_URI) == 0)
{
auto logs = json::value::object();
getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries
getLogsJSON(logs, time);
answer[L"logs"] = logs;
}
else if (URI.compare(AIRBASES_URI) == 0)
answer[L"airbases"] = airbases;
else if (URI.compare(BULLSEYE_URI) == 0)
answer[L"bullseyes"] = bullseyes;
else if (URI.compare(MISSION_URI) == 0) {
else if (URI.compare(AIRBASES_URI) == 0 && missionData.has_object_field(L"airbases")) {
answer[L"airbases"] = missionData[L"airbases"];
}
else if (URI.compare(BULLSEYE_URI) == 0 && missionData.has_object_field(L"bullseyes")) {
answer[L"bullseyes"] = missionData[L"bullseyes"];
}
else if (URI.compare(MISSION_URI) == 0 && missionData.has_object_field(L"mission")) {
answer[L"mission"] = missionData[L"mission"];
if (password.compare(gameMasterPassword) == 0)
mission[L"visibilityMode"] = json::value(L"Game master");
answer[L"mission"][L"visibilityMode"] = json::value(L"Game master");
else if (password.compare(blueCommanderPassword) == 0)
mission[L"visibilityMode"] = json::value(L"Blue commander");
answer[L"mission"][L"visibilityMode"] = json::value(L"Blue commander");
else if (password.compare(redCommanderPassword) == 0)
mission[L"visibilityMode"] = json::value(L"Red commander");
answer[L"mission"] = mission;
answer[L"mission"][L"visibilityMode"] = json::value(L"Red commander");
}
answer[L"time"] = json::value::string(to_wstring(ms.count()));
answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash));
answer[L"load"] = scheduler->getLoad();
answer[L"frameRate"] = scheduler->getFrameRate();
response.set_body(answer);
}
}

View File

@ -28,104 +28,58 @@ Unit::~Unit()
void Unit::initialize(json::value json)
{
if (json.has_string_field(L"Name"))
setName(to_string(json[L"Name"]));
if (json.has_string_field(L"UnitName"))
setUnitName(to_string(json[L"UnitName"]));
if (json.has_string_field(L"GroupName"))
setGroupName(to_string(json[L"GroupName"]));
if (json.has_number_field(L"Country"))
setCountry(json[L"Country"].as_number().to_int32());
if (json.has_number_field(L"CoalitionID"))
setCoalition(json[L"CoalitionID"].as_number().to_int32());
if (json.has_string_field(L"name"))
setName(to_string(json[L"name"]));
if (json.has_object_field(L"Flags"))
setHuman(json[L"Flags"][L"Human"].as_bool());
if (json.has_string_field(L"unitName"))
setUnitName(to_string(json[L"unitName"]));
if (json.has_string_field(L"groupName"))
setGroupName(to_string(json[L"groupName"]));
if (json.has_number_field(L"coalitionID"))
setCoalition(json[L"coalitionID"].as_number().to_int32());
//if (json.has_number_field(L"Country"))
// setCountry(json[L"Country"].as_number().to_int32());
/* All units which contain the name "Olympus" are automatically under AI control */
if (getUnitName().find("Olympus") != string::npos)
setControlled(true);
updateExportData(json);
update(json, 0);
setDefaults();
}
void Unit::setDefaults(bool force)
void Unit::update(json::value json, double dt)
{
if (!getControlled()) return;
if (!unitsManager->isUnitGroupLeader(this)) return;
if (!(getAlive() || unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this))) return;
if (getHuman()) return;
/* Set the default IDLE state */
setState(State::IDLE);
/* Set desired altitude to be equal to current altitude so the unit does not climb/descend after spawn */
setDesiredAltitude(position.alt);
/* Set the default options */
setROE(ROE::OPEN_FIRE_WEAPON_FREE, force);
setReactionToThreat(ReactionToThreat::EVADE_FIRE, force);
setEmissionsCountermeasures(EmissionCountermeasure::DEFEND, force);
strcpy_s(TACAN.callsign, 4, "TKR");
setTACAN(TACAN, force);
setRadio(radio, force);
setGeneralSettings(generalSettings, force);
}
void Unit::runAILoop() {
/* If the unit is alive, controlled and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */
if (!getControlled()) return;
if (!unitsManager->isUnitGroupLeader(this)) return;
if (human) return;
/* Keep running the AI loop even if the unit is dead if it is the leader of a group which has other members in it */
const bool isUnitAlive = getAlive();
const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this);
if (!(isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits)) return;
if (checkTaskFailed() && state != State::IDLE && state != State::LAND)
setState(State::IDLE);
AIloop();
}
void Unit::updateExportData(json::value json, double dt)
{
Coords newPosition = Coords(NULL);
double newHeading = 0;
double newSpeed = 0;
if (json.has_object_field(L"LatLongAlt"))
if (json.has_object_field(L"position"))
{
setPosition({
json[L"LatLongAlt"][L"Lat"].as_number().to_double(),
json[L"LatLongAlt"][L"Long"].as_number().to_double(),
json[L"LatLongAlt"][L"Alt"].as_number().to_double()
json[L"position"][L"lat"].as_number().to_double(),
json[L"position"][L"lng"].as_number().to_double(),
json[L"position"][L"alt"].as_number().to_double()
});
}
if (json.has_number_field(L"Heading"))
setHeading(json[L"Heading"].as_number().to_double());
/* Compute speed (loGetWorldObjects does not provide speed, we compute it for better performance instead of relying on many lua calls) */
if (oldPosition != NULL)
{
double dist = 0;
Geodesic::WGS84().Inverse(getPosition().lat, getPosition().lng, oldPosition.lat, oldPosition.lng, dist);
if (dt > 0)
setSpeed(getSpeed() * 0.95 + (dist / dt) * 0.05);
}
if (json.has_number_field(L"heading"))
setHeading(json[L"heading"].as_number().to_double());
oldPosition = position;
}
if (json.has_number_field(L"speed"))
setSpeed(json[L"speed"].as_number().to_double());
if (json.has_boolean_field(L"isAlive"))
setAlive(json[L"isAlive"].as_bool());
if (json.has_object_field(L"isHuman"))
setHuman(json[L"isHuman"].as_bool());
void Unit::updateMissionData(json::value json)
{
if (json.has_number_field(L"fuel")) {
setFuel(short(json[L"fuel"].as_number().to_double() * 100));
}
if (json.has_object_field(L"ammo")) {
if (json.has_object_field(L"ammo")) {
vector<DataTypes::Ammo> ammo;
for (auto const& el : json[L"ammo"].as_object()) {
DataTypes::Ammo ammoItem;
@ -146,16 +100,16 @@ void Unit::updateMissionData(json::value json)
}
setAmmo(ammo);
}
if (json.has_object_field(L"contacts")) {
vector<DataTypes::Contact> contacts;
for (auto const& el : json[L"contacts"].as_object()) {
DataTypes::Contact contactItem;
auto contactJson = el.second;
contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32();
string detectionMethod = to_string(contactJson[L"detectionMethod"]);
if (detectionMethod.compare("VISUAL") == 0) contactItem.detectionMethod = 1;
if (detectionMethod.compare("VISUAL") == 0) contactItem.detectionMethod = 1;
else if (detectionMethod.compare("OPTIC") == 0) contactItem.detectionMethod = 2;
else if (detectionMethod.compare("RADAR") == 0) contactItem.detectionMethod = 4;
else if (detectionMethod.compare("IRST") == 0) contactItem.detectionMethod = 8;
@ -168,8 +122,68 @@ void Unit::updateMissionData(json::value json)
if (json.has_boolean_field(L"hasTask"))
setHasTask(json[L"hasTask"].as_bool());
runAILoop();
}
void Unit::setDefaults(bool force)
{
}
void Unit::runAILoop() {
/* Set isLeader */
Unit* leader = nullptr;
setIsLeader(unitsManager->isUnitGroupLeader(this, leader));
/* When units are in a group, most data comes from the group leader. If new data is available, align it from the leader */
if (!getIsLeader()) {
if (leader != nullptr) {
for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++)
{
if (leader->checkFreshness(datumIndex, lastLoopTime)) {
switch (datumIndex) {
case DataIndex::controlled: updateValue(controlled, leader->controlled, datumIndex); break;
case DataIndex::state: updateValue(state, leader->state, datumIndex); break;
case DataIndex::task: updateValue(task, leader->task, datumIndex); break;
case DataIndex::hasTask: updateValue(hasTask, leader->hasTask, datumIndex); break;
case DataIndex::isTanker: updateValue(isTanker, leader->isTanker, datumIndex); break;
case DataIndex::isAWACS: updateValue(isAWACS, leader->isAWACS, datumIndex); break;
case DataIndex::onOff: updateValue(onOff, leader->onOff, datumIndex); break;
case DataIndex::followRoads: updateValue(followRoads, leader->followRoads, datumIndex); break;
case DataIndex::desiredSpeed: updateValue(desiredSpeed, leader->desiredSpeed, datumIndex); break;
case DataIndex::desiredSpeedType: updateValue(desiredSpeedType, leader->desiredSpeedType, datumIndex); break;
case DataIndex::desiredAltitude: updateValue(desiredAltitude, leader->desiredAltitude, datumIndex); break;
case DataIndex::desiredAltitudeType: updateValue(desiredAltitudeType, leader->desiredAltitudeType, datumIndex); break;
case DataIndex::leaderID: updateValue(leaderID, leader->leaderID, datumIndex); break;
case DataIndex::formationOffset: updateValue(formationOffset, leader->formationOffset, datumIndex); break;
case DataIndex::targetID: updateValue(targetID, leader->targetID, datumIndex); break;
case DataIndex::targetPosition: updateValue(targetPosition, leader->targetPosition, datumIndex); break;
case DataIndex::ROE: updateValue(ROE, leader->ROE, datumIndex); break;
case DataIndex::reactionToThreat: updateValue(reactionToThreat, leader->reactionToThreat, datumIndex); break;
case DataIndex::emissionsCountermeasures: updateValue(emissionsCountermeasures, leader->emissionsCountermeasures, datumIndex); break;
case DataIndex::TACAN: updateValue(TACAN, leader->TACAN, datumIndex); break;
case DataIndex::radio: updateValue(radio, leader->radio, datumIndex); break;
case DataIndex::generalSettings: updateValue(generalSettings, leader->generalSettings, datumIndex); break;
case DataIndex::activePath: updateValue(activePath, leader->activePath, datumIndex); break;
}
}
}
}
}
/* If the unit is alive, controlled, is the leader of the group and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */
if (getAlive() && getControlled() && !getHuman() && getIsLeader()) {
if (checkTaskFailed() && state != State::IDLE && state != State::LAND)
setState(State::IDLE);
AIloop();
}
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
lastLoopTime = ms.count();
}
bool Unit::checkFreshness(unsigned char datumIndex, unsigned long long time) {
auto it = updateTimeMap.find(datumIndex);
if (it == updateTimeMap.end())
@ -187,55 +201,51 @@ bool Unit::hasFreshData(unsigned long long time) {
void Unit::getData(stringstream& ss, unsigned long long time)
{
Unit* leader = this;
if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this))
leader = unitsManager->getGroupLeader(this);
if (leader == nullptr || (!leader->hasFreshData(time) && !hasFreshData(time))) return;
const unsigned char endOfData = DataIndex::endOfData;
ss.write((const char*)&ID, sizeof(ID));
for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++)
{
/* When units are in a group, most data comes from the group leader */
switch (datumIndex) {
case DataIndex::category: if (checkFreshness(datumIndex, time)) appendString(ss, datumIndex, category); break;
case DataIndex::alive: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, alive); break;
case DataIndex::human: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->human); break;
case DataIndex::controlled: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->controlled); break;
case DataIndex::coalition: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->coalition); break;
case DataIndex::country: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->country); break;
case DataIndex::name: if (checkFreshness(datumIndex, time)) appendString(ss, datumIndex, name); break;
case DataIndex::unitName: if (checkFreshness(datumIndex, time)) appendString(ss, datumIndex, unitName); break;
case DataIndex::groupName: if (leader->checkFreshness(datumIndex, time)) appendString(ss, datumIndex, leader->groupName); break;
case DataIndex::state: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->state); break;
case DataIndex::task: if (leader->checkFreshness(datumIndex, time)) appendString(ss, datumIndex, leader->task); break;
case DataIndex::hasTask: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->hasTask); break;
case DataIndex::position: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, position); break;
case DataIndex::speed: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, speed); break;
case DataIndex::heading: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, heading); break;
case DataIndex::isTanker: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->isTanker); break;
case DataIndex::isAWACS: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->isAWACS); break;
case DataIndex::onOff: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->onOff); break;
case DataIndex::followRoads: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->followRoads); break;
case DataIndex::fuel: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, fuel); break;
case DataIndex::desiredSpeed: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredSpeed); break;
case DataIndex::desiredSpeedType: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredSpeedType); break;
case DataIndex::desiredAltitude: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredAltitude); break;
case DataIndex::desiredAltitudeType: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredAltitudeType); break;
case DataIndex::leaderID: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->leaderID); break;
case DataIndex::formationOffset: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->formationOffset); break;
case DataIndex::targetID: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->targetID); break;
case DataIndex::targetPosition: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->targetPosition); break;
case DataIndex::ROE: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->ROE); break;
case DataIndex::reactionToThreat: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->reactionToThreat); break;
case DataIndex::emissionsCountermeasures: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->emissionsCountermeasures); break;
case DataIndex::TACAN: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->TACAN); break;
case DataIndex::radio: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->radio); break;
case DataIndex::generalSettings: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->generalSettings); break;
case DataIndex::ammo: if (checkFreshness(datumIndex, time)) appendVector(ss, datumIndex, ammo); break;
case DataIndex::contacts: if (checkFreshness(datumIndex, time)) appendVector(ss, datumIndex, contacts); break;
case DataIndex::activePath: if (leader->checkFreshness(datumIndex, time)) appendList(ss, datumIndex, leader->activePath); break;
if (checkFreshness(datumIndex, time)) {
switch (datumIndex) {
case DataIndex::category: appendString(ss, datumIndex, category); break;
case DataIndex::alive: appendNumeric(ss, datumIndex, alive); break;
case DataIndex::human: appendNumeric(ss, datumIndex, human); break;
case DataIndex::controlled: appendNumeric(ss, datumIndex, controlled); break;
case DataIndex::coalition: appendNumeric(ss, datumIndex, coalition); break;
case DataIndex::country: appendNumeric(ss, datumIndex, country); break;
case DataIndex::name: appendString(ss, datumIndex, name); break;
case DataIndex::unitName: appendString(ss, datumIndex, unitName); break;
case DataIndex::groupName: appendString(ss, datumIndex, groupName); break;
case DataIndex::state: appendNumeric(ss, datumIndex, state); break;
case DataIndex::task: appendString(ss, datumIndex, task); break;
case DataIndex::hasTask: appendNumeric(ss, datumIndex, hasTask); break;
case DataIndex::position: appendNumeric(ss, datumIndex, position); break;
case DataIndex::speed: appendNumeric(ss, datumIndex, speed); break;
case DataIndex::heading: appendNumeric(ss, datumIndex, heading); break;
case DataIndex::isTanker: appendNumeric(ss, datumIndex, isTanker); break;
case DataIndex::isAWACS: appendNumeric(ss, datumIndex, isAWACS); break;
case DataIndex::onOff: appendNumeric(ss, datumIndex, onOff); break;
case DataIndex::followRoads: appendNumeric(ss, datumIndex, followRoads); break;
case DataIndex::fuel: appendNumeric(ss, datumIndex, fuel); break;
case DataIndex::desiredSpeed: appendNumeric(ss, datumIndex, desiredSpeed); break;
case DataIndex::desiredSpeedType: appendNumeric(ss, datumIndex, desiredSpeedType); break;
case DataIndex::desiredAltitude: appendNumeric(ss, datumIndex, desiredAltitude); break;
case DataIndex::desiredAltitudeType: appendNumeric(ss, datumIndex, desiredAltitudeType); break;
case DataIndex::leaderID: appendNumeric(ss, datumIndex, leaderID); break;
case DataIndex::formationOffset: appendNumeric(ss, datumIndex, formationOffset); break;
case DataIndex::targetID: appendNumeric(ss, datumIndex, targetID); break;
case DataIndex::targetPosition: appendNumeric(ss, datumIndex, targetPosition); break;
case DataIndex::ROE: appendNumeric(ss, datumIndex, ROE); break;
case DataIndex::reactionToThreat: appendNumeric(ss, datumIndex, reactionToThreat); break;
case DataIndex::emissionsCountermeasures: appendNumeric(ss, datumIndex, emissionsCountermeasures); break;
case DataIndex::TACAN: appendNumeric(ss, datumIndex, TACAN); break;
case DataIndex::radio: appendNumeric(ss, datumIndex, radio); break;
case DataIndex::generalSettings: appendNumeric(ss, datumIndex, generalSettings); break;
case DataIndex::ammo: appendVector(ss, datumIndex, ammo); break;
case DataIndex::contacts: appendVector(ss, datumIndex, contacts); break;
case DataIndex::activePath: appendList(ss, datumIndex, activePath); break;
case DataIndex::isLeader: appendNumeric(ss, datumIndex, isLeader); break;
}
}
}
ss.write((const char*)&endOfData, sizeof(endOfData));
@ -537,8 +547,8 @@ void Unit::setRadio(DataTypes::Radio newRadio, bool force)
commandSS << "{"
<< "id = 'SetCallsign',"
<< "params = {"
<< "callname = " << radio.callsign << ","
<< "number = " << radio.callsignNumber << ","
<< "callname = " << to_string(radio.callsign) << ","
<< "number = " << to_string(radio.callsignNumber) << ","
<< "}"
<< "}";
command = dynamic_cast<Command*>(new SetCommand(groupName, commandSS.str()));
@ -572,46 +582,54 @@ void Unit::setGeneralSettings(DataTypes::GeneralSettings newGeneralSettings, boo
void Unit::setDesiredSpeed(double newDesiredSpeed)
{
desiredSpeed = newDesiredSpeed;
if (state == State::IDLE)
resetTask();
else
goToDestination(); /* Send the command to reach the destination */
if (desiredSpeed != newDesiredSpeed) {
desiredSpeed = newDesiredSpeed;
if (state == State::IDLE)
resetTask();
else
goToDestination(); /* Send the command to reach the destination */
triggerUpdate(DataIndex::desiredSpeed);
triggerUpdate(DataIndex::desiredSpeed);
}
}
void Unit::setDesiredAltitude(double newDesiredAltitude)
{
desiredAltitude = newDesiredAltitude;
if (state == State::IDLE)
resetTask();
else
goToDestination(); /* Send the command to reach the destination */
if (desiredAltitude != newDesiredAltitude) {
desiredAltitude = newDesiredAltitude;
if (state == State::IDLE)
resetTask();
else
goToDestination(); /* Send the command to reach the destination */
triggerUpdate(DataIndex::desiredAltitude);
triggerUpdate(DataIndex::desiredAltitude);
}
}
void Unit::setDesiredSpeedType(string newDesiredSpeedType)
{
desiredSpeedType = newDesiredSpeedType.compare("GS") == 0;
if (state == State::IDLE)
resetTask();
else
goToDestination(); /* Send the command to reach the destination */
if (desiredSpeedType != (newDesiredSpeedType.compare("GS") == 0)) {
desiredSpeedType = newDesiredSpeedType.compare("GS") == 0;
if (state == State::IDLE)
resetTask();
else
goToDestination(); /* Send the command to reach the destination */
triggerUpdate(DataIndex::desiredSpeedType);
triggerUpdate(DataIndex::desiredSpeedType);
}
}
void Unit::setDesiredAltitudeType(string newDesiredAltitudeType)
{
desiredAltitudeType = newDesiredAltitudeType.compare("AGL") == 0;
if (state == State::IDLE)
resetTask();
else
goToDestination(); /* Send the command to reach the destination */
if (desiredAltitudeType != (newDesiredAltitudeType.compare("AGL") == 0)) {
desiredAltitudeType = newDesiredAltitudeType.compare("AGL") == 0;
if (state == State::IDLE)
resetTask();
else
goToDestination(); /* Send the command to reach the destination */
triggerUpdate(DataIndex::desiredAltitudeType);
triggerUpdate(DataIndex::desiredAltitudeType);
}
}
void Unit::goToDestination(string enrouteTask)

View File

@ -42,33 +42,33 @@ bool UnitsManager::isUnitInGroup(Unit* unit)
if (groupName.length() == 0) return false;
for (auto const& p : units)
{
if (p.second->getGroupName().compare(groupName) == 0 && p.second != unit)
if (p.second->getGroupName().compare(groupName) == 0 && p.second != unit && p.second->getAlive())
return true;
}
}
return false;
}
bool UnitsManager::isUnitGroupLeader(Unit* unit)
/* Returns true if unit is group leader. Else, returns false, and leader will be equal to the group leader */
bool UnitsManager::isUnitGroupLeader(Unit* unit, Unit*& leader)
{
if (unit != nullptr) {
Unit* leader = getGroupLeader(unit);
return leader == nullptr? false: unit == getGroupLeader(unit);
leader = getGroupLeader(unit);
return leader == nullptr? false: unit == leader;
}
else
return false;
}
// The group leader is the unit with the lowest ID that is part of the group. This is different from DCS's concept of leader, which will change if the leader is destroyed
Unit* UnitsManager::getGroupLeader(Unit* unit)
{
if (unit != nullptr) {
string groupName = unit->getGroupName();
if (groupName.length() == 0) return nullptr;
/* Find the first unit that has the same groupName */
/* Find the first alive unit that has the same groupName */
for (auto const& p : units)
{
if (p.second->getGroupName().compare(groupName) == 0)
if (p.second->getAlive() && p.second->getGroupName().compare(groupName) == 0)
return p.second;
}
}
@ -92,63 +92,42 @@ Unit* UnitsManager::getGroupLeader(unsigned int ID)
return getGroupLeader(unit);
}
void UnitsManager::updateExportData(lua_State* L, double dt)
void UnitsManager::update(json::value& json, double dt)
{
map<unsigned int, json::value> unitJSONs = getAllUnits(L);
/* Update all units, create them if needed TODO: move code to get constructor in dedicated function */
for (auto const& p : unitJSONs)
for (auto const& p : json.as_object())
{
unsigned int ID = p.first;
unsigned int ID = std::stoi(p.first);
if (units.count(ID) == 0)
{
json::value type = static_cast<json::value>(p.second)[L"Type"];
if (type.has_number_field(L"level1"))
{
if (type[L"level1"].as_number().to_int32() == 1)
{
if (type[L"level2"].as_number().to_int32() == 1)
units[ID] = dynamic_cast<Unit*>(new Aircraft(p.second, ID));
else if (type[L"level2"].as_number().to_int32() == 2)
units[ID] = dynamic_cast<Unit*>(new Helicopter(p.second, ID));
}
else if (type[L"level1"].as_number().to_int32() == 2)
json::value value = p.second;
if (value.has_string_field(L"category")) {
string category = to_string(value[L"category"].as_string());
if (category.compare("Aircraft") == 0)
units[ID] = dynamic_cast<Unit*>(new Aircraft(p.second, ID));
else if (category.compare("Helicopter") == 0)
units[ID] = dynamic_cast<Unit*>(new Helicopter(p.second, ID));
else if (category.compare("GroundUnit") == 0)
units[ID] = dynamic_cast<Unit*>(new GroundUnit(p.second, ID));
else if (type[L"level1"].as_number().to_int32() == 3)
else if (category.compare("NavyUnit") == 0)
units[ID] = dynamic_cast<Unit*>(new NavyUnit(p.second, ID));
else if (type[L"level1"].as_number().to_int32() == 4)
{
if (type[L"level2"].as_number().to_int32() == 4)
units[ID] = dynamic_cast<Unit*>(new Missile(p.second, ID));
else if (type[L"level2"].as_number().to_int32() == 5)
units[ID] = dynamic_cast<Unit*>(new Bomb(p.second, ID));
else if (category.compare("Missile") == 0)
units[ID] = dynamic_cast<Unit*>(new Missile(p.second, ID));
else if (category.compare("Bomb") == 0)
units[ID] = dynamic_cast<Unit*>(new Bomb(p.second, ID));
/* Initialize the unit if creation was successfull */
if (units.count(ID) != 0) {
units[ID]->update(p.second, dt);
units[ID]->initialize(p.second);
}
}
/* Initialize the unit if creation was successfull */
if (units.count(ID) != 0)
units[ID]->initialize(p.second);
}
else {
/* Update the unit if present*/
if (units.count(ID) != 0)
units[ID]->updateExportData(p.second, dt);
units[ID]->update(p.second, dt);
}
}
/* Set the units that are not present in the JSON as dead (probably have been destroyed) */
for (auto const& unit : units)
unit.second->setAlive(unitJSONs.find(unit.first) != unitJSONs.end());
}
void UnitsManager::updateMissionData(json::value missionData)
{
/* Update all units */
for (auto const& p : units)
{
unsigned int ID = p.first;
if (missionData.has_field(to_wstring(ID)))
p.second->updateMissionData(missionData[to_wstring(ID)]);
}
}
void UnitsManager::runAILoop() {
@ -157,11 +136,10 @@ void UnitsManager::runAILoop() {
unit.second->runAILoop();
}
string UnitsManager::getUnitData(stringstream &ss, unsigned long long time)
void UnitsManager::getUnitData(stringstream &ss, unsigned long long time)
{
for (auto const& p : units)
p.second->getData(ss, time);
return to_base64(ss.str());
}
void UnitsManager::deleteUnit(unsigned int ID, bool explosion, bool immediate)
@ -174,16 +152,12 @@ void UnitsManager::deleteUnit(unsigned int ID, bool explosion, bool immediate)
}
void UnitsManager::acquireControl(unsigned int ID) {
Unit* unit = getUnit(ID);
if (unit != nullptr) {
for (auto const& groupMember : getGroupMembers(unit->getGroupName())) {
if (!groupMember->getControlled()) {
groupMember->setControlled(true);
groupMember->setState(State::IDLE);
groupMember->setDefaults(true);
}
Unit* leader = getGroupLeader(ID);
if (leader != nullptr) {
if (!leader->getControlled()) {
leader->setControlled(true);
leader->setDefaults(true);
}
}
}
}

View File

@ -7,6 +7,6 @@ void DllExport LogWarning(lua_State* L, string message);
void DllExport LogError(lua_State* L, string message);
void DllExport Log(lua_State* L, string message, unsigned int level);
int DllExport dostring_in(lua_State* L, string target, string command);
map<unsigned int, json::value> DllExport getAllUnits(lua_State* L);
void DllExport getAllUnits(lua_State* L, map<unsigned int, json::value>& unitJSONs);
unsigned int DllExport TACANChannelToFrequency(unsigned int channel, char XY);

View File

@ -56,10 +56,9 @@ void Log(lua_State* L, string message, unsigned int level)
STACK_CLEAN;
}
map<unsigned int, json::value> getAllUnits(lua_State* L)
void getAllUnits(lua_State* L, map<unsigned int, json::value>& unitJSONs)
{
unsigned int res = 0;
map<unsigned int, json::value> units;
STACK_INIT;
@ -84,15 +83,16 @@ map<unsigned int, json::value> getAllUnits(lua_State* L)
while (lua_next(L, 2) != 0)
{
unsigned int ID = lua_tonumber(L, -2);
// TODO more efficient method can be used, converting all the lua data to a json object may be overkill
units[ID] = luaTableToJSON(L, -1);
if (unitJSONs.find(ID) == unitJSONs.end())
unitJSONs[ID] = json::value::object();
luaTableToJSON(L, -1, unitJSONs[ID]);
STACK_POP(1)
}
}
exit:
STACK_CLEAN;
return units;
return;
}
int dostring_in(lua_State* L, string target, string command)

View File

@ -3,4 +3,4 @@
void DllExport log(const std::string& sMessage);
void DllExport log(const std::wstring& sMessage);
void DllExport getLogsJSON(json::value& json, unsigned int logsNumber = NULL);
void DllExport getLogsJSON(json::value& json, unsigned long long time);

View File

@ -7,7 +7,7 @@ class Logger
public:
void log(const string& sMessage);
void log(const wstring& sMessage);
void toJSON(json::value& json, unsigned int logsNumber = NULL);
void toJSON(json::value& json, unsigned long long time);
static Logger* GetLogger();
@ -19,7 +19,7 @@ private:
static const string m_sFileName;
static Logger* m_pThis;
static ofstream m_Logfile;
static std::list<std::string> m_logs;
static std::map<unsigned long long, std::string> m_logs;
mutex mutexLock;

View File

@ -14,7 +14,7 @@ void log(const wstring& message)
LOGGER->log(message);
}
void getLogsJSON(json::value& json, unsigned int logsNumber)
void getLogsJSON(json::value& json, unsigned long long time)
{
LOGGER->toJSON(json, logsNumber);
LOGGER->toJSON(json, time);
}

View File

@ -1,11 +1,13 @@
#include "logger.h"
#include "utils.h"
#include "defines.h"
#include <chrono>
using namespace std::chrono;
const string Logger::m_sFileName = LOG_NAME;
Logger* Logger::m_pThis = NULL;
ofstream Logger::m_Logfile;
std::list<std::string> Logger::m_logs;
std::map<unsigned long long, std::string> Logger::m_logs;
Logger::Logger()
{
@ -32,15 +34,18 @@ void Logger::Close()
m_Logfile.close();
}
void Logger::toJSON(json::value& json, unsigned int logsNumber)
void Logger::toJSON(json::value& json, unsigned long long time)
{
lock_guard<mutex> guard(mutexLock);
unsigned int i = 0;
for (auto itr = m_logs.end(); itr != m_logs.begin(); --itr)
json[L"requestTime"] = time;
/* Loop on the logs in reverse since we are usually only interested in the very last added logs */
auto itr = m_logs.end();
while (itr != m_logs.begin())
{
json[to_wstring(m_logs.size() - 1 - i)] = json::value::string(to_wstring(*itr));
if (logsNumber != 0 && i > logsNumber)
break;
--itr;
if (itr->first < time) return;
json[to_wstring(itr->first)] = json::value::string(to_wstring(itr->second));
}
}
@ -48,9 +53,10 @@ void Logger::log(const string& message)
{
lock_guard<mutex> guard(mutexLock);
Open();
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
m_Logfile << CurrentDateTime() << ":\t";
m_Logfile << message << "\n";
m_logs.push_back(CurrentDateTime() + ": " + message);
m_logs[static_cast<unsigned long long>(ms.count())] = CurrentDateTime() + ": " + message;
Close();
}
@ -58,8 +64,9 @@ void Logger::log(const wstring& message)
{
lock_guard<mutex> guard(mutexLock);
Open();
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
m_Logfile << CurrentDateTime() << ":\t";
m_Logfile << to_string(message) << "\n";
m_logs.push_back(CurrentDateTime() + ": " + to_string(message));
m_logs[static_cast<unsigned long long>(ms.count())] = CurrentDateTime() + ": " + to_string(message);
Close();
}

View File

@ -4,7 +4,7 @@
void DllExport stackUpdate(lua_State* L, int& stackDepth, int initialStack = 0);
void DllExport stackPop(lua_State* L, int popDepth = 1);
void DllExport stackClean(lua_State* L, int stackDepth);
json::value DllExport luaTableToJSON(lua_State* L, int index, bool logKeys = false);
void DllExport luaTableToJSON(lua_State* L, int index, json::value& json, bool logKeys = false);
#define STACK_UPDATE stackUpdate(L, stackDepth, initialStack);
#define STACK_INIT int stackDepth = 0; int initialStack = 0; stackUpdate(L, initialStack);

View File

@ -18,10 +18,8 @@ void stackClean(lua_State* L, int stackDepth)
lua_pop(L, stackDepth);
}
json::value luaTableToJSON(lua_State* L, int index, bool logKeys)
void luaTableToJSON(lua_State* L, int index, json::value& json, bool logKeys)
{
auto json = json::value::object();
if (lua_istable(L, index))
{
STACK_INIT;
@ -38,7 +36,9 @@ json::value luaTableToJSON(lua_State* L, int index, bool logKeys)
}
if (lua_istable(L, -2))
{
json[to_wstring(key)] = luaTableToJSON(L, -2, logKeys);
if (!json.has_object_field(to_wstring(key)))
json[to_wstring(key)] = json::value::object();
luaTableToJSON(L, -2, json[to_wstring(key)], logKeys);
}
else if (lua_isnumber(L, -2))
{
@ -58,6 +58,5 @@ json::value luaTableToJSON(lua_State* L, int index, bool logKeys)
STACK_CLEAN;
}
return json;
}

View File

@ -8,10 +8,12 @@ HINSTANCE hGetProcIDDLL = NULL;
typedef int(__stdcall* f_coreInit)(lua_State* L);
typedef int(__stdcall* f_coreDeinit)(lua_State* L);
typedef int(__stdcall* f_coreFrame)(lua_State* L);
typedef int(__stdcall* f_coreUnitsData)(lua_State* L);
typedef int(__stdcall* f_coreMissionData)(lua_State* L);
f_coreInit coreInit = nullptr;
f_coreDeinit coreDeinit = nullptr;
f_coreFrame coreFrame = nullptr;
f_coreUnitsData coreUnitsData = nullptr;
f_coreMissionData coreMissionData = nullptr;
static int onSimulationStart(lua_State* L)
@ -52,7 +54,7 @@ static int onSimulationStart(lua_State* L)
}
coreDeinit = (f_coreDeinit)GetProcAddress(hGetProcIDDLL, "coreDeinit");
if (!coreInit)
if (!coreDeinit)
{
LogError(L, "Error getting coreDeinit ProcAddress from DLL");
goto error;
@ -65,8 +67,15 @@ static int onSimulationStart(lua_State* L)
goto error;
}
coreUnitsData = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreUnitsData");
if (!coreUnitsData)
{
LogError(L, "Error getting coreUnitsData ProcAddress from DLL");
goto error;
}
coreMissionData = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreMissionData");
if (!coreFrame)
if (!coreMissionData)
{
LogError(L, "Error getting coreMissionData ProcAddress from DLL");
goto error;
@ -116,6 +125,7 @@ static int onSimulationStop(lua_State* L)
coreInit = nullptr;
coreDeinit = nullptr;
coreFrame = nullptr;
coreUnitsData = nullptr;
coreMissionData = nullptr;
}
@ -128,6 +138,15 @@ error:
return 0;
}
static int setUnitsData(lua_State* L)
{
if (coreUnitsData)
{
coreUnitsData(L);
}
return 0;
}
static int setMissionData(lua_State* L)
{
if (coreMissionData)
@ -141,6 +160,7 @@ static const luaL_Reg Map[] = {
{"onSimulationStart", onSimulationStart},
{"onSimulationFrame", onSimulationFrame},
{"onSimulationStop", onSimulationStop},
{"setUnitsData", setUnitsData },
{"setMissionData", setMissionData },
{NULL, NULL}
};

View File

@ -10,5 +10,4 @@
#define BULLSEYE_URI "bullseyes"
#define MISSION_URI "mission"
#define UPDATE_TIME_INTERVAL 0.25
#define EXECUTION_TIME_INTERVAL 0.05