9 Commits

Author SHA1 Message Date
Pax1601
c3b4c4ae36 Multiple bugfixes 2023-07-31 08:08:20 +02:00
Pax1601
fd7f4b9772 Multiple bug fixes and optimizations 2023-07-28 17:43:31 +02:00
Pax1601
bac2c430ef Merge pull request #342 from Pax1601/340-dont-install-development-node-modules
Added package command to prune dev modules
2023-07-27 19:22:35 +02:00
Pax1601
3f1c9942c3 Merge pull request #341 from Pax1601/333-missiles-and-bombs-are-not-being-exported
333 missiles and bombs are not being exported
2023-07-27 19:22:08 +02:00
Pax1601
80ed675cbc Completed transition to weapons handler 2023-07-27 19:21:34 +02:00
Pax1601
6d434e48a1 Added missing files 2023-07-27 17:16:21 +02:00
Pax1601
7961870ac4 Added package command to prune dev modules 2023-07-27 17:15:57 +02:00
Pax1601
0150ae9df1 Splitted weapons and units managers 2023-07-27 16:27:59 +02:00
Pax1601
875f3ebe68 Added handler to detect units and weapons creation 2023-07-26 22:39:26 +02:00
52 changed files with 6635 additions and 5300 deletions

View File

@@ -33,22 +33,7 @@ const DEMO_UNIT_DATA = {
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ], ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
contacts: [{ID: 1, detectionMethod: 16}], contacts: [{ID: 1, detectionMethod: 16}],
activePath: [ ] activePath: [ ]
}, ["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", }, ["3"]:{ category: "Helicopter", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "AH-64D_BLK_II", unitName: "Cool guy 1-4", 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 },
targetID: 0,
targetPosition: { lat: 0, lng: 0, alt: 0 },
ROE: 2,
reactionToThreat: 1,
emissionsCountermeasures: 1,
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
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: 1, detectionMethod: 16}],
activePath: [ ]
}, ["4"]:{ category: "Helicopter", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "AH-64D_BLK_II", unitName: "Cool guy 1-4", groupName: "Cool group 3", state: 1, task: "Being cool",
hasTask: false, position: { lat: 37.1, lng: -116.1, alt: 1000 }, speed: 200, heading: 315 * Math.PI / 180, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50, hasTask: false, position: { lat: 37.1, lng: -116.1, 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, desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
formationOffset: { x: 0, y: 0, z: 0 }, formationOffset: { x: 0, y: 0, z: 0 },
@@ -63,7 +48,7 @@ const DEMO_UNIT_DATA = {
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ], ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
contacts: [{ID: 1, detectionMethod: 16}], contacts: [{ID: 1, detectionMethod: 16}],
activePath: [ ] activePath: [ ]
}, ["5"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 1, country: 0, name: "Gepard", unitName: "Cool guy 2-1", groupName: "Cool group 4", state: 1, task: "Being cool", }, ["4"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 1, country: 0, name: "Gepard", unitName: "Cool guy 2-1", groupName: "Cool group 4", state: 1, task: "Being cool",
hasTask: false, position: { lat: 37.2, lng: -116.1, alt: 1000 }, speed: 200, heading: 315 * Math.PI / 180, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50, hasTask: false, position: { lat: 37.2, lng: -116.1, 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, desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
formationOffset: { x: 0, y: 0, z: 0 }, formationOffset: { x: 0, y: 0, z: 0 },
@@ -76,10 +61,10 @@ const DEMO_UNIT_DATA = {
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 }, radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false }, generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
ammo: [{ quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } ], ammo: [{ quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } ],
contacts: [{ID: 1, detectionMethod: 16}], contacts: [{ID: 1001, detectionMethod: 16}],
activePath: [ ], activePath: [ ],
isLeader: true isLeader: true
}, ["6"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 1, country: 0, name: "Gepard", unitName: "Cool guy 2-2", groupName: "Cool group 4", state: 1, task: "Being cool", }, ["5"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 1, country: 0, name: "Gepard", unitName: "Cool guy 2-2", groupName: "Cool group 4", state: 1, task: "Being cool",
hasTask: false, position: { lat: 37.21, lng: -116.1, alt: 1000 }, speed: 200, heading: 315 * Math.PI / 180, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50, hasTask: false, position: { lat: 37.21, lng: -116.1, 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, desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
formationOffset: { x: 0, y: 0, z: 0 }, formationOffset: { x: 0, y: 0, z: 0 },
@@ -92,16 +77,21 @@ const DEMO_UNIT_DATA = {
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 }, radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false }, generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ], ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
contacts: [{ID: 1, detectionMethod: 16}], contacts: [],
activePath: [ ], activePath: [ ],
isLeader: false isLeader: false
} }
} }
const DEMO_WEAPONS_DATA = {
["1001"]:{ category: "Missile", alive: true, coalition: 2, name: "", position: { lat: 37.1, lng: -116, alt: 1000 }, speed: 200, heading: 45 * Math.PI / 180 },
}
class DemoDataGenerator { class DemoDataGenerator {
constructor(app) constructor(app)
{ {
app.get('/demo/units', (req, res) => this.units(req, res)); app.get('/demo/units', (req, res) => this.units(req, res));
app.get('/demo/weapons', (req, res) => this.weapons(req, res));
app.get('/demo/logs', (req, res) => this.logs(req, res)); app.get('/demo/logs', (req, res) => this.logs(req, res));
app.get('/demo/bullseyes', (req, res) => this.bullseyes(req, res)); app.get('/demo/bullseyes', (req, res) => this.bullseyes(req, res));
app.get('/demo/airbases', (req, res) => this.airbases(req, res)); app.get('/demo/airbases', (req, res) => this.airbases(req, res));
@@ -168,6 +158,25 @@ class DemoDataGenerator {
res.end(Buffer.from(array, 'binary')); res.end(Buffer.from(array, 'binary'));
}; };
weapons(req, res){
var array = new Uint8Array();
var time = Date.now();
array = this.concat(array, this.uint64ToByteArray(BigInt(time)));
for (let idx in DEMO_WEAPONS_DATA) {
const weapon = DEMO_WEAPONS_DATA[idx];
array = this.concat(array, this.uint32ToByteArray(idx));
array = this.appendString(array, weapon.category, 1);
array = this.appendUint8(array, weapon.alive, 2);
array = this.appendUint16(array, weapon.coalition, 5);
array = this.appendString(array, weapon.name, 7);
array = this.appendCoordinates(array, weapon.position, 13);
array = this.appendDouble(array, weapon.speed, 14);
array = this.appendDouble(array, weapon.heading, 15);
array = this.concat(array, this.uint8ToByteArray(255));
}
res.end(Buffer.from(array, 'binary'));
};
concat(array1, array2) { concat(array1, array2) {
var mergedArray = new Uint8Array(array1.length + array2.length); var mergedArray = new Uint8Array(array1.length + array2.length);
mergedArray.set(array1); mergedArray.set(array1);

446
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "DCSOlympus", "name": "DCSOlympus",
"node-main": "./bin/www", "node-main": "./bin/www",
"main": "http://localhost:3000", "main": "http://localhost:3000",
"version": "v0.4.0-alpha", "version": "v0.4.1-alpha",
"private": true, "private": true,
"scripts": { "scripts": {
"copy": "copy.bat", "copy": "copy.bat",
@@ -10,35 +10,35 @@
"watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]" "watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]"
}, },
"dependencies": { "dependencies": {
"@turf/turf": "^6.5.0",
"@types/formatcoords": "^1.1.0",
"@types/geojson": "^7946.0.10",
"@types/leaflet": "^1.9.0",
"@types/svg-injector": "^0.0.29",
"cookie-parser": "~1.4.4", "cookie-parser": "~1.4.4",
"debug": "~2.6.9", "debug": "~2.6.9",
"ejs": "^3.1.8", "ejs": "^3.1.8",
"express": "~4.16.1", "express": "~4.16.1",
"formatcoords": "^1.1.3",
"leaflet": "^1.9.3",
"leaflet-control-mini-map": "^0.4.0",
"leaflet-path-drag": "*",
"leaflet.nauticscale": "^1.1.0",
"morgan": "~1.9.1", "morgan": "~1.9.1",
"save": "^2.9.0" "save": "^2.9.0",
"express-basic-auth": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.21.4", "@babel/preset-env": "^7.21.4",
"@tanem/svg-injector": "^10.1.55", "@tanem/svg-injector": "^10.1.55",
"@turf/turf": "^6.5.0",
"@types/formatcoords": "^1.1.0",
"@types/geojson": "^7946.0.10",
"@types/gtag.js": "^0.0.12", "@types/gtag.js": "^0.0.12",
"@types/leaflet": "^1.9.0",
"@types/node": "^18.16.1", "@types/node": "^18.16.1",
"@types/sortablejs": "^1.15.0", "@types/sortablejs": "^1.15.0",
"@types/svg-injector": "^0.0.29",
"babelify": "^10.0.0", "babelify": "^10.0.0",
"browserify": "^17.0.0", "browserify": "^17.0.0",
"concurrently": "^7.6.0", "concurrently": "^7.6.0",
"cp": "^0.2.0", "cp": "^0.2.0",
"esmify": "^2.1.1", "esmify": "^2.1.1",
"express-basic-auth": "^1.2.1", "formatcoords": "^1.1.3",
"leaflet": "^1.9.3",
"leaflet-control-mini-map": "^0.4.0",
"leaflet-path-drag": "*",
"leaflet.nauticscale": "^1.1.0",
"nodemon": "^2.0.20", "nodemon": "^2.0.20",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"tsify": "^5.0.4", "tsify": "^5.0.4",

View File

@@ -1,6 +1,6 @@
import { LatLng } from "leaflet" import { LatLng } from "leaflet"
interface UnitIconOptions { interface ObjectIconOptions {
showState: boolean, showState: boolean,
showVvi: boolean, showVvi: boolean,
showHotgroup: boolean, showHotgroup: boolean,

View File

@@ -1,7 +1,7 @@
import { Dropdown } from "../controls/dropdown"; import { Dropdown } from "../controls/dropdown";
import { zeroAppend } from "../other/utils"; import { zeroAppend } from "../other/utils";
import { ATC } from "./atc"; import { ATC } from "./atc";
import { Unit } from "../units/unit"; import { Unit } from "../unit/unit";
import { getMissionHandler, getUnitsManager } from ".."; import { getMissionHandler, getUnitsManager } from "..";
import Sortable from "sortablejs"; import Sortable from "sortablejs";
import { FlightInterface } from "./atc"; import { FlightInterface } from "./atc";

View File

@@ -1,6 +1,6 @@
import { getUnitsManager } from ".."; import { getUnitsManager } from "..";
import { Panel } from "../panels/panel"; import { Panel } from "../panels/panel";
import { Unit } from "../units/unit"; import { Unit } from "../unit/unit";
export class UnitDataTable extends Panel { export class UnitDataTable extends Panel {
constructor(id: string) { constructor(id: string) {

View File

@@ -140,6 +140,7 @@ export const CARPET_BOMBING = "Carpet bombing";
export const FIRE_AT_AREA = "Fire at area"; export const FIRE_AT_AREA = "Fire at area";
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area"; export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"];
export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["groundunit-sam", "groundunit-sam-radar", "groundunit-sam-launcher"], ["groundunit-other", "groundunit-ewr"], ["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 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 Site", "Radar"]; export const IADSTypes = ["AAA", "MANPADS", "SAM Site", "Radar"];

View File

@@ -6,7 +6,7 @@ import { ContextMenu } from "./contextmenu";
import { Dropdown } from "./dropdown"; import { Dropdown } from "./dropdown";
import { Slider } from "./slider"; import { Slider } from "./slider";
import { Switch } from "./switch"; import { Switch } from "./switch";
import { groundUnitDatabase } from "../units/groundunitdatabase"; import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { createCheckboxOption, getCheckboxOptions } from "../other/utils"; import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
export class CoalitionAreaContextMenu extends ContextMenu { export class CoalitionAreaContextMenu extends ContextMenu {

View File

@@ -1,16 +1,16 @@
import { LatLng } from "leaflet"; import { LatLng } from "leaflet";
import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from ".."; import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from "..";
import { spawnExplosion, spawnSmoke } from "../server/server"; import { spawnExplosion, spawnSmoke } from "../server/server";
import { aircraftDatabase } from "../units/aircraftdatabase"; import { aircraftDatabase } from "../unit/aircraftdatabase";
import { groundUnitDatabase } from "../units/groundunitdatabase"; import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { helicopterDatabase } from "../units/helicopterdatabase"; import { helicopterDatabase } from "../unit/helicopterdatabase";
import { ContextMenu } from "./contextmenu"; import { ContextMenu } from "./contextmenu";
import { Dropdown } from "./dropdown"; import { Dropdown } from "./dropdown";
import { Switch } from "./switch"; import { Switch } from "./switch";
import { Slider } from "./slider"; import { Slider } from "./slider";
import { ftToM } from "../other/utils"; import { ftToM } from "../other/utils";
import { GAME_MASTER } from "../constants/constants"; import { GAME_MASTER } from "../constants/constants";
import { navyUnitDatabase } from "../units/navyunitdatabase"; import { navyUnitDatabase } from "../unit/navyunitdatabase";
import { CoalitionArea } from "../map/coalitionarea"; import { CoalitionArea } from "../map/coalitionarea";
export class MapContextMenu extends ContextMenu { export class MapContextMenu extends ContextMenu {

View File

@@ -1,5 +1,5 @@
import { Map } from "./map/map" import { Map } from "./map/map"
import { UnitsManager } from "./units/unitsmanager"; import { UnitsManager } from "./unit/unitsmanager";
import { UnitInfoPanel } from "./panels/unitinfopanel"; import { UnitInfoPanel } from "./panels/unitinfopanel";
import { ConnectionStatusPanel } from "./panels/connectionstatuspanel"; import { ConnectionStatusPanel } from "./panels/connectionstatuspanel";
import { MissionHandler } from "./mission/missionhandler"; import { MissionHandler } from "./mission/missionhandler";
@@ -18,10 +18,12 @@ import { HotgroupPanel } from "./panels/hotgrouppanel";
import { SVGInjector } from "@tanem/svg-injector"; import { SVGInjector } from "@tanem/svg-injector";
import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants"; import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants";
import { ServerStatusPanel } from "./panels/serverstatuspanel"; import { ServerStatusPanel } from "./panels/serverstatuspanel";
import { WeaponsManager } from "./weapon/weaponsmanager";
var map: Map; var map: Map;
var unitsManager: UnitsManager; var unitsManager: UnitsManager;
var weaponsManager: WeaponsManager;
var missionHandler: MissionHandler; var missionHandler: MissionHandler;
var aic: AIC; var aic: AIC;
@@ -48,6 +50,7 @@ function setup() {
/* Initialize base functionalitites */ /* Initialize base functionalitites */
unitsManager = new UnitsManager(); unitsManager = new UnitsManager();
weaponsManager = new WeaponsManager();
map = new Map('map-container'); map = new Map('map-container');
missionHandler = new MissionHandler(); missionHandler = new MissionHandler();
@@ -222,6 +225,11 @@ export function getUnitsManager() {
return unitsManager; return unitsManager;
} }
export function getWeaponsManager() {
return weaponsManager;
}
export function getMissionHandler() { export function getMissionHandler() {
return missionHandler; return missionHandler;
} }

View File

@@ -6,13 +6,13 @@ import { UnitContextMenu } from "../controls/unitcontextmenu";
import { AirbaseContextMenu } from "../controls/airbasecontextmenu"; import { AirbaseContextMenu } from "../controls/airbasecontextmenu";
import { Dropdown } from "../controls/dropdown"; import { Dropdown } from "../controls/dropdown";
import { Airbase } from "../mission/airbase"; import { Airbase } from "../mission/airbase";
import { Unit } from "../units/unit"; import { Unit } from "../unit/unit";
import { bearing, createCheckboxOption } from "../other/utils"; import { bearing, createCheckboxOption } from "../other/utils";
import { DestinationPreviewMarker } from "./destinationpreviewmarker"; import { DestinationPreviewMarker } from "./destinationpreviewmarker";
import { TemporaryUnitMarker } from "./temporaryunitmarker"; import { TemporaryUnitMarker } from "./temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap"; import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector' import { SVGInjector } from '@tanem/svg-injector'
import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, FIRE_AT_AREA, MOVE_UNIT, CARPET_BOMBING, BOMBING, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS } from "../constants/constants"; import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, FIRE_AT_AREA, MOVE_UNIT, CARPET_BOMBING, BOMBING, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes } from "../constants/constants";
import { TargetMarker } from "./targetmarker"; import { TargetMarker } from "./targetmarker";
import { CoalitionArea } from "./coalitionarea"; import { CoalitionArea } from "./coalitionarea";
import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu"; import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu";
@@ -120,7 +120,7 @@ export class Map extends L.Map {
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => { document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
const el = ev.detail._element; const el = ev.detail._element;
el?.classList.toggle("off"); el?.classList.toggle("off");
getUnitsManager().setHiddenType(ev.detail.type, !el?.classList.contains("off")); ev.detail.types.forEach((type: string) => getUnitsManager().setHiddenType(type, !el?.classList.contains("off")));
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
}); });
@@ -150,7 +150,7 @@ export class Map extends L.Map {
/* Option buttons */ /* Option buttons */
this.#optionButtons["visibility"] = visibilityControls.map((option: string, index: number) => { this.#optionButtons["visibility"] = visibilityControls.map((option: string, index: number) => {
return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, visibilityControlsTootlips[index], "toggleUnitVisibility", `{"type": "${option}"}`); return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, visibilityControlsTootlips[index], "toggleUnitVisibility", `{"types": "${visibilityControlsTypes[index]}"}`);
}); });
document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]); document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]);
@@ -616,7 +616,7 @@ export class Map extends L.Map {
#showDestinationCursors() { #showDestinationCursors() {
const singleCursor = !this.#shiftKey; const singleCursor = !this.#shiftKey;
const selectedUnitsCount = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length; const selectedUnitsCount = getUnitsManager().getSelectedUnits({ excludeHumans: false, onlyOnePerGroup: true }).length;
if (selectedUnitsCount > 0) { if (selectedUnitsCount > 0) {
if (singleCursor && this.#destinationPreviewCursors.length != 1) { if (singleCursor && this.#destinationPreviewCursors.length != 1) {
this.#hideDestinationCursors(); this.#hideDestinationCursors();

View File

@@ -1,22 +1,22 @@
import { LatLng } from "leaflet"; import { LatLng } from "leaflet";
import { getInfoPopup, getMap, getUnitsManager } from ".."; import { getInfoPopup, getMap } from "..";
import { Airbase } from "./airbase"; import { Airbase } from "./airbase";
import { Bullseye } from "./bullseye"; import { Bullseye } from "./bullseye";
import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "../constants/constants"; import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
import { setCommandModeOptions } from "../server/server"; import { refreshAll, setCommandModeOptions } from "../server/server";
import { Dropdown } from "../controls/dropdown"; import { Dropdown } from "../controls/dropdown";
import { groundUnitDatabase } from "../units/groundunitdatabase"; import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { createCheckboxOption, getCheckboxOptions } from "../other/utils"; import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
import { aircraftDatabase } from "../units/aircraftdatabase"; import { aircraftDatabase } from "../unit/aircraftdatabase";
import { helicopterDatabase } from "../units/helicopterdatabase"; import { helicopterDatabase } from "../unit/helicopterdatabase";
import { navyUnitDatabase } from "../units/navyunitdatabase"; import { navyUnitDatabase } from "../unit/navyunitdatabase";
export class MissionHandler { export class MissionHandler {
#bullseyes: { [name: string]: Bullseye } = {}; #bullseyes: { [name: string]: Bullseye } = {};
#airbases: { [name: string]: Airbase } = {}; #airbases: { [name: string]: Airbase } = {};
#theatre: string = ""; #theatre: string = "";
#dateAndTime: DateAndTime = {date: {Year: 0, Month: 0, Day: 0}, time: {h: 0, m: 0, s: 0}, startTime: 0, elapsedTime: 0}; #dateAndTime: DateAndTime = {date: {Year: 0, Month: 0, Day: 0}, time: {h: 0, m: 0, s: 0}, startTime: 0, elapsedTime: 0};
#commandModeOptions: CommandModeOptions = {commandMode: "Hide all", restrictSpawns: false, restrictToCoalition: false, setupTime: Infinity, spawnPoints: {red: Infinity, blue: Infinity}, eras: []}; #commandModeOptions: CommandModeOptions = {commandMode: NONE, restrictSpawns: false, restrictToCoalition: false, setupTime: Infinity, spawnPoints: {red: Infinity, blue: Infinity}, eras: []};
#remainingSetupTime: number = 0; #remainingSetupTime: number = 0;
#spentSpawnPoint: number = 0; #spentSpawnPoint: number = 0;
#commandModeDialog: HTMLElement; #commandModeDialog: HTMLElement;
@@ -183,6 +183,16 @@ export class MissionHandler {
} }
#setcommandModeOptions(commandModeOptions: CommandModeOptions) { #setcommandModeOptions(commandModeOptions: CommandModeOptions) {
/* Refresh all the data if we have exited the NONE state */
var requestRefresh = false;
if (this.#commandModeOptions.commandMode === NONE && commandModeOptions.commandMode !== NONE)
requestRefresh = true;
/* Refresh the page if we have lost Game Master priviledges */
if (this.#commandModeOptions.commandMode === GAME_MASTER && commandModeOptions.commandMode !== GAME_MASTER)
location.reload();
/* Check if any option has changed */
var commandModeOptionsChanged = (!commandModeOptions.eras.every((value: string, idx: number) => {return value === this.getCommandModeOptions().eras[idx]}) || var commandModeOptionsChanged = (!commandModeOptions.eras.every((value: string, idx: number) => {return value === this.getCommandModeOptions().eras[idx]}) ||
commandModeOptions.spawnPoints.red !== this.getCommandModeOptions().spawnPoints.red || commandModeOptions.spawnPoints.red !== this.getCommandModeOptions().spawnPoints.red ||
commandModeOptions.spawnPoints.blue !== this.getCommandModeOptions().spawnPoints.blue || commandModeOptions.spawnPoints.blue !== this.getCommandModeOptions().spawnPoints.blue ||
@@ -195,7 +205,6 @@ export class MissionHandler {
if (commandModeOptionsChanged) { if (commandModeOptionsChanged) {
document.dispatchEvent(new CustomEvent("commandModeOptionsChanged", { detail: this })); document.dispatchEvent(new CustomEvent("commandModeOptionsChanged", { detail: this }));
document.getElementById("command-mode-toolbar")?.classList.remove("hide"); document.getElementById("command-mode-toolbar")?.classList.remove("hide");
const el = document.getElementById("command-mode"); const el = document.getElementById("command-mode");
if (el) { if (el) {
@@ -206,6 +215,9 @@ export class MissionHandler {
document.querySelector("#spawn-points-container")?.classList.toggle("hide", this.getCommandModeOptions().commandMode === GAME_MASTER || !this.getCommandModeOptions().restrictSpawns); document.querySelector("#spawn-points-container")?.classList.toggle("hide", this.getCommandModeOptions().commandMode === GAME_MASTER || !this.getCommandModeOptions().restrictSpawns);
document.querySelector("#command-mode-settings-button")?.classList.toggle("hide", this.getCommandModeOptions().commandMode !== GAME_MASTER); document.querySelector("#command-mode-settings-button")?.classList.toggle("hide", this.getCommandModeOptions().commandMode !== GAME_MASTER);
if (requestRefresh)
refreshAll();
} }
#onAirbaseClick(e: any) { #onAirbaseClick(e: any) {

View File

@@ -1,9 +1,9 @@
import { LatLng, Point, Polygon } from "leaflet"; import { LatLng, Point, Polygon } from "leaflet";
import * as turf from "@turf/turf"; import * as turf from "@turf/turf";
import { UnitDatabase } from "../units/unitdatabase"; import { UnitDatabase } from "../unit/unitdatabase";
import { aircraftDatabase } from "../units/aircraftdatabase"; import { aircraftDatabase } from "../unit/aircraftdatabase";
import { helicopterDatabase } from "../units/helicopterdatabase"; import { helicopterDatabase } from "../unit/helicopterdatabase";
import { groundUnitDatabase } from "../units/groundunitdatabase"; import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { Buffer } from "buffer"; import { Buffer } from "buffer";
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants"; import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
import { Dropdown } from "../controls/dropdown"; import { Dropdown } from "../controls/dropdown";

View File

@@ -1,5 +1,5 @@
import { getUnitsManager } from ".."; import { getUnitsManager } from "..";
import { Unit } from "../units/unit"; import { Unit } from "../unit/unit";
import { Panel } from "./panel"; import { Panel } from "./panel";
export class HotgroupPanel extends Panel { export class HotgroupPanel extends Panel {

View File

@@ -1,7 +1,7 @@
import { Icon, LatLng, Marker, Polyline } from "leaflet"; import { Icon, LatLng, Marker, Polyline } from "leaflet";
import { getMap, getMissionHandler, getUnitsManager } from ".."; import { getMap, getMissionHandler, getUnitsManager } from "..";
import { distance, bearing, zeroAppend, mToNm, nmToFt } from "../other/utils"; import { distance, bearing, zeroAppend, mToNm, nmToFt } from "../other/utils";
import { Unit } from "../units/unit"; import { Unit } from "../unit/unit";
import { Panel } from "./panel"; import { Panel } from "./panel";
import formatcoords from "formatcoords"; import formatcoords from "formatcoords";

View File

@@ -2,8 +2,8 @@ import { SVGInjector } from "@tanem/svg-injector";
import { getUnitsManager } from ".."; import { getUnitsManager } from "..";
import { Dropdown } from "../controls/dropdown"; import { Dropdown } from "../controls/dropdown";
import { Slider } from "../controls/slider"; import { Slider } from "../controls/slider";
import { aircraftDatabase } from "../units/aircraftdatabase"; import { aircraftDatabase } from "../unit/aircraftdatabase";
import { Unit } from "../units/unit"; import { Unit } from "../unit/unit";
import { Panel } from "./panel"; import { Panel } from "./panel";
import { Switch } from "../controls/switch"; import { Switch } from "../controls/switch";
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants"; import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants";
@@ -22,6 +22,8 @@ export class UnitControlPanel extends Panel {
#radioCallsignDropdown: Dropdown; #radioCallsignDropdown: Dropdown;
#optionButtons: { [key: string]: HTMLButtonElement[] } = {} #optionButtons: { [key: string]: HTMLButtonElement[] } = {}
#advancedSettingsDialog: HTMLElement; #advancedSettingsDialog: HTMLElement;
#units: Unit[] = [];
#selectedUnitsTypes: string[] = [];
constructor(ID: string) { constructor(ID: string) {
super(ID); super(ID);
@@ -96,9 +98,11 @@ export class UnitControlPanel extends Panel {
} }
addButtons() { addButtons() {
var units = getUnitsManager().getSelectedUnits(); this.#units = getUnitsManager().getSelectedUnits();
if (units.length < 20) { this.#selectedUnitsTypes = getUnitsManager().getSelectedUnitsTypes();
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => {
if (this.#units.length < 20) {
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...this.#units.map((unit: Unit, index: number) => {
var button = document.createElement("button"); var button = document.createElement("button");
var callsign = unit.getUnitName() || ""; var callsign = unit.getUnitName() || "";
var label = unit.getDatabase()?.getByName(unit.getName())?.label || unit.getName(); var label = unit.getDatabase()?.getByName(unit.getName())?.label || unit.getName();
@@ -125,37 +129,34 @@ export class UnitControlPanel extends Panel {
update() { update() {
if (this.getVisible()){ if (this.getVisible()){
const element = this.getElement(); const element = this.getElement();
const units = getUnitsManager().getSelectedUnits(); if (element != null && this.#units.length > 0) {
const selectedUnitsTypes = getUnitsManager().getSelectedUnitsTypes();
if (element != null && units.length > 0) {
/* Toggle visibility of control elements */ /* Toggle visibility of control elements */
element.toggleAttribute("data-show-categories-tooltip", selectedUnitsTypes.length > 1); element.toggleAttribute("data-show-categories-tooltip", this.#selectedUnitsTypes.length > 1);
element.toggleAttribute("data-show-speed-slider", selectedUnitsTypes.length == 1); element.toggleAttribute("data-show-speed-slider", this.#selectedUnitsTypes.length == 1);
element.toggleAttribute("data-show-altitude-slider", selectedUnitsTypes.length == 1 && (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter"))); element.toggleAttribute("data-show-altitude-slider", this.#selectedUnitsTypes.length == 1 && (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")));
element.toggleAttribute("data-show-roe", true); element.toggleAttribute("data-show-roe", true);
element.toggleAttribute("data-show-threat", (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")) && !(selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit"))); element.toggleAttribute("data-show-threat", (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")) && !(this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-emissions-countermeasures", (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")) && !(selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit"))); element.toggleAttribute("data-show-emissions-countermeasures", (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")) && !(this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-on-off", (selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit")) && !(selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter"))); element.toggleAttribute("data-show-on-off", (this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")) && !(this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")));
element.toggleAttribute("data-show-follow-roads", (selectedUnitsTypes.length == 1 && selectedUnitsTypes.includes("GroundUnit"))); element.toggleAttribute("data-show-follow-roads", (this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit")));
element.toggleAttribute("data-show-advanced-settings-button", units.length == 1); element.toggleAttribute("data-show-advanced-settings-button", this.#units.length == 1);
/* Flight controls */ if (this.#selectedUnitsTypes.length == 1) {
var desiredAltitude = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()}); /* Flight controls */
var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()}); var desiredAltitude = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()});
var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()}); var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()});
var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()}); var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()});
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()}); var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()});
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()}); var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()});
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()});
if (selectedUnitsTypes.length == 1) {
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false); this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false);
this.#speedTypeSwitch.setValue(desiredSpeedType != undefined? desiredSpeedType == "GS": undefined, false); this.#speedTypeSwitch.setValue(desiredSpeedType != undefined? desiredSpeedType == "GS": undefined, false);
this.#speedSlider.setMinMax(minSpeedValues[selectedUnitsTypes[0]], maxSpeedValues[selectedUnitsTypes[0]]); this.#speedSlider.setMinMax(minSpeedValues[this.#selectedUnitsTypes[0]], maxSpeedValues[this.#selectedUnitsTypes[0]]);
this.#altitudeSlider.setMinMax(minAltitudeValues[selectedUnitsTypes[0]], maxAltitudeValues[selectedUnitsTypes[0]]); this.#altitudeSlider.setMinMax(minAltitudeValues[this.#selectedUnitsTypes[0]], maxAltitudeValues[this.#selectedUnitsTypes[0]]);
this.#speedSlider.setIncrement(speedIncrements[selectedUnitsTypes[0]]); this.#speedSlider.setIncrement(speedIncrements[this.#selectedUnitsTypes[0]]);
this.#altitudeSlider.setIncrement(altitudeIncrements[selectedUnitsTypes[0]]); this.#altitudeSlider.setIncrement(altitudeIncrements[this.#selectedUnitsTypes[0]]);
this.#speedSlider.setActive(desiredSpeed != undefined); this.#speedSlider.setActive(desiredSpeed != undefined);
if (desiredSpeed != undefined) if (desiredSpeed != undefined)
@@ -172,15 +173,15 @@ export class UnitControlPanel extends Panel {
/* Option buttons */ /* Option buttons */
this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => { this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getROE() === button.value)) button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getROE() === button.value))
}); });
this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => { this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getReactionToThreat() === button.value)) button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getReactionToThreat() === button.value))
}); });
this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => { this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getEmissionsCountermeasures() === button.value)) button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getEmissionsCountermeasures() === button.value))
}); });
this.#onOffSwitch.setValue(onOff, false); this.#onOffSwitch.setValue(onOff, false);

View File

@@ -1,8 +1,8 @@
import { getUnitsManager } from ".."; import { getUnitsManager } from "..";
import { Ammo } from "../@types/unit"; import { Ammo } from "../@types/unit";
import { ConvertDDToDMS, rad2deg } from "../other/utils"; import { ConvertDDToDMS, rad2deg } from "../other/utils";
import { aircraftDatabase } from "../units/aircraftdatabase"; import { aircraftDatabase } from "../unit/aircraftdatabase";
import { Unit } from "../units/unit"; import { Unit } from "../unit/unit";
import { Panel } from "./panel"; import { Panel } from "./panel";
export class UnitInfoPanel extends Panel { export class UnitInfoPanel extends Panel {

View File

@@ -13,6 +13,10 @@ export class DataExtractor {
this.#decoder = new TextDecoder("utf-8"); this.#decoder = new TextDecoder("utf-8");
} }
setSeekPosition(seekPosition: number) {
this.#seekPosition = seekPosition;
}
getSeekPosition() { getSeekPosition() {
return this.#seekPosition; return this.#seekPosition;
} }
@@ -67,7 +71,13 @@ export class DataExtractor {
var stringBuffer = this.#buffer.slice(this.#seekPosition, this.#seekPosition + length); var stringBuffer = this.#buffer.slice(this.#seekPosition, this.#seekPosition + length);
var view = new Int8Array(stringBuffer); var view = new Int8Array(stringBuffer);
var stringLength = length; var stringLength = length;
view.forEach((value: number, idx: number) => { if (value === 0) stringLength = idx; }); view.every((value: number, idx: number) => {
if (value === 0) {
stringLength = idx;
return false;
} else
return true;
});
const value = this.#decoder.decode(stringBuffer); const value = this.#decoder.decode(stringBuffer);
this.#seekPosition += length; this.#seekPosition += length;
return value.substring(0, stringLength).trim(); return value.substring(0, stringLength).trim();

View File

@@ -1,7 +1,7 @@
import { LatLng } from 'leaflet'; import { LatLng } from 'leaflet';
import { getConnectionStatusPanel, getInfoPopup, getLogPanel, getMissionHandler, getServerStatusPanel, getUnitDataTable, getUnitsManager, setLoginStatus } from '..'; import { getConnectionStatusPanel, getInfoPopup, getLogPanel, getMissionHandler, getServerStatusPanel, getUnitsManager, getWeaponsManager, setLoginStatus } from '..';
import { GeneralSettings, Radio, TACAN } from '../@types/unit'; import { GeneralSettings, Radio, TACAN } from '../@types/unit';
import { ROEs, emissionsCountermeasures, reactionsToThreat } from '../constants/constants'; import { NONE, ROEs, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
var connected: boolean = false; var connected: boolean = false;
var paused: boolean = false; var paused: boolean = false;
@@ -9,6 +9,7 @@ var paused: boolean = false;
var REST_ADDRESS = "http://localhost:30000/olympus"; var REST_ADDRESS = "http://localhost:30000/olympus";
var DEMO_ADDRESS = window.location.href + "demo"; var DEMO_ADDRESS = window.location.href + "demo";
const UNITS_URI = "units"; const UNITS_URI = "units";
const WEAPONS_URI = "weapons";
const LOGS_URI = "logs"; const LOGS_URI = "logs";
const AIRBASES_URI = "airbases"; const AIRBASES_URI = "airbases";
const BULLSEYE_URI = "bullseyes"; const BULLSEYE_URI = "bullseyes";
@@ -19,6 +20,13 @@ var password = "";
var sessionHash: string | null = null; var sessionHash: string | null = null;
var lastUpdateTimes: {[key: string]: number} = {} var lastUpdateTimes: {[key: string]: number} = {}
lastUpdateTimes[UNITS_URI] = Date.now();
lastUpdateTimes[WEAPONS_URI] = Date.now();
lastUpdateTimes[LOGS_URI] = Date.now();
lastUpdateTimes[AIRBASES_URI] = Date.now();
lastUpdateTimes[BULLSEYE_URI] = Date.now();
lastUpdateTimes[MISSION_URI] = Date.now();
var demoEnabled = false; var demoEnabled = false;
export function toggleDemoEnabled() { export function toggleDemoEnabled() {
@@ -128,6 +136,10 @@ export function getUnits(callback: CallableFunction, refresh: boolean = false) {
GET(callback, UNITS_URI, { time: refresh ? 0 : lastUpdateTimes[UNITS_URI] }, 'arraybuffer'); GET(callback, UNITS_URI, { time: refresh ? 0 : lastUpdateTimes[UNITS_URI] }, 'arraybuffer');
} }
export function getWeapons(callback: CallableFunction, refresh: boolean = false) {
GET(callback, WEAPONS_URI, { time: refresh ? 0 : lastUpdateTimes[WEAPONS_URI] }, 'arraybuffer');
}
export function addDestination(ID: number, path: any) { export function addDestination(ID: number, path: any) {
var command = { "ID": ID, "path": path } var command = { "ID": ID, "path": path }
var data = { "setPath": command } var data = { "setPath": command }
@@ -334,26 +346,6 @@ export function setCommandModeOptions(restrictSpawns: boolean, restrictToCoaliti
} }
export function startUpdate() { export function startUpdate() {
window.setInterval(() => {
if (!getPaused()) {
getAirbases((data: AirbasesData) => {
checkSessionHash(data.sessionHash);
getMissionHandler()?.updateAirbases(data);
return data.time;
});
}
}, 10000);
window.setInterval(() => {
if (!getPaused()){
getBullseye((data: BullseyesData) => {
checkSessionHash(data.sessionHash);
getMissionHandler()?.updateBullseyes(data);
return data.time;
});
}
}, 10000);
window.setInterval(() => { window.setInterval(() => {
if (!getPaused()) { if (!getPaused()) {
getMission((data: MissionData) => { getMission((data: MissionData) => {
@@ -365,7 +357,27 @@ export function startUpdate() {
}, 1000); }, 1000);
window.setInterval(() => { window.setInterval(() => {
if (!getPaused()) { if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) {
getAirbases((data: AirbasesData) => {
checkSessionHash(data.sessionHash);
getMissionHandler()?.updateAirbases(data);
return data.time;
});
}
}, 10000);
window.setInterval(() => {
if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE){
getBullseye((data: BullseyesData) => {
checkSessionHash(data.sessionHash);
getMissionHandler()?.updateBullseyes(data);
return data.time;
});
}
}, 10000);
window.setInterval(() => {
if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) {
getLogs((data: any) => { getLogs((data: any) => {
checkSessionHash(data.sessionHash); checkSessionHash(data.sessionHash);
getLogPanel().appendLogs(data.logs) getLogPanel().appendLogs(data.logs)
@@ -375,7 +387,7 @@ export function startUpdate() {
}, 1000); }, 1000);
window.setInterval(() => { window.setInterval(() => {
if (!getPaused()) { if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) {
getUnits((buffer: ArrayBuffer) => { getUnits((buffer: ArrayBuffer) => {
var time = getUnitsManager()?.update(buffer); var time = getUnitsManager()?.update(buffer);
return time; return time;
@@ -384,7 +396,16 @@ export function startUpdate() {
}, 250); }, 250);
window.setInterval(() => { window.setInterval(() => {
if (!getPaused()) { if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) {
getWeapons((buffer: ArrayBuffer) => {
var time = getWeaponsManager()?.update(buffer);
return time;
}, false);
}
}, 250);
window.setInterval(() => {
if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) {
getUnits((buffer: ArrayBuffer) => { getUnits((buffer: ArrayBuffer) => {
var time = getUnitsManager()?.update(buffer); var time = getUnitsManager()?.update(buffer);
return time; return time;
@@ -392,19 +413,50 @@ export function startUpdate() {
getConnectionStatusPanel()?.update(getConnected()); getConnectionStatusPanel()?.update(getConnected());
} }
}, 5000); }, 5000);
window.setInterval(() => {
if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) {
getWeapons((buffer: ArrayBuffer) => {
var time = getWeaponsManager()?.update(buffer);
return time;
}, false);
}
}, 5000);
} }
export function requestUpdate() { export function refreshAll() {
/* Main update rate = 250ms is minimum time, equal to server update time. */ getAirbases((data: AirbasesData) => {
if (!getPaused()) { checkSessionHash(data.sessionHash);
getUnits((buffer: ArrayBuffer) => { return getUnitsManager()?.update(buffer); }, false); getMissionHandler()?.updateAirbases(data);
} return data.time;
window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000); });
getBullseye((data: BullseyesData) => {
checkSessionHash(data.sessionHash);
getMissionHandler()?.updateBullseyes(data);
return data.time;
});
getLogs((data: any) => {
checkSessionHash(data.sessionHash);
getLogPanel().appendLogs(data.logs)
return data.time;
});
getWeapons((buffer: ArrayBuffer) => {
var time = getWeaponsManager()?.update(buffer);
return time;
}, true);
getUnits((buffer: ArrayBuffer) => {
var time = getUnitsManager()?.update(buffer);
return time;
}, true);
} }
export function checkSessionHash(newSessionHash: string) { export function checkSessionHash(newSessionHash: string) {
if (sessionHash != null) { if (sessionHash != null) {
if (newSessionHash != sessionHash) if (newSessionHash !== sessionHash)
location.reload(); location.reload();
} }
else else

View File

@@ -7,8 +7,8 @@ import { SVGInjector } from '@tanem/svg-injector';
import { UnitDatabase } from './unitdatabase'; import { UnitDatabase } from './unitdatabase';
import { TargetMarker } from '../map/targetmarker'; import { TargetMarker } from '../map/targetmarker';
import { BOMBING, CARPET_BOMBING, DLINK, DataIndexes, FIRE_AT_AREA, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_CONTACT_LINES, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants'; import { BOMBING, CARPET_BOMBING, DLINK, DataIndexes, FIRE_AT_AREA, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_CONTACT_LINES, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, UnitIconOptions } from '../@types/unit'; import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, ObjectIconOptions } from '../@types/unit';
import { DataExtractor } from './dataextractor'; import { DataExtractor } from '../server/dataextractor';
import { groundUnitDatabase } from './groundunitdatabase'; import { groundUnitDatabase } from './groundunitdatabase';
import { navyUnitDatabase } from './navyunitdatabase'; import { navyUnitDatabase } from './navyunitdatabase';
@@ -135,8 +135,6 @@ export class Unit extends CustomMarker {
if (type === "GroundUnit") return GroundUnit; if (type === "GroundUnit") return GroundUnit;
if (type === "Aircraft") return Aircraft; if (type === "Aircraft") return Aircraft;
if (type === "Helicopter") return Helicopter; if (type === "Helicopter") return Helicopter;
if (type === "Missile") return Missile;
if (type === "Bomb") return Bomb;
if (type === "NavyUnit") return NavyUnit; if (type === "NavyUnit") return NavyUnit;
} }
@@ -192,7 +190,7 @@ export class Unit extends CustomMarker {
case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break; case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break;
case DataIndexes.human: this.#human = dataExtractor.extractBool(); break; case DataIndexes.human: this.#human = dataExtractor.extractBool(); break;
case DataIndexes.controlled: this.#controlled = dataExtractor.extractBool(); updateMarker = true; break; case DataIndexes.controlled: this.#controlled = dataExtractor.extractBool(); updateMarker = true; break;
case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); break; case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); updateMarker = true; break;
case DataIndexes.country: this.#country = dataExtractor.extractUInt8(); break; case DataIndexes.country: this.#country = dataExtractor.extractUInt8(); break;
case DataIndexes.name: this.#name = dataExtractor.extractString(); break; case DataIndexes.name: this.#name = dataExtractor.extractString(); break;
case DataIndexes.unitName: this.#unitName = dataExtractor.extractString(); break; case DataIndexes.unitName: this.#unitName = dataExtractor.extractString(); break;
@@ -297,7 +295,7 @@ export class Unit extends CustomMarker {
return getUnitDatabaseByCategory(this.getMarkerCategory()); return getUnitDatabaseByCategory(this.getMarkerCategory());
} }
getIconOptions(): UnitIconOptions { getIconOptions(): ObjectIconOptions {
// Default values, overloaded by child classes if needed // Default values, overloaded by child classes if needed
return { return {
showState: false, showState: false,
@@ -408,9 +406,11 @@ export class Unit extends CustomMarker {
el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`); el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`);
el.setAttribute("data-coalition", this.#coalition); el.setAttribute("data-coalition", this.#coalition);
var iconOptions = this.getIconOptions();
// Generate and append elements depending on active options // Generate and append elements depending on active options
// Velocity vector // Velocity vector
if (this.getIconOptions().showVvi) { if (iconOptions.showVvi) {
var vvi = document.createElement("div"); var vvi = document.createElement("div");
vvi.classList.add("unit-vvi"); vvi.classList.add("unit-vvi");
vvi.toggleAttribute("data-rotate-to-heading"); vvi.toggleAttribute("data-rotate-to-heading");
@@ -418,7 +418,7 @@ export class Unit extends CustomMarker {
} }
// Hotgroup indicator // Hotgroup indicator
if (this.getIconOptions().showHotgroup) { if (iconOptions.showHotgroup) {
var hotgroup = document.createElement("div"); var hotgroup = document.createElement("div");
hotgroup.classList.add("unit-hotgroup"); hotgroup.classList.add("unit-hotgroup");
var hotgroupId = document.createElement("div"); var hotgroupId = document.createElement("div");
@@ -428,26 +428,26 @@ export class Unit extends CustomMarker {
} }
// Main icon // Main icon
if (this.getIconOptions().showUnitIcon) { if (iconOptions.showUnitIcon) {
var unitIcon = document.createElement("div"); var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon"); unitIcon.classList.add("unit-icon");
var img = document.createElement("img"); var img = document.createElement("img");
img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`; img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`;
img.onload = () => SVGInjector(img); img.onload = () => SVGInjector(img);
unitIcon.appendChild(img); unitIcon.appendChild(img);
unitIcon.toggleAttribute("data-rotate-to-heading", this.getIconOptions().rotateToHeading); unitIcon.toggleAttribute("data-rotate-to-heading", iconOptions.rotateToHeading);
el.append(unitIcon); el.append(unitIcon);
} }
// State icon // State icon
if (this.getIconOptions().showState) { if (iconOptions.showState) {
var state = document.createElement("div"); var state = document.createElement("div");
state.classList.add("unit-state"); state.classList.add("unit-state");
el.appendChild(state); el.appendChild(state);
} }
// Short label // Short label
if (this.getIconOptions().showShortLabel) { if (iconOptions.showShortLabel) {
var shortLabel = document.createElement("div"); var shortLabel = document.createElement("div");
shortLabel.classList.add("unit-short-label"); shortLabel.classList.add("unit-short-label");
shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.#name)?.shortLabel || ""; shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.#name)?.shortLabel || "";
@@ -455,7 +455,7 @@ export class Unit extends CustomMarker {
} }
// Fuel indicator // Fuel indicator
if (this.getIconOptions().showFuel) { if (iconOptions.showFuel) {
var fuelIndicator = document.createElement("div"); var fuelIndicator = document.createElement("div");
fuelIndicator.classList.add("unit-fuel"); fuelIndicator.classList.add("unit-fuel");
var fuelLevel = document.createElement("div"); var fuelLevel = document.createElement("div");
@@ -465,7 +465,7 @@ export class Unit extends CustomMarker {
} }
// Ammo indicator // Ammo indicator
if (this.getIconOptions().showAmmo) { if (iconOptions.showAmmo) {
var ammoIndicator = document.createElement("div"); var ammoIndicator = document.createElement("div");
ammoIndicator.classList.add("unit-ammo"); ammoIndicator.classList.add("unit-ammo");
for (let i = 0; i <= 3; i++) for (let i = 0; i <= 3; i++)
@@ -474,7 +474,7 @@ export class Unit extends CustomMarker {
} }
// Unit summary // Unit summary
if (this.getIconOptions().showSummary) { if (iconOptions.showSummary) {
var summary = document.createElement("div"); var summary = document.createElement("div");
summary.classList.add("unit-summary"); summary.classList.add("unit-summary");
var callsign = document.createElement("div"); var callsign = document.createElement("div");
@@ -484,7 +484,7 @@ export class Unit extends CustomMarker {
altitude.classList.add("unit-altitude"); altitude.classList.add("unit-altitude");
var speed = document.createElement("div"); var speed = document.createElement("div");
speed.classList.add("unit-speed"); speed.classList.add("unit-speed");
if (this.getIconOptions().showCallsign) summary.appendChild(callsign); if (iconOptions.showCallsign) summary.appendChild(callsign);
summary.appendChild(altitude); summary.appendChild(altitude);
summary.appendChild(speed); summary.appendChild(speed);
el.appendChild(summary); el.appendChild(summary);
@@ -500,8 +500,8 @@ export class Unit extends CustomMarker {
(this.#controlled == false && hiddenUnits.includes("dcs")) || (this.#controlled == false && hiddenUnits.includes("dcs")) ||
(hiddenUnits.includes(this.getMarkerCategory())) || (hiddenUnits.includes(this.getMarkerCategory())) ||
(hiddenUnits.includes(this.#coalition)) || (hiddenUnits.includes(this.#coalition)) ||
(!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0) || (!this.belongsToCommandedCoalition() && (this.#detectionMethods.length == 0 || (this.#detectionMethods.length == 1 && this.#detectionMethods[0] === RWR))) ||
(getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getMap().getZoom() < 13)) && (getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getMap().getZoom() < 13 && (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) &&
!(this.getSelected()); !(this.getSelected());
this.setHidden(hidden || !this.#alive); this.setHidden(hidden || !this.#alive);
@@ -748,6 +748,11 @@ export class Unit extends CustomMarker {
options["fire-at-area"] = { text: "Fire at area", tooltip: "Fire at a large area" }; options["fire-at-area"] = { text: "Fire at area", tooltip: "Fire at a large area" };
} }
if ((selectedUnits.length === 0 && this.getCategory() == "NavyUnit") || selectedUnitTypes.length === 1 && ["NavyUnit"].includes(selectedUnitTypes[0])) {
if (selectedUnits.concat([this]).every((unit: Unit) => { return ["Cruiser", "Destroyer", "Frigate"].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) 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." }; options["group"] = { text: "Create group", tooltip: "Create a group from the selected units." };
@@ -976,7 +981,7 @@ export class Unit extends CustomMarker {
if (getMap().getVisibilityOptions()[SHOW_CONTACT_LINES]) { if (getMap().getVisibilityOptions()[SHOW_CONTACT_LINES]) {
for (let index in this.#contacts) { for (let index in this.#contacts) {
var contactData = this.#contacts[index]; var contactData = this.#contacts[index];
var contact = getUnitsManager().getUnitByID(contactData.ID) var contact = getUnitsManager().getUnitByID(contactData.ID);
if (contact != null && contact.getAlive()) { if (contact != null && contact.getAlive()) {
var startLatLng = new LatLng(this.#position.lat, this.#position.lng); var startLatLng = new LatLng(this.#position.lat, this.#position.lng);
var endLatLng: LatLng; var endLatLng: LatLng;
@@ -1149,74 +1154,3 @@ export class NavyUnit extends Unit {
return blueprint?.type? blueprint.type: ""; return blueprint?.type? blueprint.type: "";
} }
} }
export class Weapon extends Unit {
constructor(ID: number) {
super(ID);
this.setSelectable(false);
}
}
export class Missile extends Weapon {
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Missile";
}
getMarkerCategory() {
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)
};
}
}
export class Bomb extends Weapon {
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Bomb";
}
getMarkerCategory() {
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,14 +1,14 @@
import { LatLng, LatLngBounds } from "leaflet"; import { LatLng, LatLngBounds } from "leaflet";
import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitsManager } from ".."; import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitsManager } from "..";
import { Unit } from "./unit"; import { Unit } from "./unit";
import { cloneUnit, deleteUnit, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server"; import { cloneUnit, deleteUnit, refreshAll, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server";
import { bearingAndDistanceToLatLng, deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils"; import { bearingAndDistanceToLatLng, deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils";
import { CoalitionArea } from "../map/coalitionarea"; import { CoalitionArea } from "../map/coalitionarea";
import { groundUnitDatabase } from "./groundunitdatabase"; import { groundUnitDatabase } from "./groundunitdatabase";
import { DataIndexes, GAME_MASTER, IADSDensities, IDLE, MOVE_UNIT, NONE } from "../constants/constants"; import { DataIndexes, GAME_MASTER, IADSDensities, IDLE, MOVE_UNIT, NONE } from "../constants/constants";
import { DataExtractor } from "./dataextractor"; import { DataExtractor } from "../server/dataextractor";
import { Contact } from "../@types/unit"; import { Contact } from "../@types/unit";
import { citiesDatabase } from "./citiesdatabase"; import { citiesDatabase } from "./citiesDatabase";
import { aircraftDatabase } from "./aircraftdatabase"; import { aircraftDatabase } from "./aircraftdatabase";
import { helicopterDatabase } from "./helicopterdatabase"; import { helicopterDatabase } from "./helicopterdatabase";
import { navyUnitDatabase } from "./navyunitdatabase"; import { navyUnitDatabase } from "./navyunitdatabase";
@@ -35,7 +35,7 @@ export class UnitsManager {
document.addEventListener('exportToFile', () => this.exportToFile()); document.addEventListener('exportToFile', () => this.exportToFile());
document.addEventListener('importFromFile', () => this.importFromFile()); document.addEventListener('importFromFile', () => this.importFromFile());
document.addEventListener('contactsUpdated', (e: CustomEvent) => {this.#requestDetectionUpdate = true}); document.addEventListener('contactsUpdated', (e: CustomEvent) => {this.#requestDetectionUpdate = true});
document.addEventListener("commandModeOptionsChanged", () => {Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility())}); document.addEventListener('commandModeOptionsChanged', () => {Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility())});
} }
getSelectableAircraft() { getSelectableAircraft() {
@@ -73,14 +73,9 @@ export class UnitsManager {
} }
} }
removeUnit(ID: number) {
}
update(buffer: ArrayBuffer) { update(buffer: ArrayBuffer) {
var dataExtractor = new DataExtractor(buffer); var dataExtractor = new DataExtractor(buffer);
var updateTime = Number(dataExtractor.extractUInt64()); var updateTime = Number(dataExtractor.extractUInt64());
var requestRefresh = false;
while (dataExtractor.getSeekPosition() < buffer.byteLength) { while (dataExtractor.getSeekPosition() < buffer.byteLength) {
const ID = dataExtractor.extractUInt32(); const ID = dataExtractor.extractUInt32();
if (!(ID in this.#units)) { if (!(ID in this.#units)) {
@@ -90,18 +85,40 @@ export class UnitsManager {
this.addUnit(ID, category); this.addUnit(ID, category);
} }
else { else {
requestRefresh = true; /* Inconsistent data, we need to wait for a refresh */
return updateTime;
} }
} }
this.#units[ID]?.setData(dataExtractor); this.#units[ID]?.setData(dataExtractor);
} }
if (this.#requestDetectionUpdate && getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) { if (this.#requestDetectionUpdate && getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) {
/* Create a dictionary of empty detection methods arrays */
var detectionMethods: {[key: string]: number[]} = {};
for (let ID in this.#units) { for (let ID in this.#units) {
var unit = this.#units[ID]; const unit = this.#units[ID];
if (!unit.belongsToCommandedCoalition()) detectionMethods[ID] = [];
unit.setDetectionMethods(this.getUnitDetectedMethods(unit));
} }
/* Fill the array with the detection methods */
for (let ID in this.#units) {
const unit = this.#units[ID];
if (unit.getAlive() && unit.belongsToCommandedCoalition()){
const contacts = unit.getContacts();
contacts.forEach((contact: Contact) => {
const contactID = contact.ID;
if (!(detectionMethods[contactID].includes(contact.detectionMethod)))
detectionMethods[contactID]?.push(contact.detectionMethod);
})
}
}
/* Set the detection methods for every unit */
for (let ID in this.#units) {
const unit = this.#units[ID];
unit.setDetectionMethods(detectionMethods[ID]);
}
this.#requestDetectionUpdate = false; this.#requestDetectionUpdate = false;
} }
@@ -654,9 +671,10 @@ export class UnitsManager {
var contents = e.target.result; var contents = e.target.result;
var groups = JSON.parse(contents); var groups = JSON.parse(contents);
for (let groupName in groups) { for (let groupName in groups) {
if (groupName !== "" && groups[groupName].length > 0 && groups[groupName].every((unit: any) => {return unit.category == "GroundUnit";})) { if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: any) => {return unit.category == "GroundUnit";}) || groups[groupName].every((unit: any) => {return unit.category == "NavyUnit";}))) {
var units = groups[groupName].map((unit: any) => {return {unitType: unit.name, location: unit.position}}); var aliveUnits = groups[groupName].filter((unit: any) => {return unit.alive});
getUnitsManager().spawnUnits("GroundUnit", units, groups[groupName][0].coalition, true); var units = aliveUnits.map((unit: any) => {return {unitType: unit.name, location: unit.position}});
getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true);
} }
} }
}; };
@@ -668,28 +686,28 @@ export class UnitsManager {
spawnUnits(category: string, units: any, coalition: string = "blue", immediate: boolean = true, airbase: string = "") { spawnUnits(category: string, units: any, coalition: string = "blue", immediate: boolean = true, airbase: string = "") {
var spawnPoints = 0; var spawnPoints = 0;
if (category === "Aircraft") { if (category === "Aircraft") {
if (airbase == "" && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { if (airbase == "" && getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
getInfoPopup().setText("Aircrafts can be air spawned during the SETUP phase only"); getInfoPopup().setText("Aircrafts can be air spawned during the SETUP phase only");
return false; return false;
} }
spawnPoints = units.reduce((points: number, unit: any) => {return points + aircraftDatabase.getSpawnPointsByName(unit.unitType)}, 0); spawnPoints = units.reduce((points: number, unit: any) => {return points + aircraftDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnAircrafts(units, coalition, airbase, immediate, spawnPoints); spawnAircrafts(units, coalition, airbase, immediate, spawnPoints);
} else if (category === "Helicopter") { } else if (category === "Helicopter") {
if (airbase == "" && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { if (airbase == "" && getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
getInfoPopup().setText("Helicopters can be air spawned during the SETUP phase only"); getInfoPopup().setText("Helicopters can be air spawned during the SETUP phase only");
return false; return false;
} }
spawnPoints = units.reduce((points: number, unit: any) => {return points + helicopterDatabase.getSpawnPointsByName(unit.unitType)}, 0); spawnPoints = units.reduce((points: number, unit: any) => {return points + helicopterDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnHelicopters(units, coalition, airbase, immediate, spawnPoints); spawnHelicopters(units, coalition, airbase, immediate, spawnPoints);
} else if (category === "GroundUnit") { } else if (category === "GroundUnit") {
if (getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { if (getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
getInfoPopup().setText("Ground units can be spawned during the SETUP phase only"); getInfoPopup().setText("Ground units can be spawned during the SETUP phase only");
return false; return false;
} }
spawnPoints = units.reduce((points: number, unit: any) => {return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0); spawnPoints = units.reduce((points: number, unit: any) => {return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnGroundUnits(units, coalition, immediate, spawnPoints); spawnGroundUnits(units, coalition, immediate, spawnPoints);
} else if (category === "NavyUnit") { } else if (category === "NavyUnit") {
if (getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { if (getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
getInfoPopup().setText("Navy units can be spawned during the SETUP phase only"); getInfoPopup().setText("Navy units can be spawned during the SETUP phase only");
return false; return false;
} }
@@ -718,9 +736,9 @@ export class UnitsManager {
#onUnitSelection(unit: Unit) { #onUnitSelection(unit: Unit) {
if (this.getSelectedUnits().length > 0) { if (this.getSelectedUnits().length > 0) {
getMap().setState(MOVE_UNIT);
/* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */ /* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */
if (!this.#selectionEventDisabled) { if (!this.#selectionEventDisabled) {
getMap().setState(MOVE_UNIT);
window.setTimeout(() => { window.setTimeout(() => {
document.dispatchEvent(new CustomEvent("unitsSelection", { detail: this.getSelectedUnits() })); document.dispatchEvent(new CustomEvent("unitsSelection", { detail: this.getSelectedUnits() }));
this.#selectionEventDisabled = false; this.#selectionEventDisabled = false;

323
client/src/weapon/weapon.ts Normal file
View File

@@ -0,0 +1,323 @@
import { LatLng, DivIcon, Map } from 'leaflet';
import { getMap, getMissionHandler, getUnitsManager } from '..';
import { enumToCoalition, mToFt, msToKnots, rad2deg } from '../other/utils';
import { CustomMarker } from '../map/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { DLINK, DataIndexes, GAME_MASTER, IRST, OPTIC, RADAR, VISUAL } from '../constants/constants';
import { ObjectIconOptions } from '../@types/unit';
import { DataExtractor } from '../server/dataextractor';
export class Weapon extends CustomMarker {
ID: number;
#alive: boolean = false;
#coalition: string = "neutral";
#name: string = "";
#position: LatLng = new LatLng(0, 0, 0);
#speed: number = 0;
#heading: number = 0;
#hidden: boolean = false;
#detectionMethods: number[] = [];
getAlive() {return this.#alive};
getCoalition() {return this.#coalition};
getName() {return this.#name};
getPosition() {return this.#position};
getSpeed() {return this.#speed};
getHeading() {return this.#heading};
static getConstructor(type: string) {
if (type === "Missile") return Missile;
if (type === "Bomb") return Bomb;
}
constructor(ID: number) {
super(new LatLng(0, 0), { riseOnHover: true, keyboard: false });
this.ID = ID;
/* Deselect units if they are hidden */
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { !this.getHidden() }, 300);
});
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { !this.getHidden() }, 300);
});
document.addEventListener("mapVisibilityOptionsChanged", (ev: CustomEventInit) => {
this.#updateMarker();
});
}
getCategory() {
// Overloaded by child classes
return "";
}
/********************** Unit data *************************/
setData(dataExtractor: DataExtractor) {
var updateMarker = !getMap().hasLayer(this);
var datumIndex = 0;
while (datumIndex != DataIndexes.endOfData) {
datumIndex = dataExtractor.extractUInt8();
switch (datumIndex) {
case DataIndexes.category: dataExtractor.extractString(); break;
case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break;
case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); break;
case DataIndexes.name: this.#name = dataExtractor.extractString(); break;
case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break;
case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break;
case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break;
}
}
if (updateMarker)
this.#updateMarker();
}
getData() {
return {
category: this.getCategory(),
ID: this.ID,
alive: this.#alive,
coalition: this.#coalition,
name: this.#name,
position: this.#position,
speed: this.#speed,
heading: this.#heading
}
}
getMarkerCategory(): string {
return "";
}
getIconOptions(): ObjectIconOptions {
// Default values, overloaded by child classes if needed
return {
showState: false,
showVvi: false,
showHotgroup: false,
showUnitIcon: true,
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: true,
showCallsign: true,
rotateToHeading: false
}
}
setAlive(newAlive: boolean) {
this.#alive = newAlive;
}
belongsToCommandedCoalition() {
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER && getMissionHandler().getCommandedCoalition() !== this.#coalition)
return false;
return true;
}
getType() {
return "";
}
/********************** Icon *************************/
createIcon(): void {
/* Set the icon */
var icon = new DivIcon({
className: 'leaflet-unit-icon',
iconAnchor: [25, 25],
iconSize: [50, 50],
});
this.setIcon(icon);
var el = document.createElement("div");
el.classList.add("unit");
el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`);
el.setAttribute("data-coalition", this.#coalition);
// Generate and append elements depending on active options
// Velocity vector
if (this.getIconOptions().showVvi) {
var vvi = document.createElement("div");
vvi.classList.add("unit-vvi");
vvi.toggleAttribute("data-rotate-to-heading");
el.append(vvi);
}
// Main icon
if (this.getIconOptions().showUnitIcon) {
var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon");
var img = document.createElement("img");
img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`;
img.onload = () => SVGInjector(img);
unitIcon.appendChild(img);
unitIcon.toggleAttribute("data-rotate-to-heading", this.getIconOptions().rotateToHeading);
el.append(unitIcon);
}
this.getElement()?.appendChild(el);
}
/********************** Visibility *************************/
updateVisibility() {
const hiddenUnits = getUnitsManager().getHiddenTypes();
var hidden = (hiddenUnits.includes(this.getMarkerCategory())) ||
(hiddenUnits.includes(this.#coalition)) ||
(!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0);
this.setHidden(hidden || !this.#alive);
}
setHidden(hidden: boolean) {
this.#hidden = hidden;
/* Add the marker if not present */
if (!getMap().hasLayer(this) && !this.getHidden()) {
if (getMap().isZooming())
this.once("zoomend", () => {this.addTo(getMap())})
else
this.addTo(getMap());
}
/* Hide the marker if necessary*/
if (getMap().hasLayer(this) && this.getHidden()) {
getMap().removeLayer(this);
}
}
getHidden() {
return this.#hidden;
}
setDetectionMethods(newDetectionMethods: number[]) {
if (!this.belongsToCommandedCoalition()) {
/* Check if the detection methods of this unit have changed */
if (this.#detectionMethods.length !== newDetectionMethods.length || this.getDetectionMethods().some(value => !newDetectionMethods.includes(value))) {
/* Force a redraw of the unit to reflect the new status of the detection methods */
this.setHidden(true);
this.#detectionMethods = newDetectionMethods;
this.updateVisibility();
}
}
}
getDetectionMethods() {
return this.#detectionMethods;
}
/***********************************************/
onAdd(map: Map): this {
super.onAdd(map);
/* If this is the first time adding this unit to the map, remove the temporary marker */
getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng));
return this;
}
#updateMarker() {
this.updateVisibility();
/* Draw the marker */
if (!this.getHidden()) {
if (this.getLatLng().lat !== this.#position.lat || this.getLatLng().lng !== this.#position.lng) {
this.setLatLng(new LatLng(this.#position.lat, this.#position.lng));
}
var element = this.getElement();
if (element != null) {
/* Draw the velocity vector */
element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.#speed / 5}px;`);
/* Set dead/alive flag */
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive);
/* Set altitude and speed */
if (element.querySelector(".unit-altitude"))
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.#position.alt as number) / 100));
if (element.querySelector(".unit-speed"))
(<HTMLElement>element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.#speed))) + "GS";
/* Rotate elements according to heading */
element.querySelectorAll("[data-rotate-to-heading]").forEach(el => {
const headingDeg = rad2deg(this.#heading);
let currentStyle = el.getAttribute("style") || "";
el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`);
});
}
/* Set vertical offset for altitude stacking */
var pos = getMap().latLngToLayerPoint(this.getLatLng()).round();
this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y);
}
}
}
export class Missile extends Weapon {
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Missile";
}
getMarkerCategory() {
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)
};
}
}
export class Bomb extends Weapon {
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Bomb";
}
getMarkerCategory() {
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

@@ -0,0 +1,84 @@
import { getMissionHandler, getUnitsManager } from "..";
import { Weapon } from "./weapon";
import { DataIndexes, GAME_MASTER } from "../constants/constants";
import { DataExtractor } from "../server/dataextractor";
import { Contact } from "../@types/unit";
export class WeaponsManager {
#weapons: { [ID: number]: Weapon };
#requestDetectionUpdate: boolean = false;
constructor() {
this.#weapons = {};
document.addEventListener("commandModeOptionsChanged", () => {Object.values(this.#weapons).forEach((weapon: Weapon) => weapon.updateVisibility())});
document.addEventListener('contactsUpdated', (e: CustomEvent) => {this.#requestDetectionUpdate = true});
}
getWeapons() {
return this.#weapons;
}
getWeaponByID(ID: number) {
if (ID in this.#weapons)
return this.#weapons[ID];
else
return null;
}
addWeapon(ID: number, category: string) {
if (category){
/* The name of the weapon category is exactly the same as the constructor name */
var constructor = Weapon.getConstructor(category);
if (constructor != undefined) {
this.#weapons[ID] = new constructor(ID);
}
}
}
update(buffer: ArrayBuffer) {
var dataExtractor = new DataExtractor(buffer);
var updateTime = Number(dataExtractor.extractUInt64());
while (dataExtractor.getSeekPosition() < buffer.byteLength) {
const ID = dataExtractor.extractUInt32();
if (!(ID in this.#weapons)) {
const datumIndex = dataExtractor.extractUInt8();
if (datumIndex == DataIndexes.category) {
const category = dataExtractor.extractString();
this.addWeapon(ID, category);
}
else {
/* Inconsistent data, we need to wait for a refresh */
return updateTime;
}
}
this.#weapons[ID]?.setData(dataExtractor);
}
if (this.#requestDetectionUpdate && getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) {
for (let ID in this.#weapons) {
var weapon = this.#weapons[ID];
if (!weapon.belongsToCommandedCoalition())
weapon.setDetectionMethods(this.getWeaponDetectedMethods(weapon));
}
this.#requestDetectionUpdate = false;
}
return updateTime;
}
getWeaponDetectedMethods(weapon: Weapon) {
var detectionMethods: number[] = [];
var units = getUnitsManager().getUnits();
for (let idx in units) {
if (units[idx].getAlive() && units[idx].getIsLeader() && units[idx].getCoalition() !== "neutral" && units[idx].getCoalition() != weapon.getCoalition())
{
units[idx].getContacts().forEach((contact: Contact) => {
if (contact.ID == weapon.ID && !detectionMethods.includes(contact.detectionMethod))
detectionMethods.push(contact.detectionMethod);
});
}
}
return detectionMethods;
}
}

View File

@@ -3,7 +3,7 @@
<div id="app-summary"> <div id="app-summary">
<h2>DCS Olympus</h2> <h2>DCS Olympus</h2>
<h4>Dynamic Unit Command</h4> <h4>Dynamic Unit Command</h4>
<div class="app-version">Version <span class="app-version-number">v0.4.0-alpha</span></div> <div class="app-version">Version <span class="app-version-number">v0.4.1-alpha</span></div>
</div> </div>
<div id="authentication-form"> <div id="authentication-form">

View File

@@ -6,7 +6,7 @@
<div class="ol-select-options"> <div class="ol-select-options">
<div id="toolbar-summary"> <div id="toolbar-summary">
<h3>DCS Olympus</h3> <h3>DCS Olympus</h3>
<div class="accent-green app-version-number">version v0.4.0-alpha</div> <div class="accent-green app-version-number">version v0.4.1-alpha</div>
</div> </div>
<div> <div>
<a href="https://www.discord.com" target="_blank">Discord</a> <a href="https://www.discord.com" target="_blank">Discord</a>

View File

@@ -1,5 +1,5 @@
#define nwjsFolder "C:\Users\dpass\Documents\nwjs\" #define nwjsFolder "C:\Users\dpass\Documents\nwjs\"
#define version "v0.4.0-alpha" #define version "v0.4.1-alpha"
[Setup] [Setup]
AppName=DCS Olympus AppName=DCS Olympus
@@ -8,7 +8,7 @@ DefaultDirName={usersavedgames}\DCS.openbeta
DefaultGroupName=DCSOlympus DefaultGroupName=DCSOlympus
OutputBaseFilename=DCSOlympus_{#version} OutputBaseFilename=DCSOlympus_{#version}
UninstallFilesDir={app}\Mods\Services\Olympus UninstallFilesDir={app}\Mods\Services\Olympus
;SetupIconFile="..\img\olympus.ico" SetupIconFile="..\img\olympus.ico"
[Tasks] [Tasks]
; NOTE: The following entry contains English phrases ("Create a desktop icon" and "Additional icons"). You are free to translate them into another language if required. ; NOTE: The following entry contains English phrases ("Create a desktop icon" and "Additional icons"). You are free to translate them into another language if required.
@@ -16,8 +16,6 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{
[Files] [Files]
; NOTE: Don't use "Flags: ignoreversion" on any shared system files ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
;Source: "..\scripts\OlympusExport.lua"; DestDir: "{app}\Scripts"; Flags: ignoreversion
;Source: "..\scripts\OlympusPatcher.exe"; DestDir: "{app}\Scripts"; Flags: ignoreversion
Source: "..\scripts\OlympusHook.lua"; DestDir: "{app}\Scripts\Hooks"; Flags: ignoreversion Source: "..\scripts\OlympusHook.lua"; DestDir: "{app}\Scripts\Hooks"; Flags: ignoreversion
Source: "..\olympus.json"; DestDir: "{app}\Mods\Services\Olympus"; Flags: onlyifdoesntexist Source: "..\olympus.json"; DestDir: "{app}\Mods\Services\Olympus"; Flags: onlyifdoesntexist
Source: "..\scripts\OlympusCommand.lua"; DestDir: "{app}\Mods\Services\Olympus\Scripts"; Flags: ignoreversion Source: "..\scripts\OlympusCommand.lua"; DestDir: "{app}\Mods\Services\Olympus\Scripts"; Flags: ignoreversion
@@ -62,9 +60,3 @@ ChangesEnvironment=yes
[Icons] [Icons]
Name: "{userdesktop}\DCS Olympus Client"; Filename: "{app}\Mods\Services\Olympus\client\nw.exe"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus.ico" Name: "{userdesktop}\DCS Olympus Client"; Filename: "{app}\Mods\Services\Olympus\client\nw.exe"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus.ico"
;[Run]
;Filename: "{app}\Scripts\OlympusPatcher.exe"; Parameters: "-i"
;[UninstallRun]
;Filename: "{app}\Scripts\OlympusPatcher.exe"; Parameters: "-u"

View File

@@ -15,7 +15,7 @@ declare_plugin(self_ID,
shortName = "Olympus", shortName = "Olympus",
fileMenuName = "Olympus", fileMenuName = "Olympus",
version = "v0.4.0-alpha", version = "v0.4.1-alpha",
state = "installed", state = "installed",
developerName= "DCS Refugees 767 squadron", developerName= "DCS Refugees 767 squadron",
info = _("DCS Olympus is a mod for DCS World. It allows users to spawn, control, task, group, and remove units from a DCS World server using a real-time map interface, similarly to Real Time Strategy games. The user interface also provides useful informations units, like loadouts, fuel, tasking, and so on. In the future, more features for DCS World GCI and JTAC will be available."), info = _("DCS Olympus is a mod for DCS World. It allows users to spawn, control, task, group, and remove units from a DCS World server using a real-time map interface, similarly to Real Time Strategy games. The user interface also provides useful informations units, like loadouts, fuel, tasking, and so on. In the future, more features for DCS World GCI and JTAC will be available."),

4
package.bat Normal file
View File

@@ -0,0 +1,4 @@
cd client
call npm prune --production
cd ..
call "C:\Program Files (x86)\Inno Setup 6\iscc.exe" "installer\olympus.iss"

View File

@@ -1,20 +1,26 @@
local version = "v0.4.0-alpha" local version = "v0.4.1-alpha"
local debug = false local debug = false
Olympus.unitCounter = 1
Olympus.payloadRegistry = {}
Olympus.groupIndex = 0
Olympus.groupStep = 5
Olympus.OlympusDLL = nil Olympus.OlympusDLL = nil
Olympus.DLLsloaded = false Olympus.DLLsloaded = false
Olympus.OlympusModPath = os.getenv('DCSOLYMPUS_PATH')..'\\bin\\' Olympus.OlympusModPath = os.getenv('DCSOLYMPUS_PATH')..'\\bin\\'
Olympus.log = mist.Logger:new("Olympus", 'info') Olympus.log = mist.Logger:new("Olympus", 'info')
Olympus.unitCounter = 1
Olympus.payloadRegistry = {}
Olympus.missionData = {} Olympus.missionData = {}
Olympus.unitsData = {} Olympus.unitsData = {}
Olympus.unitNames = {} Olympus.weaponsData = {}
Olympus.unitIndex = 0
Olympus.unitStep = 50
Olympus.units = {}
Olympus.weaponIndex = 0
Olympus.weaponStep = 50
Olympus.weapons = {}
Olympus.missionStartTime = DCS.getRealTime() Olympus.missionStartTime = DCS.getRealTime()
@@ -43,35 +49,36 @@ end
-- Gets a unit class reference from a given ObjectID (the ID used by Olympus for unit referencing) -- Gets a unit class reference from a given ObjectID (the ID used by Olympus for unit referencing)
function Olympus.getUnitByID(ID) function Olympus.getUnitByID(ID)
for name, table in pairs(mist.DBs.unitsByName) do return Olympus.units[ID];
local unit = Unit.getByName(name)
if unit and unit["getObjectID"] and unit:getObjectID() == ID then
return unit
end
end
return nil
end end
function Olympus.getCountryIDByCoalition(coalition) function Olympus.getCountryIDByCoalition(coalitionString)
local countryID = 0 for countryName, countryId in pairs(country.id) do
if coalition == 'red' then if coalition.getCountryCoalition(countryId) == Olympus.getCoalitionIDByCoalition(coalitionString) then
countryID = country.id.CJTF_RED return countryId
elseif coalition == 'blue' then end
countryID = country.id.CJTF_BLUE
else
countryID = country.id.UN_PEACEKEEPERS
end end
return countryID return 0
end
function Olympus.getCoalitionIDByCoalition(coalitionString)
local coalitionID = 0
if coalitionString == "red" then
coalitionID = 1
elseif coalitionString == "blue" then
coalitionID = 2
end
return coalitionID
end end
function Olympus.getCoalitionByCoalitionID(coalitionID) function Olympus.getCoalitionByCoalitionID(coalitionID)
local coalition = "neutral" local coalitionString = "neutral"
if coalitionID == 1 then if coalitionID == 1 then
coalition = "red" coalitionString = "red"
elseif coalitionID == 2 then elseif coalitionID == 2 then
coalition = "blue" coalitionString = "blue"
end end
return coalition return coalitionString
end end
-- Builds a valid task depending on the provided options -- Builds a valid task depending on the provided options
@@ -219,7 +226,6 @@ function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedT
if category == "Aircraft" then if category == "Aircraft" then
local startPoint = mist.getLeadPos(group) local startPoint = mist.getLeadPos(group)
local endPoint = coord.LLtoLO(lat, lng, 0) local endPoint = coord.LLtoLO(lat, lng, 0)
if altitudeType == "AGL" then if altitudeType == "AGL" then
altitude = land.getHeight({x = endPoint.x, y = endPoint.z}) + altitude altitude = land.getHeight({x = endPoint.x, y = endPoint.z}) + altitude
end end
@@ -305,11 +311,15 @@ function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedT
end end
Olympus.debug("Olympus.move executed successfully on Helicopter", 2) Olympus.debug("Olympus.move executed successfully on Helicopter", 2)
elseif category == "GroundUnit" then elseif category == "GroundUnit" then
local startPoint = mist.getLeadPos(group)
local endPoint = coord.LLtoLO(lat, lng, 0)
local bearing = math.atan2(endPoint.z - startPoint.z, endPoint.x - startPoint.x)
vars = vars =
{ {
group = group, group = group,
point = coord.LLtoLO(lat, lng, 0), point = endPoint,
heading = 0, heading = bearing,
speed = speed speed = speed
} }
@@ -323,11 +333,15 @@ function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedT
mist.groupToRandomPoint(vars) mist.groupToRandomPoint(vars)
Olympus.debug("Olympus.move executed succesfully on GroundUnit", 2) Olympus.debug("Olympus.move executed succesfully on GroundUnit", 2)
elseif category == "NavyUnit" then elseif category == "NavyUnit" then
local startPoint = mist.getLeadPos(group)
local endPoint = coord.LLtoLO(lat, lng, 0)
local bearing = math.atan2(endPoint.z - startPoint.z, endPoint.x - startPoint.x)
vars = vars =
{ {
group = group, group = group,
point = coord.LLtoLO(lat, lng, 0), point = endPoint,
heading = 0, heading = bearing,
speed = speed speed = speed
} }
mist.groupToRandomPoint(vars) mist.groupToRandomPoint(vars)
@@ -542,7 +556,7 @@ function Olympus.generateNavyUnitsTable(units)
["y"] = spawnLocation.z + value.dy, ["y"] = spawnLocation.z + value.dy,
["heading"] = 0, ["heading"] = 0,
["skill"] = "High", ["skill"] = "High",
["name"] = "NavyUnit-" .. Olympus.unitCounter .. "-" .. #unitTable + 1, ["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitTable + 1,
["transportable"] = { ["randomTransportable"] = false } ["transportable"] = { ["randomTransportable"] = false }
} }
end end
@@ -554,7 +568,7 @@ function Olympus.generateNavyUnitsTable(units)
["y"] = spawnLocation.z, ["y"] = spawnLocation.z,
["heading"] = 0, ["heading"] = 0,
["skill"] = "High", ["skill"] = "High",
["name"] = "NavyUnit-" .. Olympus.unitCounter .. "-" .. #unitTable + 1, ["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitTable + 1,
["transportable"] = { ["randomTransportable"] = false } ["transportable"] = { ["randomTransportable"] = false }
} }
end end
@@ -568,10 +582,9 @@ function Olympus.clone(ID, lat, lng, category)
Olympus.debug("Olympus.clone " .. ID .. ", " .. category, 2) Olympus.debug("Olympus.clone " .. ID .. ", " .. category, 2)
local unit = Olympus.getUnitByID(ID) local unit = Olympus.getUnitByID(ID)
if unit then if unit then
local coalition = Olympus.getCoalitionByCoalitionID(unit:getCoalition())
-- TODO: understand category in this script -- TODO: understand category in this script
local spawnTable = { local spawnTable = {
coalition = coalition, coalition = Olympus.getCoalitionByCoalitionID(unit:getCoalition()),
category = category, category = category,
units = { units = {
[1] = { [1] = {
@@ -651,6 +664,240 @@ function Olympus.setOnOff(groupName, onOff)
end end
end end
function Olympus.setUnitsData(arg, time)
-- Units data
local units = {}
local startIndex = Olympus.unitIndex
local endIndex = startIndex + Olympus.unitStep
local index = 0
for ID, unit in pairs(Olympus.units) do
index = index + 1
if index > startIndex then
if unit ~= nil then
local table = {}
-- 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
else
units[ID] = {isAlive = false}
Olympus.units[ID] = nil
end
-- If the category is handled by Olympus, get the data
if table["category"] ~= nil 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["coalitionID"] = unit:getCoalition()
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["isAlive"] = unit:isExist() and unit:isActive() and unit:getLife() >= 1
local group = unit:getGroup()
if group ~= nil then
local controller = group:getController()
if controller ~= nil then
-- Get the targets detected by the unit controller
local contacts = {}
local unitController = unit:getController()
if unitController ~= nil then
for det, enum in pairs(Controller.Detection) do
local controllerTargets = unitController:getDetectedTargets(enum)
for i, target in ipairs(controllerTargets) do
if target ~= nil and target.object ~= nil and target.visible then
target["detectionMethod"] = det
contacts[#contacts + 1] = target
end
end
end
end
table["country"] = unit:getCountry()
table["unitName"] = unit:getName()
table["groupName"] = group:getName()
table["isHuman"] = (unit:getPlayerName() ~= nil)
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["contacts"] = contacts
units[ID] = table
end
end
end
else
units[ID] = {isAlive = false}
Olympus.units[ID] = nil
end
end
if index >= endIndex then
break
end
end
if index ~= endIndex then
Olympus.unitIndex = 0
else
Olympus.unitIndex = endIndex
end
-- Assemble unitsData table
Olympus.unitsData["units"] = units
Olympus.OlympusDLL.setUnitsData()
return time + 0.05
end
function Olympus.setWeaponsData(arg, time)
-- Weapons data
local weapons = {}
local startIndex = Olympus.weaponIndex
local endIndex = startIndex + Olympus.weaponStep
local index = 0
for ID, weapon in pairs(Olympus.weapons) do
index = index + 1
if index > startIndex then
if weapon ~= nil then
local table = {}
-- Get the object category in Olympus name
local objectCategory = weapon:getCategory()
if objectCategory == Object.Category.WEAPON then
if weapon:getDesc().category == Weapon.Category.MISSILE then
table["category"] = "Missile"
elseif weapon:getDesc().category == Weapon.Category.ROCKET then
table["category"] = "Missile"
elseif weapon:getDesc().category == Weapon.Category.BOMB then
table["category"] = "Bomb"
end
else
weapons[ID] = {isAlive = false}
Olympus.weapons[ID] = nil
end
-- If the category is handled by Olympus, get the data
if table["category"] ~= nil then
-- Compute weapon position and heading
local lat, lng, alt = coord.LOtoLL(weapon:getPoint())
local position = weapon:getPosition()
local heading = math.atan2( position.x.z, position.x.x )
-- Fill the data table
table["name"] = weapon:getTypeName()
table["coalitionID"] = weapon:getCoalition()
table["position"] = {}
table["position"]["lat"] = lat
table["position"]["lng"] = lng
table["position"]["alt"] = alt
table["speed"] = mist.vec.mag(weapon:getVelocity())
table["heading"] = heading
table["isAlive"] = weapon:isExist()
weapons[ID] = table
end
else
weapons[ID] = {isAlive = false}
Olympus.weapons[ID] = nil
end
end
if index >= endIndex then
break
end
end
if index ~= endIndex then
Olympus.weaponIndex = 0
else
Olympus.weaponIndex = endIndex
end
-- Assemble weaponsData table
Olympus.weaponsData["weapons"] = weapons
Olympus.OlympusDLL.setWeaponsData()
return time + 0.25
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 = {}
for i = 1, #base do
local info = {}
local latitude, longitude, altitude = coord.LOtoLL(Airbase.getPoint(base[i]))
info["callsign"] = Airbase.getCallsign(base[i])
info["coalition"] = Olympus.getCoalitionByCoalitionID(Airbase.getCoalition(base[i]))
info["latitude"] = latitude
info["longitude"] = longitude
if Airbase.getUnit(base[i]) then
info["unitId"] = Airbase.getUnit(base[i]):getID()
end
airbases[i] = info
end
-- Mission
local mission = {}
mission.theatre = env.mission.theatre
mission.dateAndTime = {
["elapsedTime"] = DCS.getRealTime() - Olympus.missionStartTime,
["time"] = mist.time.getDHMS(timer.getAbsTime()),
["startTime"] = env.mission.start_time,
["date"] = env.mission.date
}
-- Assemble table
Olympus.missionData["bullseyes"] = bullseyes
Olympus.missionData["airbases"] = airbases
Olympus.missionData["mission"] = mission
Olympus.OlympusDLL.setMissionData()
return time + 1
end
function Olympus.initializeUnits()
if mist and mist.DBs and mist.DBs.MEunitsById then
for id, unitTable in pairs(mist.DBs.MEunitsById) do
local unit = Unit.getByName(unitTable["unitName"])
if unit then
Olympus.units[unit["id_"]] = unit
end
end
Olympus.notify("Olympus units table initialized", 2)
else
Olympus.debug("MIST DBs not ready", 2)
timer.scheduleFunction(Olympus.initializeUnits, {}, timer.getTime() + 1)
end
end
function Olympus.serializeTable(val, name, skipnewlines, depth) function Olympus.serializeTable(val, name, skipnewlines, depth)
skipnewlines = skipnewlines or false skipnewlines = skipnewlines or false
depth = depth or 0 depth = depth or 0
@@ -712,171 +959,10 @@ function Olympus.hasKey(tab, key)
return false return false
end end
function Olympus.setUnitsData(arg, time) ------------------------------------------------------------------------------------------------------
-- Units data -- Olympus startup script
local units = {} ------------------------------------------------------------------------------------------------------
local startIndex = Olympus.groupIndex
local endIndex = startIndex + Olympus.groupStep
local index = 0
if mist ~= nil and mist.DBs ~= nil and mist.DBs.groupsByName ~= nil then
for groupName, gp in pairs(mist.DBs.groupsByName) do
index = index + 1
if index > startIndex then
if groupName ~= nil then
local group = Group.getByName(groupName)
if group ~= nil then
-- 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["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
-- 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["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()
table["isAlive"] = (unit:getLife() > 1) and unit:isExist()
units[unit:getObjectID()] = table
Olympus.unitNames[unit:getObjectID()] = unit:getName() -- Used to find what units are dead, since they will not be in mist.DBs.groupsByName
end
end
end
end
end
if index >= endIndex then
break
end
end
if index ~= endIndex then
Olympus.groupIndex = 0
else
Olympus.groupIndex = endIndex
end
end
-- All the units that can't be retrieved by getByName are dead
for ID, name in pairs(Olympus.unitNames) do
local unit = Unit.getByName(name)
if unit == nil then
units[ID] = {isAlive = false}
Olympus.unitNames[ID] = nil
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 = {}
for i = 1, #base do
local info = {}
local latitude, longitude, altitude = coord.LOtoLL(Airbase.getPoint(base[i]))
info["callsign"] = Airbase.getCallsign(base[i])
info["coalition"] = Olympus.getCoalitionByCoalitionID(Airbase.getCoalition(base[i]))
info["latitude"] = latitude
info["longitude"] = longitude
if Airbase.getUnit(base[i]) then
info["unitId"] = Airbase.getUnit(base[i]):getID()
end
airbases[i] = info
end
-- Mission
local mission = {}
mission.theatre = env.mission.theatre
mission.dateAndTime = {
["elapsedTime"] = DCS.getRealTime() - Olympus.missionStartTime,
["time"] = mist.time.getDHMS(timer.getAbsTime()),
["startTime"] = env.mission.start_time,
["date"] = env.mission.date
}
-- Assemble table
Olympus.missionData["bullseyes"] = bullseyes
Olympus.missionData["airbases"] = airbases
Olympus.missionData["mission"] = mission
Olympus.OlympusDLL.setMissionData()
return time + 1
end
local OlympusName = 'Olympus ' .. version .. ' C++ module'; local OlympusName = 'Olympus ' .. version .. ' C++ module';
isOlympusModuleInitialized=true;
Olympus.DLLsloaded = Olympus.loadDLLs() Olympus.DLLsloaded = Olympus.loadDLLs()
if Olympus.DLLsloaded then if Olympus.DLLsloaded then
Olympus.notify(OlympusName..' successfully loaded.', 20) Olympus.notify(OlympusName..' successfully loaded.', 20)
@@ -884,7 +970,36 @@ else
Olympus.notify('Failed to load '..OlympusName, 20) Olympus.notify('Failed to load '..OlympusName, 20)
end end
-- Create the handler to detect new units
if handler ~= nil then
world.removeEventHandler(handler)
Olympus.debug("Olympus handler removed" , 2)
end
handler = {}
function handler:onEvent(event)
if event.id == 1 then
local weapon = event.weapon
if Olympus ~= nil and Olympus.weapons ~= nil then
Olympus.weapons[weapon["id_"]] = weapon
Olympus.debug("New weapon created " .. weapon["id_"], 2)
end
elseif event.id == 15 then
local unit = event.initiator
if Olympus ~= nil and Olympus.units ~= nil then
Olympus.units[unit["id_"]] = unit
Olympus.debug("New unit created " .. unit["id_"], 2)
end
end
end
world.addEventHandler(handler)
-- Start the periodic functions
timer.scheduleFunction(Olympus.setUnitsData, {}, timer.getTime() + 0.05) timer.scheduleFunction(Olympus.setUnitsData, {}, timer.getTime() + 0.05)
timer.scheduleFunction(Olympus.setWeaponsData, {}, timer.getTime() + 0.25)
timer.scheduleFunction(Olympus.setMissionData, {}, timer.getTime() + 1) timer.scheduleFunction(Olympus.setMissionData, {}, timer.getTime() + 1)
-- Initialize the ME units
Olympus.initializeUnits()
Olympus.notify("OlympusCommand script " .. version .. " loaded successfully", 2, true) Olympus.notify("OlympusCommand script " .. version .. " loaded successfully", 2, true)

View File

@@ -1,4 +1,4 @@
local version = 'v0.4.0-alpha' local version = 'v0.4.1-alpha'
Olympus = {} Olympus = {}
Olympus.OlympusDLL = nil Olympus.OlympusDLL = nil

View File

@@ -47,6 +47,7 @@
<ClInclude Include="include\unit.h" /> <ClInclude Include="include\unit.h" />
<ClInclude Include="include\unitsmanager.h" /> <ClInclude Include="include\unitsmanager.h" />
<ClInclude Include="include\weapon.h" /> <ClInclude Include="include\weapon.h" />
<ClInclude Include="include\weaponsmanager.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="src\aircraft.cpp" /> <ClCompile Include="src\aircraft.cpp" />
@@ -64,6 +65,7 @@
<ClCompile Include="src\unit.cpp" /> <ClCompile Include="src\unit.cpp" />
<ClCompile Include="src\unitsmanager.cpp" /> <ClCompile Include="src\unitsmanager.cpp" />
<ClCompile Include="src\weapon.cpp" /> <ClCompile Include="src\weapon.cpp" />
<ClCompile Include="src\weaponsmanager.cpp" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion> <VCProjectVersion>16.0</VCProjectVersion>

View File

@@ -51,6 +51,9 @@
<ClInclude Include="include\datatypes.h"> <ClInclude Include="include\datatypes.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="include\weaponsmanager.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="src\aircraft.cpp"> <ClCompile Include="src\aircraft.cpp">
@@ -98,5 +101,8 @@
<ClCompile Include="src\datatypes.cpp"> <ClCompile Include="src\datatypes.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="src\weaponsmanager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,6 +1,73 @@
#pragma once #pragma once
#include "framework.h" #include "framework.h"
namespace DataIndex {
enum DataIndexes {
startOfData = 0,
category,
alive,
human,
controlled,
coalition,
country,
name,
unitName,
groupName,
state,
task,
hasTask,
position,
speed,
heading,
isTanker,
isAWACS,
onOff,
followRoads,
fuel,
desiredSpeed,
desiredSpeedType,
desiredAltitude,
desiredAltitudeType,
leaderID,
formationOffset,
targetID,
targetPosition,
ROE,
reactionToThreat,
emissionsCountermeasures,
TACAN,
radio,
generalSettings,
ammo,
contacts,
activePath,
isLeader,
lastIndex,
endOfData = 255
};
}
namespace State
{
enum States
{
NONE = 0,
IDLE,
REACH_DESTINATION,
ATTACK,
FOLLOW,
LAND,
REFUEL,
AWACS,
TANKER,
BOMB_POINT,
CARPET_BOMB,
BOMB_BUILDING,
FIRE_AT_AREA
};
};
#pragma pack(push, 1) #pragma pack(push, 1)
namespace DataTypes { namespace DataTypes {
struct TACAN struct TACAN

View File

@@ -1,12 +1,20 @@
#pragma once #pragma once
#include "unit.h" #include "unit.h"
#define NAVY_DEST_DIST_THR 100
class NavyUnit : public Unit class NavyUnit : public Unit
{ {
public: public:
NavyUnit(json::value json, unsigned int ID); NavyUnit(json::value json, unsigned int ID);
virtual void AIloop();
virtual void setState(unsigned char newState);
virtual void setDefaults(bool force = false);
virtual void changeSpeed(string change); virtual void changeSpeed(string change);
virtual void setOnOff(bool newOnOff, bool force = false);
protected:
virtual void AIloop();
}; };

View File

@@ -13,72 +13,6 @@ using namespace std::chrono;
#define TASK_CHECK_INIT_VALUE 10 #define TASK_CHECK_INIT_VALUE 10
namespace DataIndex {
enum DataIndexes {
startOfData = 0,
category,
alive,
human,
controlled,
coalition,
country,
name,
unitName,
groupName,
state,
task,
hasTask,
position,
speed,
heading,
isTanker,
isAWACS,
onOff,
followRoads,
fuel,
desiredSpeed,
desiredSpeedType,
desiredAltitude,
desiredAltitudeType,
leaderID,
formationOffset,
targetID,
targetPosition,
ROE,
reactionToThreat,
emissionsCountermeasures,
TACAN,
radio,
generalSettings,
ammo,
contacts,
activePath,
isLeader,
lastIndex,
endOfData = 255
};
}
namespace State
{
enum States
{
NONE = 0,
IDLE,
REACH_DESTINATION,
ATTACK,
FOLLOW,
LAND,
REFUEL,
AWACS,
TANKER,
BOMB_POINT,
CARPET_BOMB,
BOMB_BUILDING,
FIRE_AT_AREA
};
};
class Unit class Unit
{ {
public: public:

View File

@@ -1,13 +1,107 @@
#pragma once #pragma once
#include "unit.h" #include "framework.h"
#include "utils.h"
#include "dcstools.h"
#include "luatools.h"
#include "measure.h"
#include "logger.h"
#include "commands.h"
#include "datatypes.h"
class Weapon : public Unit #include <chrono>
using namespace std::chrono;
class Weapon
{ {
public: public:
Weapon(json::value json, unsigned int ID); Weapon(json::value json, unsigned int ID);
~Weapon();
/********** Methods **********/
void initialize(json::value json);
void update(json::value json, double dt);
unsigned int getID() { return ID; }
void getData(stringstream& ss, unsigned long long time);
void triggerUpdate(unsigned char datumIndex);
bool hasFreshData(unsigned long long time);
bool checkFreshness(unsigned char datumIndex, unsigned long long time);
/********** Setters **********/
virtual void setCategory(string newValue) { updateValue(category, newValue, DataIndex::category); }
virtual void setAlive(bool newValue) { updateValue(alive, newValue, DataIndex::alive); }
virtual void setCoalition(unsigned char newValue) { updateValue(coalition, newValue, DataIndex::coalition); }
virtual void setName(string newValue) { updateValue(name, newValue, DataIndex::name); }
virtual void setPosition(Coords newValue) { updateValue(position, newValue, DataIndex::position); }
virtual void setSpeed(double newValue) { updateValue(speed, newValue, DataIndex::speed); }
virtual void setHeading(double newValue) { updateValue(heading, newValue, DataIndex::heading); }
/********** Getters **********/
virtual string getCategory() { return category; };
virtual bool getAlive() { return alive; }
virtual unsigned char getCoalition() { return coalition; }
virtual string getName() { return name; }
virtual Coords getPosition() { return position; }
virtual double getSpeed() { return speed; }
virtual double getHeading() { return heading; }
protected: protected:
/* Weapons are not controllable and have no AIloop */ unsigned int ID;
virtual void AIloop() {};
string category;
bool alive = false;
unsigned char coalition = NULL;
string name = "";
Coords position = Coords(NULL);
double speed = NULL;
double heading = NULL;
/********** Other **********/
map<unsigned char, unsigned long long> updateTimeMap;
/********** Private methods **********/
void appendString(stringstream& ss, const unsigned char& datumIndex, const string& datumValue) {
const unsigned short size = datumValue.size();
ss.write((const char*)&datumIndex, sizeof(unsigned char));
ss.write((const char*)&size, sizeof(unsigned short));
ss << datumValue;
}
/********** Template methods **********/
template <typename T>
void updateValue(T& value, T& newValue, unsigned char datumIndex)
{
if (newValue != value)
{
triggerUpdate(datumIndex);
value = newValue;
}
}
template <typename T>
void appendNumeric(stringstream& ss, const unsigned char& datumIndex, T& datumValue) {
ss.write((const char*)&datumIndex, sizeof(unsigned char));
ss.write((const char*)&datumValue, sizeof(T));
}
template <typename T>
void appendVector(stringstream& ss, const unsigned char& datumIndex, vector<T>& datumValue) {
const unsigned short size = datumValue.size();
ss.write((const char*)&datumIndex, sizeof(unsigned char));
ss.write((const char*)&size, sizeof(unsigned short));
for (auto& el : datumValue)
ss.write((const char*)&el, sizeof(T));
}
template <typename T>
void appendList(stringstream& ss, const unsigned char& datumIndex, list<T>& datumValue) {
const unsigned short size = datumValue.size();
ss.write((const char*)&datumIndex, sizeof(unsigned char));
ss.write((const char*)&size, sizeof(unsigned short));
for (auto& el : datumValue)
ss.write((const char*)&el, sizeof(T));
}
}; };
class Missile : public Weapon class Missile : public Weapon

View File

@@ -0,0 +1,21 @@
#pragma once
#include "framework.h"
#include "dcstools.h"
class Weapon;
class WeaponsManager
{
public:
WeaponsManager(lua_State* L);
~WeaponsManager();
map<unsigned int, Weapon*>& getWeapons() { return weapons; };
Weapon* getWeapon(unsigned int ID);
void update(json::value& missionData, double dt);
void getWeaponData(stringstream& ss, unsigned long long time);
private:
map<unsigned int, Weapon*> weapons;
};

View File

@@ -2,6 +2,7 @@
#include "logger.h" #include "logger.h"
#include "defines.h" #include "defines.h"
#include "unitsManager.h" #include "unitsManager.h"
#include "weaponsManager.h"
#include "server.h" #include "server.h"
#include "scheduler.h" #include "scheduler.h"
#include "scriptLoader.h" #include "scriptLoader.h"
@@ -9,11 +10,13 @@
#include <chrono> #include <chrono>
using namespace std::chrono; using namespace std::chrono;
auto lastUpdate = std::chrono::system_clock::now(); auto lastUnitsUpdate = std::chrono::system_clock::now();
auto lastWeaponsUpdate = std::chrono::system_clock::now();
auto lastExecution = std::chrono::system_clock::now(); auto lastExecution = std::chrono::system_clock::now();
/* Singleton objects */ /* Singleton objects */
UnitsManager* unitsManager = nullptr; UnitsManager* unitsManager = nullptr;
WeaponsManager* weaponsManager = nullptr;
Server* server = nullptr; Server* server = nullptr;
Scheduler* scheduler = nullptr; Scheduler* scheduler = nullptr;
@@ -38,6 +41,7 @@ extern "C" DllExport int coreDeinit(lua_State* L)
server->stop(L); server->stop(L);
delete unitsManager; delete unitsManager;
delete weaponsManager;
delete server; delete server;
delete scheduler; delete scheduler;
@@ -51,6 +55,7 @@ extern "C" DllExport int coreInit(lua_State* L)
{ {
sessionHash = random_string(16); sessionHash = random_string(16);
unitsManager = new UnitsManager(L); unitsManager = new UnitsManager(L);
weaponsManager = new WeaponsManager(L);
server = new Server(L); server = new Server(L);
scheduler = new Scheduler(L); scheduler = new Scheduler(L);
@@ -101,15 +106,36 @@ extern "C" DllExport int coreUnitsData(lua_State * L)
lua_getfield(L, -1, "unitsData"); lua_getfield(L, -1, "unitsData");
luaTableToJSON(L, -1, unitsData); luaTableToJSON(L, -1, unitsData);
const std::chrono::duration<double> updateDuration = std::chrono::system_clock::now() - lastUpdate; const std::chrono::duration<double> updateDuration = std::chrono::system_clock::now() - lastUnitsUpdate;
if (unitsData.has_object_field(L"units")) { if (unitsData.has_object_field(L"units")) {
unitsManager->update(unitsData[L"units"], updateDuration.count()); unitsManager->update(unitsData[L"units"], updateDuration.count());
} }
lastUpdate = std::chrono::system_clock::now(); lastUnitsUpdate = std::chrono::system_clock::now();
return(0); return(0);
} }
extern "C" DllExport int coreWeaponsData(lua_State * L)
{
if (!initialized)
return (0);
/* Lock for thread safety */
lock_guard<mutex> guard(mutexLock);
json::value weaponsData = json::value::object();
lua_getglobal(L, "Olympus");
lua_getfield(L, -1, "weaponsData");
luaTableToJSON(L, -1, weaponsData);
const std::chrono::duration<double> updateDuration = std::chrono::system_clock::now() - lastWeaponsUpdate;
if (weaponsData.has_object_field(L"weapons")) {
weaponsManager->update(weaponsData[L"weapons"], updateDuration.count());
}
lastWeaponsUpdate = std::chrono::system_clock::now();
return(0);
}
extern "C" DllExport int coreMissionData(lua_State * L) extern "C" DllExport int coreMissionData(lua_State * L)
{ {

View File

@@ -21,23 +21,132 @@ NavyUnit::NavyUnit(json::value json, unsigned int ID) : Unit(json, ID)
setDesiredSpeed(10); setDesiredSpeed(10);
}; };
void NavyUnit::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 NavyUnit::setState(unsigned char newState)
{
/************ Perform any action required when LEAVING a state ************/
if (newState != state) {
switch (state) {
case State::IDLE: {
break;
}
case State::REACH_DESTINATION: {
break;
}
case State::FIRE_AT_AREA: {
setTargetPosition(Coords(NULL));
break;
}
default:
break;
}
}
/************ Perform any action required when ENTERING a state ************/
switch (newState) {
case State::IDLE: {
clearActivePath();
resetActiveDestination();
break;
}
case State::REACH_DESTINATION: {
resetActiveDestination();
break;
}
case State::FIRE_AT_AREA: {
clearActivePath();
resetActiveDestination();
break;
}
default:
break;
}
if (newState != state)
resetTask();
log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState));
state = newState;
triggerUpdate(DataIndex::state);
}
void NavyUnit::AIloop() void NavyUnit::AIloop()
{ {
/* TODO */ switch (state) {
case State::IDLE: {
setTask("Idle");
if (getHasTask())
resetTask();
break;
}
case State::REACH_DESTINATION: {
string enrouteTask = "{}";
bool looping = false;
if (activeDestination == NULL || !getHasTask())
{
if (!setActiveDestination())
setState(State::IDLE);
else
goToDestination(enrouteTask);
}
else {
if (isDestinationReached(NAVY_DEST_DIST_THR)) {
if (updateActivePath(looping) && setActiveDestination())
goToDestination(enrouteTask);
else
setState(State::IDLE);
}
}
break;
}
case State::FIRE_AT_AREA: {
setTask("Firing at area");
if (!getHasTask()) {
std::ostringstream taskSS;
taskSS << "{id = 'FireAtPoint', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << ", radius = 1000}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str()));
scheduler->appendCommand(command);
setHasTask(true);
}
}
default:
break;
}
} }
void NavyUnit::changeSpeed(string change) void NavyUnit::changeSpeed(string change)
{ {
if (change.compare("stop") == 0) if (change.compare("stop") == 0)
{ setState(State::IDLE);
}
else if (change.compare("slow") == 0) else if (change.compare("slow") == 0)
{ setDesiredSpeed(getDesiredSpeed() - knotsToMs(5));
}
else if (change.compare("fast") == 0) else if (change.compare("fast") == 0)
{ setDesiredSpeed(getDesiredSpeed() + knotsToMs(5));
if (getDesiredSpeed() < 0)
setDesiredSpeed(0);
}
void NavyUnit::setOnOff(bool newOnOff, bool force)
{
if (newOnOff != onOff || force) {
Unit::setOnOff(newOnOff, force);
Command* command = dynamic_cast<Command*>(new SetOnOff(groupName, onOff));
scheduler->appendCommand(command);
} }
} }

View File

@@ -2,6 +2,7 @@
#include "logger.h" #include "logger.h"
#include "defines.h" #include "defines.h"
#include "unitsManager.h" #include "unitsManager.h"
#include "weaponsManager.h"
#include "scheduler.h" #include "scheduler.h"
#include "luatools.h" #include "luatools.h"
#include <exception> #include <exception>
@@ -13,6 +14,7 @@ using namespace std::chrono;
using namespace base64; using namespace base64;
extern UnitsManager* unitsManager; extern UnitsManager* unitsManager;
extern WeaponsManager* weaponsManager;
extern Scheduler* scheduler; extern Scheduler* scheduler;
extern json::value missionData; extern json::value missionData;
extern mutex mutexLock; extern mutex mutexLock;
@@ -94,7 +96,7 @@ void Server::handle_get(http_request request)
if (path.size() > 0) if (path.size() > 0)
{ {
string URI = to_string(path[0]); string URI = to_string(path[0]);
/* Units data. This is the only binary format data transmitted, all others are transmitted as text json for simplicity */ /* Units data */
if (URI.compare(UNITS_URI) == 0) if (URI.compare(UNITS_URI) == 0)
{ {
unsigned long long updateTime = ms.count(); unsigned long long updateTime = ms.count();
@@ -103,8 +105,16 @@ void Server::handle_get(http_request request)
unitsManager->getUnitData(ss, time); unitsManager->getUnitData(ss, time);
response.set_body(concurrency::streams::bytestream::open_istream(ss.str())); response.set_body(concurrency::streams::bytestream::open_istream(ss.str()));
} }
else if (URI.compare(WEAPONS_URI) == 0)
{
unsigned long long updateTime = ms.count();
stringstream ss;
ss.write((char*)&updateTime, sizeof(updateTime));
weaponsManager->getWeaponData(ss, time);
response.set_body(concurrency::streams::bytestream::open_istream(ss.str()));
}
else { else {
/* Logs data*/ /* Logs data */
if (URI.compare(LOGS_URI) == 0) if (URI.compare(LOGS_URI) == 0)
{ {
auto logs = json::value::object(); auto logs = json::value::object();

View File

@@ -72,7 +72,7 @@ void Unit::update(json::value json, double dt)
if (json.has_boolean_field(L"isAlive")) if (json.has_boolean_field(L"isAlive"))
setAlive(json[L"isAlive"].as_bool()); setAlive(json[L"isAlive"].as_bool());
if (json.has_object_field(L"isHuman")) if (json.has_boolean_field(L"isHuman"))
setHuman(json[L"isHuman"].as_bool()); setHuman(json[L"isHuman"].as_bool());
if (json.has_number_field(L"fuel")) { if (json.has_number_field(L"fuel")) {
@@ -84,18 +84,26 @@ void Unit::update(json::value json, double dt)
for (auto const& el : json[L"ammo"].as_object()) { for (auto const& el : json[L"ammo"].as_object()) {
DataTypes::Ammo ammoItem; DataTypes::Ammo ammoItem;
auto ammoJson = el.second; auto ammoJson = el.second;
ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32();
string name = to_string(ammoJson[L"desc"][L"displayName"].as_string()).substr(0, sizeof(ammoItem.name) - 1);
strcpy_s(ammoItem.name, sizeof(ammoItem.name), name.c_str());
if (ammoJson[L"desc"].has_number_field(L"guidance")) if (ammoJson.has_number_field(L"count"))
ammoItem.guidance = ammoJson[L"desc"][L"guidance"].as_number().to_uint32(); ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32();
if (ammoJson[L"desc"].has_number_field(L"category")) if (ammoJson.has_object_field(L"desc")) {
ammoItem.category = ammoJson[L"desc"][L"category"].as_number().to_uint32(); if (ammoJson[L"desc"].has_string_field(L"displayName")) {
string name = to_string(ammoJson[L"desc"][L"displayName"].as_string());
name = name.substr(0, min(name.size(), sizeof(ammoItem.name) - 1));
strcpy_s(ammoItem.name, sizeof(ammoItem.name), name.c_str());
}
if (ammoJson[L"desc"].has_number_field(L"missileCategory")) if (ammoJson[L"desc"].has_number_field(L"guidance"))
ammoItem.missileCategory = ammoJson[L"desc"][L"missileCategory"].as_number().to_uint32(); ammoItem.guidance = ammoJson[L"desc"][L"guidance"].as_number().to_uint32();
if (ammoJson[L"desc"].has_number_field(L"category"))
ammoItem.category = ammoJson[L"desc"][L"category"].as_number().to_uint32();
if (ammoJson[L"desc"].has_number_field(L"missileCategory"))
ammoItem.missileCategory = ammoJson[L"desc"][L"missileCategory"].as_number().to_uint32();
}
ammo.push_back(ammoItem); ammo.push_back(ammoItem);
} }
setAmmo(ammo); setAmmo(ammo);
@@ -109,7 +117,7 @@ void Unit::update(json::value json, double dt)
contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32(); contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32();
string detectionMethod = to_string(contactJson[L"detectionMethod"]); 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("OPTIC") == 0) contactItem.detectionMethod = 2;
else if (detectionMethod.compare("RADAR") == 0) contactItem.detectionMethod = 4; else if (detectionMethod.compare("RADAR") == 0) contactItem.detectionMethod = 4;
else if (detectionMethod.compare("IRST") == 0) contactItem.detectionMethod = 8; else if (detectionMethod.compare("IRST") == 0) contactItem.detectionMethod = 8;
@@ -211,48 +219,56 @@ void Unit::getData(stringstream& ss, unsigned long long time)
const unsigned char endOfData = DataIndex::endOfData; const unsigned char endOfData = DataIndex::endOfData;
ss.write((const char*)&ID, sizeof(ID)); ss.write((const char*)&ID, sizeof(ID));
for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++) if (!alive && time == 0) {
{ unsigned char datumIndex = DataIndex::category;
if (checkFreshness(datumIndex, time)) { appendString(ss, datumIndex, category);
switch (datumIndex) { datumIndex = DataIndex::alive;
case DataIndex::category: appendString(ss, datumIndex, category); break; appendNumeric(ss, datumIndex, alive);
case DataIndex::alive: appendNumeric(ss, datumIndex, alive); break; }
case DataIndex::human: appendNumeric(ss, datumIndex, human); break; else {
case DataIndex::controlled: appendNumeric(ss, datumIndex, controlled); break; for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++)
case DataIndex::coalition: appendNumeric(ss, datumIndex, coalition); break; {
case DataIndex::country: appendNumeric(ss, datumIndex, country); break; if (checkFreshness(datumIndex, time)) {
case DataIndex::name: appendString(ss, datumIndex, name); break; switch (datumIndex) {
case DataIndex::unitName: appendString(ss, datumIndex, unitName); break; case DataIndex::category: appendString(ss, datumIndex, category); break;
case DataIndex::groupName: appendString(ss, datumIndex, groupName); break; case DataIndex::alive: appendNumeric(ss, datumIndex, alive); break;
case DataIndex::state: appendNumeric(ss, datumIndex, state); break; case DataIndex::human: appendNumeric(ss, datumIndex, human); break;
case DataIndex::task: appendString(ss, datumIndex, task); break; case DataIndex::controlled: appendNumeric(ss, datumIndex, controlled); break;
case DataIndex::hasTask: appendNumeric(ss, datumIndex, hasTask); break; case DataIndex::coalition: appendNumeric(ss, datumIndex, coalition); break;
case DataIndex::position: appendNumeric(ss, datumIndex, position); break; case DataIndex::country: appendNumeric(ss, datumIndex, country); break;
case DataIndex::speed: appendNumeric(ss, datumIndex, speed); break; case DataIndex::name: appendString(ss, datumIndex, name); break;
case DataIndex::heading: appendNumeric(ss, datumIndex, heading); break; case DataIndex::unitName: appendString(ss, datumIndex, unitName); break;
case DataIndex::isTanker: appendNumeric(ss, datumIndex, isTanker); break; case DataIndex::groupName: appendString(ss, datumIndex, groupName); break;
case DataIndex::isAWACS: appendNumeric(ss, datumIndex, isAWACS); break; case DataIndex::state: appendNumeric(ss, datumIndex, state); break;
case DataIndex::onOff: appendNumeric(ss, datumIndex, onOff); break; case DataIndex::task: appendString(ss, datumIndex, task); break;
case DataIndex::followRoads: appendNumeric(ss, datumIndex, followRoads); break; case DataIndex::hasTask: appendNumeric(ss, datumIndex, hasTask); break;
case DataIndex::fuel: appendNumeric(ss, datumIndex, fuel); break; case DataIndex::position: appendNumeric(ss, datumIndex, position); break;
case DataIndex::desiredSpeed: appendNumeric(ss, datumIndex, desiredSpeed); break; case DataIndex::speed: appendNumeric(ss, datumIndex, speed); break;
case DataIndex::desiredSpeedType: appendNumeric(ss, datumIndex, desiredSpeedType); break; case DataIndex::heading: appendNumeric(ss, datumIndex, heading); break;
case DataIndex::desiredAltitude: appendNumeric(ss, datumIndex, desiredAltitude); break; case DataIndex::isTanker: appendNumeric(ss, datumIndex, isTanker); break;
case DataIndex::desiredAltitudeType: appendNumeric(ss, datumIndex, desiredAltitudeType); break; case DataIndex::isAWACS: appendNumeric(ss, datumIndex, isAWACS); break;
case DataIndex::leaderID: appendNumeric(ss, datumIndex, leaderID); break; case DataIndex::onOff: appendNumeric(ss, datumIndex, onOff); break;
case DataIndex::formationOffset: appendNumeric(ss, datumIndex, formationOffset); break; case DataIndex::followRoads: appendNumeric(ss, datumIndex, followRoads); break;
case DataIndex::targetID: appendNumeric(ss, datumIndex, targetID); break; case DataIndex::fuel: appendNumeric(ss, datumIndex, fuel); break;
case DataIndex::targetPosition: appendNumeric(ss, datumIndex, targetPosition); break; case DataIndex::desiredSpeed: appendNumeric(ss, datumIndex, desiredSpeed); break;
case DataIndex::ROE: appendNumeric(ss, datumIndex, ROE); break; case DataIndex::desiredSpeedType: appendNumeric(ss, datumIndex, desiredSpeedType); break;
case DataIndex::reactionToThreat: appendNumeric(ss, datumIndex, reactionToThreat); break; case DataIndex::desiredAltitude: appendNumeric(ss, datumIndex, desiredAltitude); break;
case DataIndex::emissionsCountermeasures: appendNumeric(ss, datumIndex, emissionsCountermeasures); break; case DataIndex::desiredAltitudeType: appendNumeric(ss, datumIndex, desiredAltitudeType); break;
case DataIndex::TACAN: appendNumeric(ss, datumIndex, TACAN); break; case DataIndex::leaderID: appendNumeric(ss, datumIndex, leaderID); break;
case DataIndex::radio: appendNumeric(ss, datumIndex, radio); break; case DataIndex::formationOffset: appendNumeric(ss, datumIndex, formationOffset); break;
case DataIndex::generalSettings: appendNumeric(ss, datumIndex, generalSettings); break; case DataIndex::targetID: appendNumeric(ss, datumIndex, targetID); break;
case DataIndex::ammo: appendVector(ss, datumIndex, ammo); break; case DataIndex::targetPosition: appendNumeric(ss, datumIndex, targetPosition); break;
case DataIndex::contacts: appendVector(ss, datumIndex, contacts); break; case DataIndex::ROE: appendNumeric(ss, datumIndex, ROE); break;
case DataIndex::activePath: appendList(ss, datumIndex, activePath); break; case DataIndex::reactionToThreat: appendNumeric(ss, datumIndex, reactionToThreat); break;
case DataIndex::isLeader: appendNumeric(ss, datumIndex, isLeader); 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;
}
} }
} }
} }

View File

@@ -17,7 +17,7 @@ extern Scheduler* scheduler;
UnitsManager::UnitsManager(lua_State* L) UnitsManager::UnitsManager(lua_State* L)
{ {
LogInfo(L, "Units Factory constructor called successfully"); LogInfo(L, "Units Manager constructor called successfully");
} }
UnitsManager::~UnitsManager() UnitsManager::~UnitsManager()
@@ -110,10 +110,6 @@ void UnitsManager::update(json::value& json, double dt)
units[ID] = dynamic_cast<Unit*>(new GroundUnit(p.second, ID)); units[ID] = dynamic_cast<Unit*>(new GroundUnit(p.second, ID));
else if (category.compare("NavyUnit") == 0) else if (category.compare("NavyUnit") == 0)
units[ID] = dynamic_cast<Unit*>(new NavyUnit(p.second, ID)); units[ID] = dynamic_cast<Unit*>(new NavyUnit(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 */ /* Initialize the unit if creation was successfull */
if (units.count(ID) != 0) { if (units.count(ID) != 0) {

View File

@@ -4,19 +4,102 @@
#include "commands.h" #include "commands.h"
#include "scheduler.h" #include "scheduler.h"
#include "defines.h" #include "defines.h"
#include "unitsmanager.h"
#include <GeographicLib/Geodesic.hpp> #include <chrono>
using namespace GeographicLib; using namespace std::chrono;
extern Scheduler* scheduler; Weapon::Weapon(json::value json, unsigned int ID) :
extern UnitsManager* unitsManager; ID(ID)
{
log("Creating weapon with ID: " + to_string(ID));
}
/* Weapon */ Weapon::~Weapon()
Weapon::Weapon(json::value json, unsigned int ID) : Unit(json, ID)
{ {
}; }
void Weapon::initialize(json::value json)
{
if (json.has_string_field(L"name"))
setName(to_string(json[L"name"]));
if (json.has_number_field(L"coalitionID"))
setCoalition(json[L"coalitionID"].as_number().to_int32());
update(json, 0);
}
void Weapon::update(json::value json, double dt)
{
if (json.has_object_field(L"position"))
{
setPosition({
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());
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());
}
bool Weapon::checkFreshness(unsigned char datumIndex, unsigned long long time) {
auto it = updateTimeMap.find(datumIndex);
if (it == updateTimeMap.end())
return false;
else
return it->second > time;
}
bool Weapon::hasFreshData(unsigned long long time) {
for (auto it : updateTimeMap)
if (it.second > time)
return true;
return false;
}
void Weapon::getData(stringstream& ss, unsigned long long time)
{
const unsigned char endOfData = DataIndex::endOfData;
ss.write((const char*)&ID, sizeof(ID));
if (!alive && time == 0) {
unsigned char datumIndex = DataIndex::category;
appendString(ss, datumIndex, category);
datumIndex = DataIndex::alive;
appendNumeric(ss, datumIndex, alive);
}
else {
for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++)
{
if (checkFreshness(datumIndex, time)) {
switch (datumIndex) {
case DataIndex::category: appendString(ss, datumIndex, category); break;
case DataIndex::alive: appendNumeric(ss, datumIndex, alive); break;
case DataIndex::coalition: appendNumeric(ss, datumIndex, coalition); break;
case DataIndex::name: appendString(ss, datumIndex, name); 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;
}
}
}
}
ss.write((const char*)&endOfData, sizeof(endOfData));
}
void Weapon::triggerUpdate(unsigned char datumIndex) {
updateTimeMap[datumIndex] = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
}
/* Missile */ /* Missile */
Missile::Missile(json::value json, unsigned int ID) : Weapon(json, ID) Missile::Missile(json::value json, unsigned int ID) : Weapon(json, ID)

View File

@@ -0,0 +1,65 @@
#include "framework.h"
#include "weaponsManager.h"
#include "logger.h"
#include "weapon.h"
#include "scheduler.h"
#include "base64.hpp"
using namespace base64;
WeaponsManager::WeaponsManager(lua_State* L)
{
LogInfo(L, "Weapons Manager constructor called successfully");
}
WeaponsManager::~WeaponsManager()
{
}
Weapon* WeaponsManager::getWeapon(unsigned int ID)
{
if (weapons.find(ID) == weapons.end()) {
return nullptr;
}
else {
return weapons[ID];
}
}
void WeaponsManager::update(json::value& json, double dt)
{
for (auto const& p : json.as_object())
{
unsigned int ID = std::stoi(p.first);
if (weapons.count(ID) == 0)
{
json::value value = p.second;
if (value.has_string_field(L"category")) {
string category = to_string(value[L"category"].as_string());
if (category.compare("Missile") == 0)
weapons[ID] = dynamic_cast<Weapon*>(new Missile(p.second, ID));
else if (category.compare("Bomb") == 0)
weapons[ID] = dynamic_cast<Weapon*>(new Bomb(p.second, ID));
/* Initialize the weapon if creation was successfull */
if (weapons.count(ID) != 0) {
weapons[ID]->update(p.second, dt);
weapons[ID]->initialize(p.second);
}
}
}
else {
/* Update the weapon if present*/
if (weapons.count(ID) != 0)
weapons[ID]->update(p.second, dt);
}
}
}
void WeaponsManager::getWeaponData(stringstream& ss, unsigned long long time)
{
for (auto const& p : weapons)
p.second->getData(ss, time);
}

View File

@@ -9,11 +9,13 @@ typedef int(__stdcall* f_coreInit)(lua_State* L);
typedef int(__stdcall* f_coreDeinit)(lua_State* L); typedef int(__stdcall* f_coreDeinit)(lua_State* L);
typedef int(__stdcall* f_coreFrame)(lua_State* L); typedef int(__stdcall* f_coreFrame)(lua_State* L);
typedef int(__stdcall* f_coreUnitsData)(lua_State* L); typedef int(__stdcall* f_coreUnitsData)(lua_State* L);
typedef int(__stdcall* f_coreWeaponsData)(lua_State* L);
typedef int(__stdcall* f_coreMissionData)(lua_State* L); typedef int(__stdcall* f_coreMissionData)(lua_State* L);
f_coreInit coreInit = nullptr; f_coreInit coreInit = nullptr;
f_coreDeinit coreDeinit = nullptr; f_coreDeinit coreDeinit = nullptr;
f_coreFrame coreFrame = nullptr; f_coreFrame coreFrame = nullptr;
f_coreUnitsData coreUnitsData = nullptr; f_coreUnitsData coreUnitsData = nullptr;
f_coreWeaponsData coreWeaponsData = nullptr;
f_coreMissionData coreMissionData = nullptr; f_coreMissionData coreMissionData = nullptr;
static int onSimulationStart(lua_State* L) static int onSimulationStart(lua_State* L)
@@ -67,14 +69,21 @@ static int onSimulationStart(lua_State* L)
goto error; goto error;
} }
coreUnitsData = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreUnitsData"); coreUnitsData = (f_coreUnitsData)GetProcAddress(hGetProcIDDLL, "coreUnitsData");
if (!coreUnitsData) if (!coreUnitsData)
{ {
LogError(L, "Error getting coreUnitsData ProcAddress from DLL"); LogError(L, "Error getting coreUnitsData ProcAddress from DLL");
goto error; goto error;
} }
coreMissionData = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreMissionData"); coreWeaponsData = (f_coreWeaponsData)GetProcAddress(hGetProcIDDLL, "coreWeaponsData");
if (!coreWeaponsData)
{
LogError(L, "Error getting coreWeaponsData ProcAddress from DLL");
goto error;
}
coreMissionData = (f_coreMissionData)GetProcAddress(hGetProcIDDLL, "coreMissionData");
if (!coreMissionData) if (!coreMissionData)
{ {
LogError(L, "Error getting coreMissionData ProcAddress from DLL"); LogError(L, "Error getting coreMissionData ProcAddress from DLL");
@@ -126,6 +135,7 @@ static int onSimulationStop(lua_State* L)
coreDeinit = nullptr; coreDeinit = nullptr;
coreFrame = nullptr; coreFrame = nullptr;
coreUnitsData = nullptr; coreUnitsData = nullptr;
coreWeaponsData = nullptr;
coreMissionData = nullptr; coreMissionData = nullptr;
} }
@@ -147,6 +157,15 @@ static int setUnitsData(lua_State* L)
return 0; return 0;
} }
static int setWeaponsData(lua_State* L)
{
if (coreWeaponsData)
{
coreWeaponsData(L);
}
return 0;
}
static int setMissionData(lua_State* L) static int setMissionData(lua_State* L)
{ {
if (coreMissionData) if (coreMissionData)
@@ -161,6 +180,7 @@ static const luaL_Reg Map[] = {
{"onSimulationFrame", onSimulationFrame}, {"onSimulationFrame", onSimulationFrame},
{"onSimulationStop", onSimulationStop}, {"onSimulationStop", onSimulationStop},
{"setUnitsData", setUnitsData }, {"setUnitsData", setUnitsData },
{"setWeaponsData", setWeaponsData },
{"setMissionData", setMissionData }, {"setMissionData", setMissionData },
{NULL, NULL} {NULL, NULL}
}; };

View File

@@ -1,10 +1,11 @@
#pragma once #pragma once
#define VERSION "v0.4.0-alpha" #define VERSION "v0.4.1-alpha"
#define LOG_NAME "Olympus_log.txt" #define LOG_NAME "Olympus_log.txt"
#define REST_ADDRESS "http://localhost:30000" #define REST_ADDRESS "http://localhost:30000"
#define REST_URI "olympus" #define REST_URI "olympus"
#define UNITS_URI "units" #define UNITS_URI "units"
#define WEAPONS_URI "weapons"
#define LOGS_URI "logs" #define LOGS_URI "logs"
#define AIRBASES_URI "airbases" #define AIRBASES_URI "airbases"
#define BULLSEYE_URI "bullseyes" #define BULLSEYE_URI "bullseyes"