From 588228c0504e4f8ff8fbd70dba641de95bed81b3 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Fri, 15 Sep 2023 17:05:26 +0200 Subject: [PATCH] Implemented basic Plugin handling --- client/{src => }/@types/dom.d.ts | 15 +- client/@types/olympus.d.ts | 267 ++++++++++++++++++ client/app.js | 2 + client/demo.js | 2 +- client/plugins/controltips/copy.bat | 5 + client/plugins/controltips/index.js | 252 +++++++++++++++++ client/plugins/controltips/package-lock.json | 162 +++++++++++ client/plugins/controltips/package.json | 10 + client/plugins/controltips/plugin.json | 6 + .../controltips/src}/controltips.ts | 112 ++++---- client/plugins/controltips/src/index.ts | 5 + .../controltips/style.css} | 18 +- client/plugins/controltips/tsconfig.json | 104 +++++++ .../public/plugins/controltipsplugin/index.js | 252 +++++++++++++++++ .../plugins/controltipsplugin/plugin.json | 6 + .../plugins/controltipsplugin/style.css | 33 +++ client/public/stylesheets/leaflet/leaflet.css | 7 +- client/routes/api/atc.js | 229 +++++++-------- client/routes/plugins.js | 23 ++ client/routes/resources.js | 1 + client/src/@types/server.d.ts | 52 ---- client/src/@types/unit.d.ts | 104 ------- client/src/@types/unitdatabase.d.ts | 43 --- client/src/app.ts | 151 ++++++++++ client/src/constants/constants.ts | 2 +- client/src/contextmenus/airbasecontextmenu.ts | 16 +- client/src/contextmenus/airbasespawnmenu.ts | 4 +- .../contextmenus/coalitionareacontextmenu.ts | 16 +- client/src/contextmenus/mapcontextmenu.ts | 22 +- client/src/controls/unitspawnmenu.ts | 36 ++- client/src/features/featureswitches.ts | 172 ----------- client/src/features/toggleablefeature.ts | 35 --- client/src/index.ts | 252 +++-------------- client/src/indexapp.ts | 66 ----- client/src/map/coalitionarea/coalitionarea.ts | 28 +- client/src/map/map.ts | 64 ++--- client/src/map/markers/smokemarker.ts | 4 +- client/src/map/markers/temporaryunitmarker.ts | 4 +- client/src/mission/airbase.ts | 24 -- client/src/mission/missionmanager.ts | 13 +- client/src/olympusapp.ts | 70 ----- client/src/other/eventsmanager.ts | 7 +- client/src/other/manager.ts | 47 +-- client/src/other/utils.ts | 1 - client/src/panels/hotgrouppanel.ts | 12 +- client/src/panels/logpanel.ts | 7 +- client/src/panels/mouseinfopanel.ts | 40 +-- client/src/panels/panel.ts | 30 +- client/src/panels/paneleventsmanager.ts | 49 +--- client/src/panels/panelsmanager.ts | 17 -- client/src/panels/unitcontrolpanel.ts | 47 ++- client/src/panels/unitinfopanel.ts | 1 - client/src/plugin/plugin.ts | 36 --- client/src/plugin/pluginmanager.ts | 58 +++- client/src/server/dataextractor.ts | 1 - client/src/server/server.ts | 57 ++-- client/src/shortcut/shortcut.ts | 54 +--- client/src/shortcut/shortcutmanager.ts | 49 ++-- client/src/unit/databases/aircraftdatabase.ts | 4 +- .../src/unit/databases/groundunitdatabase.ts | 4 +- .../src/unit/databases/helicopterdatabase.ts | 4 +- client/src/unit/databases/navyunitdatabase.ts | 4 +- client/src/unit/databases/unitdatabase.ts | 13 +- client/src/unit/unit.ts | 132 +++++---- client/src/unit/unitsmanager.ts | 67 ++--- client/src/weapon/weapon.ts | 23 +- client/src/weapon/weaponsmanager.ts | 5 +- client/tsconfig.json | 12 +- client/views/aic/aic.ejs | 52 ---- client/views/atc/addflight.ejs | 5 - client/views/atc/atc.ejs | 11 - client/views/atc/board.ejs | 22 -- client/views/atc/unitdatatable.ejs | 13 - client/views/index.ejs | 3 - client/views/other/controltips.ejs | 1 - 75 files changed, 1920 insertions(+), 1657 deletions(-) rename client/{src => }/@types/dom.d.ts (89%) create mode 100644 client/@types/olympus.d.ts create mode 100644 client/plugins/controltips/copy.bat create mode 100644 client/plugins/controltips/index.js create mode 100644 client/plugins/controltips/package-lock.json create mode 100644 client/plugins/controltips/package.json create mode 100644 client/plugins/controltips/plugin.json rename client/{src/shortcut => plugins/controltips/src}/controltips.ts (70%) create mode 100644 client/plugins/controltips/src/index.ts rename client/{public/stylesheets/other/controltips.css => plugins/controltips/style.css} (61%) create mode 100644 client/plugins/controltips/tsconfig.json create mode 100644 client/public/plugins/controltipsplugin/index.js create mode 100644 client/public/plugins/controltipsplugin/plugin.json create mode 100644 client/public/plugins/controltipsplugin/style.css create mode 100644 client/routes/plugins.js delete mode 100644 client/src/@types/server.d.ts delete mode 100644 client/src/@types/unit.d.ts delete mode 100644 client/src/@types/unitdatabase.d.ts create mode 100644 client/src/app.ts delete mode 100644 client/src/features/featureswitches.ts delete mode 100644 client/src/features/toggleablefeature.ts delete mode 100644 client/src/indexapp.ts delete mode 100644 client/src/olympusapp.ts delete mode 100644 client/src/panels/panelsmanager.ts delete mode 100644 client/src/plugin/plugin.ts delete mode 100644 client/views/aic/aic.ejs delete mode 100644 client/views/atc/addflight.ejs delete mode 100644 client/views/atc/atc.ejs delete mode 100644 client/views/atc/board.ejs delete mode 100644 client/views/atc/unitdatatable.ejs delete mode 100644 client/views/other/controltips.ejs diff --git a/client/src/@types/dom.d.ts b/client/@types/dom.d.ts similarity index 89% rename from client/src/@types/dom.d.ts rename to client/@types/dom.d.ts index 7fea9ff0..4d8080e9 100644 --- a/client/src/@types/dom.d.ts +++ b/client/@types/dom.d.ts @@ -29,17 +29,8 @@ declare global { listener: (this: Document, ev: CustomEventMap[K]) => void): void; dispatchEvent(ev: CustomEventMap[K]): void; } + + function getOlympusPlugin(): OlympusPlugin; } -export interface ConfigParameters { - port: number; - address: string; -} - -export interface ContextMenuOption { - tooltip: string; - src: string; - callback: CallableFunction; -} - -export { }; \ No newline at end of file +export { }; diff --git a/client/@types/olympus.d.ts b/client/@types/olympus.d.ts new file mode 100644 index 00000000..d9856110 --- /dev/null +++ b/client/@types/olympus.d.ts @@ -0,0 +1,267 @@ +interface OlympusPlugin { + getName: () => string; + initialize: (any) => boolean; +} + +declare global { + function getOlympusPlugin(): OlympusPlugin; +} + +interface ConfigurationOptions { + port: number; + address: string; +} + +interface ContextMenuOption { + tooltip: string; + src: string; + callback: CallableFunction; +} + +interface AirbasesData { + airbases: { [key: string]: any }, + sessionHash: string; + time: number; +} + +interface BullseyesData { + bullseyes: { [key: string]: { latitude: number, longitude: number, coalition: string } }, + sessionHash: string; + time: number; +} + +interface MissionData { + mission: { + theatre: string, + dateAndTime: DateAndTime; + commandModeOptions: CommandModeOptions; + coalitions: { red: string[], blue: string[] } = { }; + } + time: number; + sessionHash: string; +} + +interface CommandModeOptions { + commandMode: string; + restrictSpawns: boolean; + restrictToCoalition: boolean; + setupTime: number; + spawnPoints: { + red: number, + blue: number + }, + eras: string[] +} + +interface DateAndTime { + date: { Year: number, Month: number, Day: number }; + time: { h: number, m: number, s: number }; + elapsedTime: number; + startTime: number; +} + +interface LogData { + logs: { [key: string]: string }, + sessionHash: string; + time: number; +} + +interface ServerRequestOptions { + time?: number; + commandHash?: string; +} + +interface UnitSpawnTable { + unitType: string, + location: latlng, + altitude?: number, + loadout?: string, + liveryID: string +} + +interface ObjectIconOptions { + showState: boolean, + showVvi: boolean, + showHotgroup: boolean, + showUnitIcon: boolean, + showShortLabel: boolean, + showFuel: boolean, + showAmmo: boolean, + showSummary: boolean, + showCallsign: boolean, + rotateToHeading: boolean +} + +interface GeneralSettings { + prohibitJettison: boolean; + prohibitAA: boolean; + prohibitAG: boolean; + prohibitAfterburner: boolean; + prohibitAirWpn: boolean; +} + +interface TACAN { + isOn: boolean; + channel: number; + XY: string; + callsign: string; +} + +interface Radio { + frequency: number; + callsign: number; + callsignNumber: number; +} + +interface Ammo { + quantity: number, + name: string, + guidance: number, + category: number, + missileCategory: number +} + +interface Contact { + ID: number, + detectionMethod: number +} + +interface Offset { + x: number, + y: number, + z: number +} + +interface UnitData { + category: string, + ID: number; + alive: boolean; + human: boolean; + controlled: boolean; + coalition: string; + country: number; + name: string; + unitName: string; + groupName: string; + state: string; + task: string; + hasTask: boolean; + position: LatLng; + speed: number; + heading: number; + isTanker: boolean; + isAWACS: boolean; + onOff: boolean; + followRoads: boolean; + fuel: number; + desiredSpeed: number; + desiredSpeedType: string; + desiredAltitude: number; + desiredAltitudeType: string; + leaderID: number; + formationOffset: Offset; + targetID: number; + targetPosition: LatLng; + ROE: string; + reactionToThreat: string; + emissionsCountermeasures: string; + TACAN: TACAN; + radio: Radio; + generalSettings: GeneralSettings; + ammo: Ammo[]; + contacts: Contact[]; + activePath: LatLng[]; + isLeader: boolean; +} + +interface LoadoutItemBlueprint { + name: string; + quantity: number; + effectiveAgainst?: string; +} + +interface LoadoutBlueprint { + fuel: number; + items: LoadoutItemBlueprint[]; + roles: string[]; + code: string; + name: string; +} + +interface UnitBlueprint { + name: string; + coalition: string; + era: string; + label: string; + shortLabel: string; + type?: string; + range?: string; + loadouts?: LoadoutBlueprint[]; + filename?: string; + liveries?: { [key: string]: { name: string, countries: string[] } }; + cost?: number; +} + +interface UnitSpawnOptions { + roleType: string; + name: string; + latlng: LatLng; + coalition: string; + count: number; + country: string; + loadout: LoadoutBlueprint | undefined; + airbase: Airbase | undefined; + liveryID: string | undefined; + altitude: number | undefined; +} + +interface AirbaseOptions { + name: string, + position: L.LatLng +} + +interface AirbaseChartData { + elevation: string, + ICAO: string, + TACAN: string, + runways: AirbaseChartRunwayData[] +} + +interface AirbaseChartRunwayData { + headings: AirbaseChartRunwayHeadingData[], + length: string +} + +interface AirbaseChartRunwayHeadingData { + [index: string]: { + magHeading: string, + ILS: string + } +} + +interface Listener { + callback: CallableFunction; + name?: string +} + +interface ShortcutOptions { + altKey?: boolean; + callback: CallableFunction; + ctrlKey?: boolean; + name?: string; + shiftKey?: boolean; +} + +interface KeyboardShortcutOptions extends ShortcutOptions { + code: string; + event?: "keydown" | "keyup"; +} + +interface MouseShortcutOptions extends ShortcutOptions { + button: number; + event: "mousedown" | "mouseup"; +} + +interface Manager { + add: CallableFunction; +} \ No newline at end of file diff --git a/client/app.js b/client/app.js index 651c2b73..1b2edaa9 100644 --- a/client/app.js +++ b/client/app.js @@ -10,6 +10,7 @@ var indexRouter = require('./routes/index'); var uikitRouter = require('./routes/uikit'); var usersRouter = require('./routes/users'); var resourcesRouter = require('./routes/resources'); +var pluginsRouter = require('./routes/plugins'); var app = express(); @@ -22,6 +23,7 @@ app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/api/atc', atcRouter); app.use('/api/airbases', airbasesRouter); +app.use('/plugins', pluginsRouter) app.use('/users', usersRouter); app.use('/uikit', uikitRouter); app.use('/resources', resourcesRouter); diff --git a/client/demo.js b/client/demo.js index 758754d9..99c65f9a 100644 --- a/client/demo.js +++ b/client/demo.js @@ -444,7 +444,7 @@ class DemoDataGenerator { var auth = req.get("Authorization"); if (auth) { - var username = atob(auth.replace("Basic ", "")).split(":")[0]; + var username = Buffer.from(auth.replace("Basic ", ""), 'base64').toString('binary').split(":")[0]; switch (username) { case "admin": ret.mission.commandModeOptions.commandMode = "Game master"; diff --git a/client/plugins/controltips/copy.bat b/client/plugins/controltips/copy.bat new file mode 100644 index 00000000..e0a9d89d --- /dev/null +++ b/client/plugins/controltips/copy.bat @@ -0,0 +1,5 @@ +mkdir .\\..\\..\\public\\plugins\\controltipsplugin + +copy .\\index.js .\\..\\..\\public\\plugins\\controltipsplugin\\index.js +copy .\\plugin.json .\\..\\..\\public\\plugins\\controltipsplugin\\plugin.json +copy .\\style.css .\\..\\..\\public\\plugins\\controltipsplugin\\style.css \ No newline at end of file diff --git a/client/plugins/controltips/index.js b/client/plugins/controltips/index.js new file mode 100644 index 00000000..90ee53e6 --- /dev/null +++ b/client/plugins/controltips/index.js @@ -0,0 +1,252 @@ +(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i { + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + }); + __classPrivateFieldGet(this, _ControlTips_shortcutManager, "f").onKeyUp(() => { + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + }); + document.addEventListener("airbaseMouseover", (ev) => { + __classPrivateFieldSet(this, _ControlTips_cursorIsHoveringOverAirbase, true, "f"); + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + }); + document.addEventListener("airbaseMouseout", (ev) => { + __classPrivateFieldSet(this, _ControlTips_cursorIsHoveringOverAirbase, false, "f"); + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + }); + //document.addEventListener("unitDeselection", (ev: CustomEvent) => { + // this.#updateTips(); + //}); + document.addEventListener("unitMouseover", (ev) => { + __classPrivateFieldSet(this, _ControlTips_cursorIsHoveringOverUnit, true, "f"); + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + }); + document.addEventListener("unitMouseout", (ev) => { + __classPrivateFieldSet(this, _ControlTips_cursorIsHoveringOverUnit, false, "f"); + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + }); + //document.addEventListener("unitSelection", (ev: CustomEvent) => { + // this.#updateTips() + //}); + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + return true; + } + getElement() { + return __classPrivateFieldGet(this, _ControlTips_element, "f"); + } + toggle(bool) { + this.getElement().classList.toggle("hide", bool); + } +} +exports.ControlTips = ControlTips; +_ControlTips_element = new WeakMap(), _ControlTips_app = new WeakMap(), _ControlTips_shortcutManager = new WeakMap(), _ControlTips_cursorIsHoveringOverUnit = new WeakMap(), _ControlTips_cursorIsHoveringOverAirbase = new WeakMap(), _ControlTips_instances = new WeakSet(), _ControlTips_updateTips = function _ControlTips_updateTips() { + const combos = [ + { + "keys": [], + "tips": [ + { + "key": `SHIFT`, + "action": `Box select`, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": false, + "showIfUnitSelected": false + }, + { + "key": `Mouse1`, + "action": `Deselect`, + "showIfUnitSelected": true + }, + { + "key": `Mouse1+drag`, + "action": `Move map`, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": false, + "showIfUnitSelected": false + }, + { + "key": `Mouse2`, + "action": `Spawn menu`, + "showIfUnitSelected": false, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": false + }, + { + "key": `Mouse2`, + "action": `Quick options`, + "showIfUnitSelected": false, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": true + }, + { + "key": `Mouse2`, + "action": `Airbase menu`, + "showIfUnitSelected": false, + "showIfHoveringOverAirbase": true, + "showIfHoveringOverUnit": false + }, + { + "key": `Mouse2`, + "action": `Set first waypoint`, + "showIfHoveringOverAirbase": false, + "showIfUnitSelected": true, + "unitsMustBeControlled": true + }, + { + "key": "CTRL+Mouse2", + "action": "Add waypoint", + "showIfUnitSelected": true, + "showIfHoveringOverAirbase": false, + "unitsMustBeControlled": true + }, + { + "key": `Mouse2 (hold)`, + "action": `Point operations`, + "showIfUnitSelected": true, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": false, + "unitsMustBeControlled": true + }, + { + "key": "CTRL", + "action": " Pin tool", + "showIfUnitSelected": false, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": false, + "unitsMustBeControlled": true + }, + { + "key": "CTRL+Mouse2", + "action": " Airbase menu", + "showIfUnitSelected": true, + "showIfHoveringOverAirbase": true, + "unitsMustBeControlled": true + }, + { + "key": `Delete`, + "action": `Delete unit`, + "showIfHoveringOverAirbase": false, + "showIfUnitSelected": true + } + ] + }, + { + "keys": ["ControlLeft"], + "tips": [ + { + "key": `Mouse1`, + "action": "Toggle pin", + "showIfUnitSelected": false, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": false + }, + { + "key": `Mouse1`, + "action": "Toggle selection", + "showIfUnitSelected": true, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": true + }, + { + "key": `Mouse2`, + "action": `Add waypoint`, + "showIfHoveringOverAirbase": false, + "showIfUnitSelected": true, + "unitsMustBeControlled": true + }, + { + "key": `Mouse2`, + "action": `Airbase menu`, + "showIfHoveringOverAirbase": true, + "showIfUnitSelected": true, + "unitsMustBeControlled": true + } + ] + }, + { + "keys": ["ShiftLeft"], + "tips": [ + { + "key": `mouse1+drag`, + "action": "Box select" + } + ] + } + ]; + const currentCombo = combos.find((combo) => __classPrivateFieldGet(this, _ControlTips_shortcutManager, "f").keyComboMatches(combo.keys)) || combos[0]; + const element = this.getElement(); + element.innerHTML = ""; + let numSelectedUnits = 0; + let unitSelectionContainsControlled = false; + if (__classPrivateFieldGet(this, _ControlTips_app, "f").getUnitsManager()) { + let selectedUnits = Object.values(__classPrivateFieldGet(this, _ControlTips_app, "f").getUnitsManager().getSelectedUnits()); + numSelectedUnits = selectedUnits.length; + unitSelectionContainsControlled = selectedUnits.some((unit) => unit.getControlled()); + } + currentCombo.tips.forEach((tip) => { + if (numSelectedUnits > 0) { + if (tip.showIfUnitSelected === false) { + return; + } + if (tip.unitsMustBeControlled === true && unitSelectionContainsControlled === false) { + return; + } + } + if (numSelectedUnits === 0 && tip.showIfUnitSelected === true) { + return; + } + if (typeof tip.showIfHoveringOverAirbase === "boolean") { + if (tip.showIfHoveringOverAirbase !== __classPrivateFieldGet(this, _ControlTips_cursorIsHoveringOverAirbase, "f")) { + return; + } + } + if (typeof tip.showIfHoveringOverUnit === "boolean") { + if (tip.showIfHoveringOverUnit !== __classPrivateFieldGet(this, _ControlTips_cursorIsHoveringOverUnit, "f")) { + return; + } + } + element.innerHTML += `
${tip.key}${tip.action}
`; + }); +}; + +},{}],2:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const controltips_1 = require("./controltips"); +globalThis.getOlympusPlugin = () => { + return new controltips_1.ControlTips(); +}; + +},{"./controltips":1}]},{},[2]); diff --git a/client/plugins/controltips/package-lock.json b/client/plugins/controltips/package-lock.json new file mode 100644 index 00000000..86c830ed --- /dev/null +++ b/client/plugins/controltips/package-lock.json @@ -0,0 +1,162 @@ +{ + "name": "ControlTipsPlugin", + "version": "v0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "requires": { + "error-ex": "^1.2.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "tsconfig": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-5.0.3.tgz", + "integrity": "sha512-Cq65A3kVp6BbsUgg9DRHafaGmbMb9EhAc7fjWvudNWKjkbWrt43FnrtZt6awshH1R0ocfF2Z0uxock3lVqEgOg==", + "requires": { + "any-promise": "^1.3.0", + "parse-json": "^2.2.0", + "strip-bom": "^2.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "tsify": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/tsify/-/tsify-5.0.4.tgz", + "integrity": "sha512-XAZtQ5OMPsJFclkZ9xMZWkSNyMhMxEPsz3D2zu79yoKorH9j/DT4xCloJeXk5+cDhosEibu4bseMVjyPOAyLJA==", + "requires": { + "convert-source-map": "^1.1.0", + "fs.realpath": "^1.0.0", + "object-assign": "^4.1.0", + "semver": "^6.1.0", + "through2": "^2.0.0", + "tsconfig": "^5.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } +} diff --git a/client/plugins/controltips/package.json b/client/plugins/controltips/package.json new file mode 100644 index 00000000..3b0097a1 --- /dev/null +++ b/client/plugins/controltips/package.json @@ -0,0 +1,10 @@ +{ + "name": "ControlTipsPlugin", + "version": "v0.0.1", + "private": true, + "scripts": { + "build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat" + }, + "dependencies": {}, + "devDependencies": {} +} diff --git a/client/plugins/controltips/plugin.json b/client/plugins/controltips/plugin.json new file mode 100644 index 00000000..77a1f817 --- /dev/null +++ b/client/plugins/controltips/plugin.json @@ -0,0 +1,6 @@ +{ + "name": "Control Tip Plugin", + "version": "0.0.1", + "description": "This plugin shows useful control tips on the right side of the screen. The tips change dynamically depending on what the user does", + "author": "Peekaboo" +} \ No newline at end of file diff --git a/client/src/shortcut/controltips.ts b/client/plugins/controltips/src/controltips.ts similarity index 70% rename from client/src/shortcut/controltips.ts rename to client/plugins/controltips/src/controltips.ts index a31ef77b..9e885247 100644 --- a/client/src/shortcut/controltips.ts +++ b/client/plugins/controltips/src/controltips.ts @@ -1,80 +1,79 @@ -import { OlympusApp } from "../olympusapp"; -import { ShortcutManager } from "../shortcut/shortcutmanager"; -import { Unit } from "../unit/unit"; +export class ControlTipsPlugin implements OlympusPlugin { + #element: HTMLElement; + #app: any; + #shortcutManager: any; + #cursorIsHoveringOverUnit: boolean = false; + #cursorIsHoveringOverAirbase: boolean = false; + + constructor() { + this.#element = document.createElement("div"); + this.#element.id = "control-tips-panel"; + document.body.appendChild(this.#element); + console.log("HELLO") + } -export class ControlTips { + getName() { + return "Control Tips Plugin" + } - #element:HTMLElement; - #cursorIsHoveringOverUnit:boolean = false; - #cursorIsHoveringOverAirbase:boolean = false; - #olympusApp:OlympusApp; - #shortcutManager:ShortcutManager; + initialize(app: any) { + this.#app = app; - constructor( ID:string, olympusApp:OlympusApp ) { + this.#shortcutManager = this.#app.getShortcutManager(); - this.#element = document.getElementById( ID ); - - this.#olympusApp = olympusApp; - - this.#shortcutManager = this.#olympusApp.getShortcutManager(); - - this.#shortcutManager.onKeyDown( () => { - this.#updateTips() + this.#shortcutManager.onKeyDown(() => { + this.#updateTips() }); - this.#shortcutManager.onKeyUp( () => { - this.#updateTips() + this.#shortcutManager.onKeyUp(() => { + this.#updateTips() }); - document.addEventListener( "airbaseMouseover", ( ev:CustomEventInit ) => { + document.addEventListener("airbaseMouseover", (ev: CustomEventInit) => { this.#cursorIsHoveringOverAirbase = true; this.#updateTips(); }); - document.addEventListener( "airbaseMouseout", ( ev:CustomEventInit ) => { + document.addEventListener("airbaseMouseout", (ev: CustomEventInit) => { this.#cursorIsHoveringOverAirbase = false; this.#updateTips(); }); - document.addEventListener( "unitDeselection", ( ev:CustomEvent ) => { - this.#updateTips(); - }); + //document.addEventListener("unitDeselection", (ev: CustomEvent) => { + // this.#updateTips(); + //}); - document.addEventListener( "unitMouseover", ( ev:CustomEventInit ) => { + document.addEventListener("unitMouseover", (ev: CustomEventInit) => { this.#cursorIsHoveringOverUnit = true; this.#updateTips(); }); - document.addEventListener( "unitMouseout", ( ev:CustomEventInit ) => { + document.addEventListener("unitMouseout", (ev: CustomEventInit) => { this.#cursorIsHoveringOverUnit = false; this.#updateTips(); }); - document.addEventListener( "unitSelection", ( ev:CustomEvent ) => { - this.#updateTips() - }); + //document.addEventListener("unitSelection", (ev: CustomEvent) => { + // this.#updateTips() + //}); this.#updateTips(); + return true; } getElement() { return this.#element; } - #getOlympusApp() { - return this.#olympusApp; - } - - toggle( bool?:boolean ) { - this.getElement().classList.toggle( "hide", bool ); - this.#olympusApp.getFeatureSwitches().savePreference( "controlTips", !this.getElement().classList.contains( "hide" ) ); + toggle(bool?: boolean) { + this.getElement().classList.toggle("hide", bool); } #updateTips() { - const combos:Array = [ + const combos: Array = [ { "keys": [], "tips": [ @@ -164,7 +163,7 @@ export class ControlTips { ] }, { - "keys": [ "ControlLeft" ], + "keys": ["ControlLeft"], "tips": [ { "key": `Mouse1`, @@ -197,7 +196,7 @@ export class ControlTips { ] }, { - "keys": [ "ShiftLeft" ], + "keys": ["ShiftLeft"], "tips": [ { "key": `mouse1+drag`, @@ -207,48 +206,44 @@ export class ControlTips { } ]; - const currentCombo:any = combos.find( (combo:any) => this.#shortcutManager.keyComboMatches( combo.keys ) ) || combos[0]; + const currentCombo: any = combos.find((combo: any) => this.#shortcutManager.keyComboMatches(combo.keys)) || combos[0]; const element = this.getElement(); element.innerHTML = ""; - const a = this.#getOlympusApp(); - let numSelectedUnits = 0; let unitSelectionContainsControlled = false; - if ( this.#getOlympusApp().getUnitsManager() ) { - let selectedUnits = Object.values( this.#getOlympusApp().getUnitsManager().getSelectedUnits() ); + if (this.#app.getUnitsManager()) { + let selectedUnits = Object.values(this.#app.getUnitsManager().getSelectedUnits()); numSelectedUnits = selectedUnits.length; - unitSelectionContainsControlled = selectedUnits.some( (unit:Unit) => unit.getControlled() ); + unitSelectionContainsControlled = selectedUnits.some((unit: any) => unit.getControlled()); } - - currentCombo.tips.forEach( ( tip:any ) => { - - if ( numSelectedUnits > 0 ) { - if ( tip.showIfUnitSelected === false ) { + currentCombo.tips.forEach((tip: any) => { + if (numSelectedUnits > 0) { + if (tip.showIfUnitSelected === false) { return; } - if ( tip.unitsMustBeControlled === true && unitSelectionContainsControlled === false ) { + if (tip.unitsMustBeControlled === true && unitSelectionContainsControlled === false) { return; } } - if ( numSelectedUnits === 0 && tip.showIfUnitSelected === true ) { + if (numSelectedUnits === 0 && tip.showIfUnitSelected === true) { return; } - if ( typeof tip.showIfHoveringOverAirbase === "boolean" ) { - if ( tip.showIfHoveringOverAirbase !== this.#cursorIsHoveringOverAirbase ) { + if (typeof tip.showIfHoveringOverAirbase === "boolean") { + if (tip.showIfHoveringOverAirbase !== this.#cursorIsHoveringOverAirbase) { return; } } - if ( typeof tip.showIfHoveringOverUnit === "boolean" ) { - if ( tip.showIfHoveringOverUnit !== this.#cursorIsHoveringOverUnit ) { + if (typeof tip.showIfHoveringOverUnit === "boolean") { + if (tip.showIfHoveringOverUnit !== this.#cursorIsHoveringOverUnit) { return; } } @@ -256,8 +251,5 @@ export class ControlTips { element.innerHTML += `
${tip.key}${tip.action}
` }); - - } - } \ No newline at end of file diff --git a/client/plugins/controltips/src/index.ts b/client/plugins/controltips/src/index.ts new file mode 100644 index 00000000..d6372eb6 --- /dev/null +++ b/client/plugins/controltips/src/index.ts @@ -0,0 +1,5 @@ +import { ControlTipsPlugin } from "./controltips"; + +globalThis.getOlympusPlugin = () => { + return new ControlTipsPlugin(); +} \ No newline at end of file diff --git a/client/public/stylesheets/other/controltips.css b/client/plugins/controltips/style.css similarity index 61% rename from client/public/stylesheets/other/controltips.css rename to client/plugins/controltips/style.css index 6ed26c79..0ab48856 100644 --- a/client/public/stylesheets/other/controltips.css +++ b/client/plugins/controltips/style.css @@ -11,23 +11,23 @@ z-index: 999; } -#control-tips-panel > * { +#control-tips-panel>* { align-items: center; align-self: end; - background-color: var( --background-steel ); + background-color: var(--background-steel); border-radius: var(--border-radius-md); color: white; column-gap: 8px; - display:flex; + display: flex; justify-items: right; opacity: .9; - padding:5px; - width:fit-content; + padding: 5px; + width: fit-content; } -#control-tips-panel > * > .key { - background-color: var( --background-grey ); - border-radius: var( --border-radius-sm ); +#control-tips-panel>*>.key { + background-color: var(--background-grey); + border-radius: var(--border-radius-sm); color: white; - padding:1px 4px; + padding: 1px 4px; } \ No newline at end of file diff --git a/client/plugins/controltips/tsconfig.json b/client/plugins/controltips/tsconfig.json new file mode 100644 index 00000000..2ba9ed01 --- /dev/null +++ b/client/plugins/controltips/tsconfig.json @@ -0,0 +1,104 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Language and Environment */ + "target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + "rootDirs": ["./src", "../../@types"], /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + "typeRoots": [ + "./node_modules/@types", + "../../@types" + ], /* Specify multiple folders that act like './node_modules/@types'. */ + "types": [ + "olympus" + ], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + /* JavaScript Support */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": [ + "src/*.ts", + "../../@types/*.d.ts" + ] +} \ No newline at end of file diff --git a/client/public/plugins/controltipsplugin/index.js b/client/public/plugins/controltipsplugin/index.js new file mode 100644 index 00000000..90ee53e6 --- /dev/null +++ b/client/public/plugins/controltipsplugin/index.js @@ -0,0 +1,252 @@ +(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i { + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + }); + __classPrivateFieldGet(this, _ControlTips_shortcutManager, "f").onKeyUp(() => { + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + }); + document.addEventListener("airbaseMouseover", (ev) => { + __classPrivateFieldSet(this, _ControlTips_cursorIsHoveringOverAirbase, true, "f"); + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + }); + document.addEventListener("airbaseMouseout", (ev) => { + __classPrivateFieldSet(this, _ControlTips_cursorIsHoveringOverAirbase, false, "f"); + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + }); + //document.addEventListener("unitDeselection", (ev: CustomEvent) => { + // this.#updateTips(); + //}); + document.addEventListener("unitMouseover", (ev) => { + __classPrivateFieldSet(this, _ControlTips_cursorIsHoveringOverUnit, true, "f"); + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + }); + document.addEventListener("unitMouseout", (ev) => { + __classPrivateFieldSet(this, _ControlTips_cursorIsHoveringOverUnit, false, "f"); + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + }); + //document.addEventListener("unitSelection", (ev: CustomEvent) => { + // this.#updateTips() + //}); + __classPrivateFieldGet(this, _ControlTips_instances, "m", _ControlTips_updateTips).call(this); + return true; + } + getElement() { + return __classPrivateFieldGet(this, _ControlTips_element, "f"); + } + toggle(bool) { + this.getElement().classList.toggle("hide", bool); + } +} +exports.ControlTips = ControlTips; +_ControlTips_element = new WeakMap(), _ControlTips_app = new WeakMap(), _ControlTips_shortcutManager = new WeakMap(), _ControlTips_cursorIsHoveringOverUnit = new WeakMap(), _ControlTips_cursorIsHoveringOverAirbase = new WeakMap(), _ControlTips_instances = new WeakSet(), _ControlTips_updateTips = function _ControlTips_updateTips() { + const combos = [ + { + "keys": [], + "tips": [ + { + "key": `SHIFT`, + "action": `Box select`, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": false, + "showIfUnitSelected": false + }, + { + "key": `Mouse1`, + "action": `Deselect`, + "showIfUnitSelected": true + }, + { + "key": `Mouse1+drag`, + "action": `Move map`, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": false, + "showIfUnitSelected": false + }, + { + "key": `Mouse2`, + "action": `Spawn menu`, + "showIfUnitSelected": false, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": false + }, + { + "key": `Mouse2`, + "action": `Quick options`, + "showIfUnitSelected": false, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": true + }, + { + "key": `Mouse2`, + "action": `Airbase menu`, + "showIfUnitSelected": false, + "showIfHoveringOverAirbase": true, + "showIfHoveringOverUnit": false + }, + { + "key": `Mouse2`, + "action": `Set first waypoint`, + "showIfHoveringOverAirbase": false, + "showIfUnitSelected": true, + "unitsMustBeControlled": true + }, + { + "key": "CTRL+Mouse2", + "action": "Add waypoint", + "showIfUnitSelected": true, + "showIfHoveringOverAirbase": false, + "unitsMustBeControlled": true + }, + { + "key": `Mouse2 (hold)`, + "action": `Point operations`, + "showIfUnitSelected": true, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": false, + "unitsMustBeControlled": true + }, + { + "key": "CTRL", + "action": " Pin tool", + "showIfUnitSelected": false, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": false, + "unitsMustBeControlled": true + }, + { + "key": "CTRL+Mouse2", + "action": " Airbase menu", + "showIfUnitSelected": true, + "showIfHoveringOverAirbase": true, + "unitsMustBeControlled": true + }, + { + "key": `Delete`, + "action": `Delete unit`, + "showIfHoveringOverAirbase": false, + "showIfUnitSelected": true + } + ] + }, + { + "keys": ["ControlLeft"], + "tips": [ + { + "key": `Mouse1`, + "action": "Toggle pin", + "showIfUnitSelected": false, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": false + }, + { + "key": `Mouse1`, + "action": "Toggle selection", + "showIfUnitSelected": true, + "showIfHoveringOverAirbase": false, + "showIfHoveringOverUnit": true + }, + { + "key": `Mouse2`, + "action": `Add waypoint`, + "showIfHoveringOverAirbase": false, + "showIfUnitSelected": true, + "unitsMustBeControlled": true + }, + { + "key": `Mouse2`, + "action": `Airbase menu`, + "showIfHoveringOverAirbase": true, + "showIfUnitSelected": true, + "unitsMustBeControlled": true + } + ] + }, + { + "keys": ["ShiftLeft"], + "tips": [ + { + "key": `mouse1+drag`, + "action": "Box select" + } + ] + } + ]; + const currentCombo = combos.find((combo) => __classPrivateFieldGet(this, _ControlTips_shortcutManager, "f").keyComboMatches(combo.keys)) || combos[0]; + const element = this.getElement(); + element.innerHTML = ""; + let numSelectedUnits = 0; + let unitSelectionContainsControlled = false; + if (__classPrivateFieldGet(this, _ControlTips_app, "f").getUnitsManager()) { + let selectedUnits = Object.values(__classPrivateFieldGet(this, _ControlTips_app, "f").getUnitsManager().getSelectedUnits()); + numSelectedUnits = selectedUnits.length; + unitSelectionContainsControlled = selectedUnits.some((unit) => unit.getControlled()); + } + currentCombo.tips.forEach((tip) => { + if (numSelectedUnits > 0) { + if (tip.showIfUnitSelected === false) { + return; + } + if (tip.unitsMustBeControlled === true && unitSelectionContainsControlled === false) { + return; + } + } + if (numSelectedUnits === 0 && tip.showIfUnitSelected === true) { + return; + } + if (typeof tip.showIfHoveringOverAirbase === "boolean") { + if (tip.showIfHoveringOverAirbase !== __classPrivateFieldGet(this, _ControlTips_cursorIsHoveringOverAirbase, "f")) { + return; + } + } + if (typeof tip.showIfHoveringOverUnit === "boolean") { + if (tip.showIfHoveringOverUnit !== __classPrivateFieldGet(this, _ControlTips_cursorIsHoveringOverUnit, "f")) { + return; + } + } + element.innerHTML += `
${tip.key}${tip.action}
`; + }); +}; + +},{}],2:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const controltips_1 = require("./controltips"); +globalThis.getOlympusPlugin = () => { + return new controltips_1.ControlTips(); +}; + +},{"./controltips":1}]},{},[2]); diff --git a/client/public/plugins/controltipsplugin/plugin.json b/client/public/plugins/controltipsplugin/plugin.json new file mode 100644 index 00000000..77a1f817 --- /dev/null +++ b/client/public/plugins/controltipsplugin/plugin.json @@ -0,0 +1,6 @@ +{ + "name": "Control Tip Plugin", + "version": "0.0.1", + "description": "This plugin shows useful control tips on the right side of the screen. The tips change dynamically depending on what the user does", + "author": "Peekaboo" +} \ No newline at end of file diff --git a/client/public/plugins/controltipsplugin/style.css b/client/public/plugins/controltipsplugin/style.css new file mode 100644 index 00000000..0ab48856 --- /dev/null +++ b/client/public/plugins/controltipsplugin/style.css @@ -0,0 +1,33 @@ +#control-tips-panel { + align-self: center; + display: flex; + flex-flow: column wrap; + font-size: 13px; + justify-self: flex-end; + position: absolute; + right: 10px; + row-gap: 20px; + text-align: right; + z-index: 999; +} + +#control-tips-panel>* { + align-items: center; + align-self: end; + background-color: var(--background-steel); + border-radius: var(--border-radius-md); + color: white; + column-gap: 8px; + display: flex; + justify-items: right; + opacity: .9; + padding: 5px; + width: fit-content; +} + +#control-tips-panel>*>.key { + background-color: var(--background-grey); + border-radius: var(--border-radius-sm); + color: white; + padding: 1px 4px; +} \ No newline at end of file diff --git a/client/public/stylesheets/leaflet/leaflet.css b/client/public/stylesheets/leaflet/leaflet.css index 9ade8dc4..1981009f 100644 --- a/client/public/stylesheets/leaflet/leaflet.css +++ b/client/public/stylesheets/leaflet/leaflet.css @@ -60,11 +60,6 @@ padding: 0; } -.leaflet-container img.leaflet-tile { - /* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */ - mix-blend-mode: plus-lighter; -} - .leaflet-container.leaflet-touch-zoom { -ms-touch-action: pan-x pan-y; touch-action: pan-x pan-y; @@ -651,7 +646,7 @@ svg.leaflet-image-layer.leaflet-interactive path { } /* Printing */ - + @media print { /* Prevent printers from removing background-images of controls. */ .leaflet-control { diff --git a/client/routes/api/atc.js b/client/routes/api/atc.js index 2429cdce..b0896b15 100644 --- a/client/routes/api/atc.js +++ b/client/routes/api/atc.js @@ -1,82 +1,65 @@ -var express = require('express'); -var app = express(); +var express = require('express'); +var app = express(); const bodyParser = require('body-parser'); -app.use(bodyParser.urlencoded({ extended: false})); +app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); - -/* - - Flight: - "name" - "take-off time" - "priority" - "status" - -//*/ - function uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } - - - -function Flight( name, boardId, unitId ) { +function Flight(name, boardId, unitId) { this.assignedAltitude = 0; - this.assignedSpeed = 0; - this.id = uuidv4(); - this.boardId = boardId; - this.name = name; - this.status = "unknown"; - this.takeoffTime = -1; - this.unitId = parseInt( unitId ); + this.assignedSpeed = 0; + this.id = uuidv4(); + this.boardId = boardId; + this.name = name; + this.status = "unknown"; + this.takeoffTime = -1; + this.unitId = parseInt(unitId); } -Flight.prototype.getData = function() { +Flight.prototype.getData = function () { return { - "assignedAltitude" : this.assignedAltitude, - "assignedSpeed" : this.assignedSpeed, - "id" : this.id, - "boardId" : this.boardId, - "name" : this.name, - "status" : this.status, - "takeoffTime" : this.takeoffTime, - "unitId" : this.unitId + "assignedAltitude": this.assignedAltitude, + "assignedSpeed": this.assignedSpeed, + "id": this.id, + "boardId": this.boardId, + "name": this.name, + "status": this.status, + "takeoffTime": this.takeoffTime, + "unitId": this.unitId }; } +Flight.prototype.setAssignedAltitude = function (assignedAltitude) { -Flight.prototype.setAssignedAltitude = function( assignedAltitude ) { - - if ( isNaN( assignedAltitude ) ) { + if (isNaN(assignedAltitude)) { return "Altitude must be a number" } - this.assignedAltitude = parseInt( assignedAltitude ); + this.assignedAltitude = parseInt(assignedAltitude); return true; } +Flight.prototype.setAssignedSpeed = function (assignedSpeed) { -Flight.prototype.setAssignedSpeed = function( assignedSpeed ) { - - if ( isNaN( assignedSpeed ) ) { + if (isNaN(assignedSpeed)) { return "Speed must be a number" } - this.assignedSpeed = parseInt( assignedSpeed ); + this.assignedSpeed = parseInt(assignedSpeed); return true; } - -Flight.prototype.setOrder = function( order ) { +Flight.prototype.setOrder = function (order) { this.order = order; @@ -84,10 +67,9 @@ Flight.prototype.setOrder = function( order ) { } +Flight.prototype.setStatus = function (status) { -Flight.prototype.setStatus = function( status ) { - - if ( [ "unknown", "checkedin", "readytotaxi", "clearedtotaxi", "halted", "terminated" ].indexOf( status ) < 0 ) { + if (["unknown", "checkedin", "readytotaxi", "clearedtotaxi", "halted", "terminated"].indexOf(status) < 0) { return "Invalid status"; } @@ -97,197 +79,186 @@ Flight.prototype.setStatus = function( status ) { } +Flight.prototype.setTakeoffTime = function (takeoffTime) { -Flight.prototype.setTakeoffTime = function( takeoffTime ) { - - if ( takeoffTime === "" || takeoffTime === -1 ) { + if (takeoffTime === "" || takeoffTime === -1) { this.takeoffTime = -1; } - if ( isNaN( takeoffTime ) ) { + if (isNaN(takeoffTime)) { return "Invalid takeoff time" } - this.takeoffTime = parseInt( takeoffTime ); + this.takeoffTime = parseInt(takeoffTime); return true; } - - -function ATCDataHandler( data ) { +function ATCDataHandler(data) { this.data = data; } -ATCDataHandler.prototype.addFlight = function( flight ) { +ATCDataHandler.prototype.addFlight = function (flight) { - if ( flight instanceof Flight === false ) { - throw new Error( "Given flight is not an instance of Flight" ); + if (flight instanceof Flight === false) { + throw new Error("Given flight is not an instance of Flight"); } - this.data.flights[ flight.id ] = flight; + this.data.flights[flight.id] = flight; } - -ATCDataHandler.prototype.deleteFlight = function( flightId ) { - delete this.data.flights[ flightId ]; +ATCDataHandler.prototype.deleteFlight = function (flightId) { + delete this.data.flights[flightId]; } - -ATCDataHandler.prototype.getFlight = function( flightId ) { - return this.data.flights[ flightId ] || false; +ATCDataHandler.prototype.getFlight = function (flightId) { + return this.data.flights[flightId] || false; } - -ATCDataHandler.prototype.getFlights = function() { +ATCDataHandler.prototype.getFlights = function () { return this.data.flights; } - -const dataHandler = new ATCDataHandler( { +const dataHandler = new ATCDataHandler({ "flights": {} -} ); - - +}); /**************************************************************************************************************/ // Endpoints /**************************************************************************************************************/ +app.get("/flight", (req, res) => { + let flights = Object.values(dataHandler.getFlights()); -app.get( "/flight", ( req, res ) => { + if (flights && req.query.boardId) { - let flights = Object.values( dataHandler.getFlights() ); - - if ( flights && req.query.boardId ) { - - flights = flights.reduce( ( acc, flight ) => { - if ( flight.boardId === req.query.boardId ) { - acc[ flight.id ] = flight; + flights = flights.reduce((acc, flight) => { + if (flight.boardId === req.query.boardId) { + acc[flight.id] = flight; } return acc; - }, {} ); + }, {}); } - res.json( flights ); + res.json(flights); }); -app.patch( "/flight/:flightId", ( req, res ) => { +app.patch("/flight/:flightId", (req, res) => { const flightId = req.params.flightId; - const flight = dataHandler.getFlight( flightId ); + const flight = dataHandler.getFlight(flightId); - if ( !flight ) { - res.status( 400 ).send( `Unrecognised flight ID (given: "${req.params.flightId}")` ); + if (!flight) { + res.status(400).send(`Unrecognised flight ID (given: "${req.params.flightId}")`); } - if ( req.body.hasOwnProperty( "assignedAltitude" ) ) { + if (req.body.hasOwnProperty("assignedAltitude")) { - const altitudeChangeSuccess = flight.setAssignedAltitude( req.body.assignedAltitude ); + const altitudeChangeSuccess = flight.setAssignedAltitude(req.body.assignedAltitude); - if ( altitudeChangeSuccess !== true ) { - res.status( 400 ).send( altitudeChangeSuccess ); + if (altitudeChangeSuccess !== true) { + res.status(400).send(altitudeChangeSuccess); } } - if ( req.body.hasOwnProperty( "assignedSpeed" ) ) { + if (req.body.hasOwnProperty("assignedSpeed")) { - const speedChangeSuccess = flight.setAssignedSpeed( req.body.assignedSpeed ); + const speedChangeSuccess = flight.setAssignedSpeed(req.body.assignedSpeed); - if ( speedChangeSuccess !== true ) { - res.status( 400 ).send( speedChangeSuccess ); + if (speedChangeSuccess !== true) { + res.status(400).send(speedChangeSuccess); } } - if ( req.body.status ) { + if (req.body.status) { - const statusChangeSuccess = flight.setStatus( req.body.status ); + const statusChangeSuccess = flight.setStatus(req.body.status); - if ( statusChangeSuccess !== true ) { - res.status( 400 ).send( statusChangeSuccess ); + if (statusChangeSuccess !== true) { + res.status(400).send(statusChangeSuccess); } } - if ( req.body.hasOwnProperty( "takeoffTime" ) ) { + if (req.body.hasOwnProperty("takeoffTime")) { - const takeoffChangeSuccess = flight.setTakeoffTime( req.body.takeoffTime ); + const takeoffChangeSuccess = flight.setTakeoffTime(req.body.takeoffTime); - if ( takeoffChangeSuccess !== true ) { - res.status( 400 ).send( takeoffChangeSuccess ); + if (takeoffChangeSuccess !== true) { + res.status(400).send(takeoffChangeSuccess); } } - res.json( flight.getData() ); + res.json(flight.getData()); }); -app.post( "/flight/order", ( req, res ) => { +app.post("/flight/order", (req, res) => { - if ( !req.body.boardId ) { - res.status( 400 ).send( "Invalid/missing boardId" ); + if (!req.body.boardId) { + res.status(400).send("Invalid/missing boardId"); } - if ( !req.body.order || !Array.isArray( req.body.order ) ) { - res.status( 400 ).send( "Invalid/missing boardId" ); + if (!req.body.order || !Array.isArray(req.body.order)) { + res.status(400).send("Invalid/missing boardId"); } - req.body.order.forEach( ( flightId, i ) => { + req.body.order.forEach((flightId, i) => { - dataHandler.getFlight( flightId ).setOrder( i ); + dataHandler.getFlight(flightId).setOrder(i); }); - res.send( "" ); + res.send(""); }); -app.post( "/flight", ( req, res ) => { +app.post("/flight", (req, res) => { - if ( !req.body.boardId ) { - res.status( 400 ).send( "Invalid/missing boardId" ); + if (!req.body.boardId) { + res.status(400).send("Invalid/missing boardId"); } - if ( !req.body.name ) { - res.status( 400 ).send( "Invalid/missing flight name" ); + if (!req.body.name) { + res.status(400).send("Invalid/missing flight name"); } - if ( !req.body.unitId || isNaN( req.body.unitId ) ) { - res.status( 400 ).send( "Invalid/missing unitId" ); + if (!req.body.unitId || isNaN(req.body.unitId)) { + res.status(400).send("Invalid/missing unitId"); } - const flight = new Flight( req.body.name, req.body.boardId, req.body.unitId ); + const flight = new Flight(req.body.name, req.body.boardId, req.body.unitId); - dataHandler.addFlight( flight ); + dataHandler.addFlight(flight); - res.status( 201 ); + res.status(201); - res.json( flight.getData() ); + res.json(flight.getData()); }); -app.delete( "/flight/:flightId", ( req, res ) => { +app.delete("/flight/:flightId", (req, res) => { - const flight = dataHandler.getFlight( req.params.flightId ); + const flight = dataHandler.getFlight(req.params.flightId); - if ( !flight ) { - res.status( 400 ).send( `Unrecognised flight ID (given: "${req.params.flightId}")` ); + if (!flight) { + res.status(400).send(`Unrecognised flight ID (given: "${req.params.flightId}")`); } - dataHandler.deleteFlight( req.params.flightId ); + dataHandler.deleteFlight(req.params.flightId); - res.status( 204 ).send( "" ); + res.status(204).send(""); }); diff --git a/client/routes/plugins.js b/client/routes/plugins.js new file mode 100644 index 00000000..1ed9511f --- /dev/null +++ b/client/routes/plugins.js @@ -0,0 +1,23 @@ +const express = require('express'); +const fs = require('fs'); +const path = require('path'); + +const pluginsDirectory = "./public/plugins" + +const router = express.Router(); + +function listDirectories(source) { + const directories = fs.readdirSync(source, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name); + + return directories; +} + +router.get('/list', function (req, res) { + var directories = listDirectories(pluginsDirectory); + console.log(directories) + res.send(directories.filter(directory => fs.existsSync(path.join(pluginsDirectory, directory)))); +}); + +module.exports = router; diff --git a/client/routes/resources.js b/client/routes/resources.js index 2be3af2f..308243bc 100644 --- a/client/routes/resources.js +++ b/client/routes/resources.js @@ -1,6 +1,7 @@ const express = require('express'); const router = express.Router(); +// TODO should be user selectable or at least configurable from configuration file var theme = "olympus"; router.get('/theme/*', function (req, res, next) { diff --git a/client/src/@types/server.d.ts b/client/src/@types/server.d.ts deleted file mode 100644 index 851bfd53..00000000 --- a/client/src/@types/server.d.ts +++ /dev/null @@ -1,52 +0,0 @@ -interface AirbasesData { - airbases: {[key: string]: any}, - sessionHash: string; - time: number; -} - -interface BullseyesData { - bullseyes: {[key: string]: {latitude: number, longitude: number, coalition: string}}, - sessionHash: string; - time: number; -} - -interface MissionData { - mission: { - theatre: string, - dateAndTime: DateAndTime; - commandModeOptions: CommandModeOptions; - coalitions: {red: string[], blue: string[]} = {}; - } - time: number; - sessionHash: string; -} - -interface CommandModeOptions { - commandMode: string; - restrictSpawns: boolean; - restrictToCoalition: boolean; - setupTime: number; - spawnPoints: { - red: number, - blue: number - }, - eras: string[] -} - -interface DateAndTime { - date: {Year: number, Month: number, Day: number}; - time: {h: number, m: number, s: number}; - elapsedTime: number; - startTime: number; -} - -interface LogData { - logs: {[key: string]: string}, - sessionHash: string; - time: number; -} - -interface ServerRequestOptions { - time?: number; - commandHash?: string; -} \ No newline at end of file diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts deleted file mode 100644 index 90c7266c..00000000 --- a/client/src/@types/unit.d.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { LatLng } from "leaflet" - -interface UnitSpawnTable { - unitType: string, - location: latlng, - altitude?: number, - loadout?: string, - liveryID: string -} - -interface ObjectIconOptions { - showState: boolean, - showVvi: boolean, - showHotgroup: boolean, - showUnitIcon: boolean, - showShortLabel: boolean, - showFuel: boolean, - showAmmo: boolean, - showSummary: boolean, - showCallsign: boolean, - rotateToHeading: boolean -} - -interface GeneralSettings { - prohibitJettison: boolean; - prohibitAA: boolean; - prohibitAG: boolean; - prohibitAfterburner: boolean; - prohibitAirWpn: boolean; -} - -interface TACAN { - isOn: boolean; - channel: number; - XY: string; - callsign: string; -} - -interface Radio { - frequency: number; - callsign: number; - callsignNumber: number; -} - -interface Ammo { - quantity: number, - name: string, - guidance: number, - category: number, - missileCategory: number -} - -interface Contact { - ID: number, - detectionMethod: number -} - -interface Offset { - x: number, - y: number, - z: number -} - -interface UnitData { - category: string, - ID: number; - alive: boolean; - human: boolean; - controlled: boolean; - coalition: string; - country: number; - name: string; - unitName: string; - groupName: string; - state: string; - task: string; - hasTask: boolean; - position: LatLng; - speed: number; - heading: number; - isTanker: boolean; - isAWACS: boolean; - onOff: boolean; - followRoads: boolean; - fuel: number; - desiredSpeed: number; - desiredSpeedType: string; - desiredAltitude: number; - desiredAltitudeType: string; - leaderID: number; - formationOffset: Offset; - targetID: number; - targetPosition: LatLng; - ROE: string; - reactionToThreat: string; - emissionsCountermeasures: string; - TACAN: TACAN; - radio: Radio; - generalSettings: GeneralSettings; - ammo: Ammo[]; - contacts: Contact[]; - activePath: LatLng[]; - isLeader: boolean; -} \ No newline at end of file diff --git a/client/src/@types/unitdatabase.d.ts b/client/src/@types/unitdatabase.d.ts deleted file mode 100644 index 08c9efbd..00000000 --- a/client/src/@types/unitdatabase.d.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { LatLng } from "leaflet"; -import { Airbase } from "../mission/airbase"; - -interface LoadoutItemBlueprint { - name: string; - quantity: number; - effectiveAgainst?: string; -} - -interface LoadoutBlueprint { - fuel: number; - items: LoadoutItemBlueprint[]; - roles: string[]; - code: string; - name: string; -} - -interface UnitBlueprint { - name: string; - coalition: string; - era: string; - label: string; - shortLabel: string; - type?: string; - range?: string; - loadouts?: LoadoutBlueprint[]; - filename?: string; - liveries?: {[key: string]: {name: string, countries: string[]}}; - cost?: number; -} - -interface UnitSpawnOptions { - roleType: string; - name: string; - latlng: LatLng; - coalition: string; - count: number; - country: string; - loadout: LoadoutBlueprint | undefined; - airbase: Airbase | undefined; - liveryID: string | undefined; - altitude: number | undefined; -} diff --git a/client/src/app.ts b/client/src/app.ts new file mode 100644 index 00000000..3010a884 --- /dev/null +++ b/client/src/app.ts @@ -0,0 +1,151 @@ +import { Map } from "./map/map"; +import { MissionManager } from "./mission/missionmanager"; +import { ConnectionStatusPanel } from "./panels/connectionstatuspanel"; +import { HotgroupPanel } from "./panels/hotgrouppanel"; +import { LogPanel } from "./panels/logpanel"; +import { MouseInfoPanel } from "./panels/mouseinfopanel"; +import { ServerStatusPanel } from "./panels/serverstatuspanel"; +import { UnitControlPanel } from "./panels/unitcontrolpanel"; +import { UnitInfoPanel } from "./panels/unitinfopanel"; +import { PluginsManager } from "./plugin/pluginmanager"; +import { Popup } from "./popups/popup"; +import { ShortcutManager } from "./shortcut/shortcutmanager"; +import { CommandModeToolbar } from "./toolbars/commandmodetoolbar"; +import { PrimaryToolbar } from "./toolbars/primarytoolbar"; +import { UnitsManager } from "./unit/unitsmanager"; +import { WeaponsManager } from "./weapon/weaponsmanager"; + +import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants"; +import { Manager } from "./other/manager"; + +export class OlympusApp { + /* Global data */ + #activeCoalition: string = "blue"; + + /* Main leaflet map, extended by custom methods */ + #map: Map | null = null; + + /* Managers */ + #unitsManager: UnitsManager | null = null; + #weaponsManager: WeaponsManager | null = null; + #missionManager: MissionManager | null = null; + #pluginsManager: PluginsManager | null = null; + #panelsManager: Manager | null = null; + #popupsManager: Manager | null = null; + #toolbarsManager: Manager | null = null; + #shortcutManager: ShortcutManager | null = null; + + /* UI Toolbars */ + #primaryToolbar: PrimaryToolbar| null = null; + #commandModeToolbar: CommandModeToolbar| null = null; + + constructor() { + + } + + getMap() { + return this.#map as Map; + } + + getPanelsManager() { + return this.#panelsManager as Manager; + } + + getPopupsManager() { + return this.#popupsManager as Manager; + } + + getToolbarsManager() { + return this.#toolbarsManager as Manager; + } + + getShortcutManager() { + return this.#shortcutManager as ShortcutManager; + } + + getUnitsManager() { + return this.#unitsManager as UnitsManager; + } + + getWeaponsManager() { + return this.#weaponsManager as WeaponsManager; + } + + getMissionManager() { + return this.#missionManager as MissionManager; + } + + getPluginsManager() { + return this.#pluginsManager as PluginsManager; + } + + /** Set the active coalition, i.e. the currently controlled coalition. A game master can change the active coalition, while a commander is bound to his/her coalition + * + * @param newActiveCoalition + */ + setActiveCoalition(newActiveCoalition: string) { + if (this.getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER) + this.#activeCoalition = newActiveCoalition; + } + + /** + * + * @returns The active coalition + */ + getActiveCoalition() { + if (this.getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER) + return this.#activeCoalition; + else { + if (this.getMissionManager().getCommandModeOptions().commandMode == BLUE_COMMANDER) + return "blue"; + else if (this.getMissionManager().getCommandModeOptions().commandMode == RED_COMMANDER) + return "red"; + else + return "neutral"; + } + } + + /** Set a message in the login splash screen + * + * @param status The message to show in the login splash screen + */ + setLoginStatus(status: string) { + const el = document.querySelector("#login-status") as HTMLElement; + if (el) + el.dataset["status"] = status; + } + + start() { + /* Initialize base functionalitites */ + this.#map = new Map('map-container'); + + this.#unitsManager = new UnitsManager(); + this.#weaponsManager = new WeaponsManager(); + this.#missionManager = new MissionManager(); + + this.#shortcutManager = new ShortcutManager(); + + this.#panelsManager = new Manager(); + this.#popupsManager = new Manager(); + this.#toolbarsManager = new Manager(); + + // Panels + this.getPanelsManager() + .add("connectionStatus", new ConnectionStatusPanel("connection-status-panel")) + .add("hotgroup", new HotgroupPanel("hotgroup-panel")) + .add("mouseInfo", new MouseInfoPanel("mouse-info-panel")) + .add("log", new LogPanel("log-panel")) + .add("serverStatus", new ServerStatusPanel("server-status-panel")) + .add("unitControl", new UnitControlPanel("unit-control-panel")) + .add("unitInfo", new UnitInfoPanel("unit-info-panel")) + + // Popups + this.getPopupsManager().add("infoPopup", new Popup("info-popup")); + + // Toolbars + this.getToolbarsManager().add("primaryToolbar", new PrimaryToolbar("primary-toolbar")) + .add("commandModeToolbar", new PrimaryToolbar("command-mode-toolbar")); + + this.#pluginsManager = new PluginsManager(); + } +} \ No newline at end of file diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index 4bfac3c3..20bbbeeb 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -106,7 +106,7 @@ export const layers = { urlTemplate: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", maxZoom: 20, minZoom: 1, - attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community" + attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, GetApp().getMap()ping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community" }, "USGS Topo": { urlTemplate: 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}', diff --git a/client/src/contextmenus/airbasecontextmenu.ts b/client/src/contextmenus/airbasecontextmenu.ts index 5215464a..b8fb6209 100644 --- a/client/src/contextmenus/airbasecontextmenu.ts +++ b/client/src/contextmenus/airbasecontextmenu.ts @@ -1,4 +1,4 @@ -import { getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from ".."; +import { getApp } from ".."; import { GAME_MASTER } from "../constants/constants"; import { Airbase } from "../mission/airbase"; import { dataPointMap } from "../other/utils"; @@ -23,7 +23,7 @@ export class AirbaseContextMenu extends ContextMenu { document.addEventListener("contextMenuLandAirbase", (e: any) => { if (this.#airbase) - getUnitsManager().selectedUnitsLandAt(this.#airbase.getLatLng()); + getApp().getUnitsManager().selectedUnitsLandAt(this.#airbase.getLatLng()); this.hide(); }) } @@ -39,8 +39,8 @@ export class AirbaseContextMenu extends ContextMenu { this.#setProperties(this.#airbase.getProperties()); this.#setParkings(this.#airbase.getParkings()); this.#setCoalition(this.#airbase.getCoalition()); - this.#showLandButton(getUnitsManager().getSelectedUnitsCategories().length == 1 && ["Aircraft", "Helicopter"].includes(getUnitsManager().getSelectedUnitsCategories()[0]) && (getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral")) - this.#showSpawnButton(getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getMissionHandler().getCommandedCoalition()); + this.#showLandButton(getApp().getUnitsManager().getSelectedUnitsCategories().length == 1 && ["Aircraft", "Helicopter"].includes(getApp().getUnitsManager().getSelectedUnitsCategories()[0]) && (getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral")) + this.#showSpawnButton(getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getApp().getMissionManager().getCommandedCoalition()); this.#setAirbaseData(); this.clip(); @@ -109,8 +109,8 @@ export class AirbaseContextMenu extends ContextMenu { */ #showSpawnMenu() { if (this.#airbase != null) { - setActiveCoalition(this.#airbase.getCoalition()); - getMap().showAirbaseSpawnMenu(this.getX(), this.getY(), this.getLatLng(), this.#airbase); + getApp().setActiveCoalition(this.#airbase.getCoalition()); + getApp().getMap().showAirbaseSpawnMenu(this.getX(), this.getY(), this.getLatLng(), this.#airbase); } } @@ -135,11 +135,11 @@ export class AirbaseContextMenu extends ContextMenu { if ( runways.length === 0 ) { runwaysContainer.innerText = "No data"; } else { - runways.forEach( runway => { + runways.forEach( (runway: AirbaseChartRunwayData) => { let runwayDiv = document.createElement( "div" ); runwayDiv.classList.add( "runway" ); - runway.headings.forEach( headings => { + runway.headings.forEach( (headings: AirbaseChartRunwayHeadingData) => { for ( const [ heading, data ] of Object.entries( headings ) ) { let headingDiv = document.createElement( "div" ); diff --git a/client/src/contextmenus/airbasespawnmenu.ts b/client/src/contextmenus/airbasespawnmenu.ts index 4266a733..a5c40e3c 100644 --- a/client/src/contextmenus/airbasespawnmenu.ts +++ b/client/src/contextmenus/airbasespawnmenu.ts @@ -1,8 +1,8 @@ import { LatLng } from "leaflet"; -import { getActiveCoalition } from ".."; import { ContextMenu } from "./contextmenu"; import { AircraftSpawnMenu, HelicopterSpawnMenu } from "../controls/unitspawnmenu"; import { Airbase } from "../mission/airbase"; +import { getApp } from ".."; /** This context menu is shown when the user wants to spawn a new aircraft or helicopter from the ground at an airbase. * It is shown by clicking on the "spawn" button of a AirbaseContextMenu. */ @@ -54,7 +54,7 @@ export class AirbaseSpawnContextMenu extends ContextMenu { this.#aircraftSpawnMenu.setCountries(); this.#helicopterSpawnMenu.setCountries(); - this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) }); + this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) }); } /** Sets the airbase at which the new unit will be spawned diff --git a/client/src/contextmenus/coalitionareacontextmenu.ts b/client/src/contextmenus/coalitionareacontextmenu.ts index 94a88e36..375355cc 100644 --- a/client/src/contextmenus/coalitionareacontextmenu.ts +++ b/client/src/contextmenus/coalitionareacontextmenu.ts @@ -1,5 +1,5 @@ import { LatLng } from "leaflet"; -import { getMap, getMissionHandler, getUnitsManager } from ".."; +import { getApp } from ".."; import { GAME_MASTER, IADSTypes } from "../constants/constants"; import { CoalitionArea } from "../map/coalitionarea/coalitionarea"; import { ContextMenu } from "./contextmenu"; @@ -54,20 +54,20 @@ export class CoalitionAreaContextMenu extends ContextMenu { document.addEventListener("coalitionAreaBringToBack", (e: any) => { if (this.#coalitionArea) - getMap().bringCoalitionAreaToBack(this.#coalitionArea); - getMap().hideCoalitionAreaContextMenu(); + getApp().getMap().bringCoalitionAreaToBack(this.#coalitionArea); + getApp().getMap().hideCoalitionAreaContextMenu(); }); document.addEventListener("coalitionAreaDelete", (e: any) => { if (this.#coalitionArea) - getMap().deleteCoalitionArea(this.#coalitionArea); - getMap().hideCoalitionAreaContextMenu(); + getApp().getMap().deleteCoalitionArea(this.#coalitionArea); + getApp().getMap().hideCoalitionAreaContextMenu(); }); document.addEventListener("contextMenuCreateIads", (e: any) => { const area = this.getCoalitionArea(); if (area) - getUnitsManager().createIADS(area, getCheckboxOptions(this.#iadsTypesDropdown), getCheckboxOptions(this.#iadsErasDropdown), getCheckboxOptions(this.#iadsRangesDropdown), this.#iadsDensitySlider.getValue(), this.#iadsDistributionSlider.getValue()); + getApp().getUnitsManager().createIADS(area, getCheckboxOptions(this.#iadsTypesDropdown), getCheckboxOptions(this.#iadsErasDropdown), getCheckboxOptions(this.#iadsRangesDropdown), this.#iadsDensitySlider.getValue(), this.#iadsDistributionSlider.getValue()); }) this.hide(); } @@ -97,7 +97,7 @@ export class CoalitionAreaContextMenu extends ContextMenu { return createCheckboxOption(range, `Add ${range} units to the IADS`); })); - if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) + if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER) this.#coalitionSwitch.hide() } @@ -149,7 +149,7 @@ export class CoalitionAreaContextMenu extends ContextMenu { * @param value Switch position (false: blue, true: red) */ #onSwitchClick(value: boolean) { - if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER) { + if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER) { this.getCoalitionArea()?.setCoalition(value ? "red" : "blue"); this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition()) diff --git a/client/src/contextmenus/mapcontextmenu.ts b/client/src/contextmenus/mapcontextmenu.ts index b35c1705..2d86772c 100644 --- a/client/src/contextmenus/mapcontextmenu.ts +++ b/client/src/contextmenus/mapcontextmenu.ts @@ -1,5 +1,5 @@ import { LatLng } from "leaflet"; -import { getActiveCoalition, getMap, getMissionHandler, setActiveCoalition } from ".."; +import { getApp } from ".."; import { spawnExplosion, spawnSmoke } from "../server/server"; import { ContextMenu } from "./contextmenu"; import { Switch } from "../controls/switch"; @@ -50,7 +50,7 @@ export class MapContextMenu extends ContextMenu { this.hide(); spawnSmoke(e.detail.color, this.getLatLng()); var marker = new SmokeMarker(this.getLatLng(), e.detail.color); - marker.addTo(getMap()); + marker.addTo(getApp().getMap()); }); document.addEventListener("contextMenuExplosion", (e: any) => { @@ -61,7 +61,7 @@ export class MapContextMenu extends ContextMenu { document.addEventListener("editCoalitionArea", (e: any) => { this.hide(); if (this.#coalitionArea) { - getMap().deselectAllCoalitionAreas(); + getApp().getMap().deselectAllCoalitionAreas(); this.#coalitionArea.setSelected(true); } }); @@ -103,13 +103,13 @@ export class MapContextMenu extends ContextMenu { this.#navyUnitSpawnMenu.setCountries(); /* Only a Game Master can choose the coalition of a new unit */ - if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) + if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER) this.#coalitionSwitch.hide() - this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) }); - if (getActiveCoalition() == "blue") + this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) }); + if (getApp().getActiveCoalition() == "blue") this.#coalitionSwitch.setValue(false); - else if (getActiveCoalition() == "red") + else if (getApp().getActiveCoalition() == "red") this.#coalitionSwitch.setValue(true); else this.#coalitionSwitch.setValue(undefined); @@ -199,8 +199,8 @@ export class MapContextMenu extends ContextMenu { * @param value Switch position (false: "blue", true: "red") */ #onSwitchClick(value: boolean) { - value ? setActiveCoalition("red") : setActiveCoalition("blue"); - this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) }); + value ? getApp().setActiveCoalition("red") : getApp().setActiveCoalition("blue"); + this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) }); this.#aircraftSpawnMenu.setCountries(); this.#helicopterSpawnMenu.setCountries(); this.#groundUnitSpawnMenu.setCountries(); @@ -212,8 +212,8 @@ export class MapContextMenu extends ContextMenu { */ #onSwitchRightClick() { this.#coalitionSwitch.setValue(undefined); - setActiveCoalition("neutral"); - this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) }); + getApp().setActiveCoalition("neutral"); + this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) }); this.#aircraftSpawnMenu.setCountries(); this.#helicopterSpawnMenu.setCountries(); this.#groundUnitSpawnMenu.setCountries(); diff --git a/client/src/controls/unitspawnmenu.ts b/client/src/controls/unitspawnmenu.ts index 083806e7..a2702dd8 100644 --- a/client/src/controls/unitspawnmenu.ts +++ b/client/src/controls/unitspawnmenu.ts @@ -2,16 +2,14 @@ import { LatLng } from "leaflet"; import { Dropdown } from "./dropdown"; import { Slider } from "./slider"; import { UnitDatabase } from "../unit/databases/unitdatabase"; -import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager } from ".."; +import { getApp } from ".."; import { GAME_MASTER } from "../constants/constants"; -import { UnitSpawnOptions } from "../@types/unitdatabase"; import { Airbase } from "../mission/airbase"; import { ftToM } from "../other/utils"; import { aircraftDatabase } from "../unit/databases/aircraftdatabase"; import { helicopterDatabase } from "../unit/databases/helicopterdatabase"; import { groundUnitDatabase } from "../unit/databases/groundunitdatabase"; import { navyUnitDatabase } from "../unit/databases/navyunitdatabase"; -import { UnitSpawnTable } from "../@types/unit"; export class UnitSpawnMenu { #container: HTMLElement; @@ -209,8 +207,8 @@ export class UnitSpawnMenu { } setCountries() { - var coalitions = getMissionHandler().getCoalitions(); - var countries = Object.values(coalitions[getActiveCoalition() as keyof typeof coalitions]); + var coalitions = getApp().getMissionManager().getCoalitions(); + var countries = Object.values(coalitions[getApp().getActiveCoalition() as keyof typeof coalitions]); this.#unitCountryDropdown.setOptionsElements(this.#createCountryButtons(this.#unitCountryDropdown, countries, (country: string) => { this.#setUnitCountry(country) })); if (countries.length > 0 && !countries.includes(this.#spawnOptions.country)) { @@ -377,11 +375,11 @@ export class UnitSpawnMenu { } #computeSpawnPoints() { - if (getMissionHandler() && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) { + if (getApp().getMissionManager() && getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER) { var unitCount = parseInt(this.#unitCountDropdown.getValue()); var unitSpawnPoints = unitCount * this.#unitDatabase.getSpawnPointsByLabel(this.#unitLabelDropdown.getValue()); this.#deployUnitButtonEl.dataset.points = `${unitSpawnPoints}`; - this.#deployUnitButtonEl.disabled = unitSpawnPoints >= getMissionHandler().getAvailableSpawnPoints(); + this.#deployUnitButtonEl.disabled = unitSpawnPoints >= getApp().getMissionManager().getAvailableSpawnPoints(); } } } @@ -400,7 +398,7 @@ export class AircraftSpawnMenu extends UnitSpawnMenu { } deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) { - spawnOptions.coalition = getActiveCoalition(); + spawnOptions.coalition = getApp().getActiveCoalition(); if (spawnOptions) { var unitTable: UnitSpawnTable = { unitType: spawnOptions.name, @@ -414,9 +412,9 @@ export class AircraftSpawnMenu extends UnitSpawnMenu { units.push(unitTable); } - getUnitsManager().spawnUnits("Aircraft", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { + getApp().getUnitsManager().spawnUnits("Aircraft", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { if (res.commandHash !== undefined) - getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash); + getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash); }); this.getContainer().dispatchEvent(new Event("hide")); @@ -438,7 +436,7 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu { } deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) { - spawnOptions.coalition = getActiveCoalition(); + spawnOptions.coalition = getApp().getActiveCoalition(); if (spawnOptions) { var unitTable: UnitSpawnTable = { unitType: spawnOptions.name, @@ -452,9 +450,9 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu { units.push(unitTable); } - getUnitsManager().spawnUnits("Helicopter", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { + getApp().getUnitsManager().spawnUnits("Helicopter", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { if (res.commandHash !== undefined) - getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash); + getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash); }); this.getContainer().dispatchEvent(new Event("hide")); @@ -476,7 +474,7 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu { } deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) { - spawnOptions.coalition = getActiveCoalition(); + spawnOptions.coalition = getApp().getActiveCoalition(); if (spawnOptions) { var unitTable: UnitSpawnTable = { unitType: spawnOptions.name, @@ -490,9 +488,9 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu { unitTable.location.lat += i > 0? 0.0001: 0; } - getUnitsManager().spawnUnits("GroundUnit", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { + getApp().getUnitsManager().spawnUnits("GroundUnit", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { if (res.commandHash !== undefined) - getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash); + getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash); }); this.getContainer().dispatchEvent(new Event("hide")); @@ -514,7 +512,7 @@ export class NavyUnitSpawnMenu extends UnitSpawnMenu { } deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) { - spawnOptions.coalition = getActiveCoalition(); + spawnOptions.coalition = getApp().getActiveCoalition(); if (spawnOptions) { var unitTable: UnitSpawnTable = { unitType: spawnOptions.name, @@ -528,9 +526,9 @@ export class NavyUnitSpawnMenu extends UnitSpawnMenu { unitTable.location.lat += i > 0? 0.0001: 0; } - getUnitsManager().spawnUnits("NavyUnit", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { + getApp().getUnitsManager().spawnUnits("NavyUnit", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { if (res.commandHash !== undefined) - getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash); + getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash); }); this.getContainer().dispatchEvent(new Event("hide")); diff --git a/client/src/features/featureswitches.ts b/client/src/features/featureswitches.ts deleted file mode 100644 index bb78b64c..00000000 --- a/client/src/features/featureswitches.ts +++ /dev/null @@ -1,172 +0,0 @@ -export interface FeatureSwitchInterface { - "defaultEnabled": boolean, // default on/off state (if allowed by forceState) - "forceState": number, // -1 don't force; 0 force off; 1 force on - "label": string, - "name": string, - "onEnabled"?: CallableFunction, - "options"?: object, - "removeArtifactsIfDisabled"?: boolean -} - - -class FeatureSwitch { - - // From config param - defaultEnabled; - forceState = -1; - label; - name; - onEnabled; - removeArtifactsIfDisabled = true; - - // Self-set - userPreference; - - - constructor(config: FeatureSwitchInterface) { - - this.defaultEnabled = config.defaultEnabled; - this.forceState = config.forceState; - this.label = config.label; - this.name = config.name; - this.onEnabled = config.onEnabled; - - this.userPreference = this.getUserPreference(); - - } - - - getUserPreference() { - - let preferences = JSON.parse(localStorage.getItem("featureSwitches") || "{}"); - - return (preferences.hasOwnProperty(this.name)) ? preferences[this.name] : this.defaultEnabled; - - } - - - isEnabled():boolean { - - if ( this.forceState === 0 ) { - return false; - } - - if ( this.forceState === 1 ) { - return true; - } - - return this.userPreference; - } - -} - -export class FeatureSwitches { - - #featureSwitches: FeatureSwitch[] = [ - - new FeatureSwitch({ - "defaultEnabled": false, - "forceState": -1, - "label": "AIC", - "name": "aic" - }), - - new FeatureSwitch({ - "defaultEnabled": false, - "forceState": -1, - "label": "AI Formations", - "name": "ai-formations", - "removeArtifactsIfDisabled": false - }), - - new FeatureSwitch({ - "defaultEnabled": false, - "forceState": -1, - "label": "ATC", - "name": "atc" - }), - - new FeatureSwitch({ - "defaultEnabled": false, - "forceState": -1, - "label": "Control tips", - "name": "controlTips" - }), - - new FeatureSwitch({ - "defaultEnabled": false, - "forceState": -1, - "label": "Force show unit control panel", - "name": "forceShowUnitControlPanel" - }), - - new FeatureSwitch({ - "defaultEnabled": true, - "forceState": -1, - "label": "Show splash screen", - "name": "splashScreen" - }) - - ]; - - - constructor() { - - this.#testSwitches(); - - this.savePreferences(); - - } - - - getSwitch(switchName: string) { - - return this.#featureSwitches.find(featureSwitch => featureSwitch.name === switchName); - - } - - - #testSwitches() { - for (const featureSwitch of this.#featureSwitches) { - if (featureSwitch.isEnabled()) { - if (typeof featureSwitch.onEnabled === "function") { - featureSwitch.onEnabled(); - } - } else { - document.querySelectorAll("[data-feature-switch='" + featureSwitch.name + "']").forEach(el => { - if (featureSwitch.removeArtifactsIfDisabled === false) { - el.remove(); - } else { - el.classList.add("hide"); - } - }); - } - document.body.classList.toggle("feature-" + featureSwitch.name, featureSwitch.isEnabled()); - } - } - - savePreferences() { - - let preferences: any = {}; - - for (const featureSwitch of this.#featureSwitches) { - preferences[featureSwitch.name] = featureSwitch.isEnabled(); - } - - localStorage.setItem("featureSwitches", JSON.stringify(preferences)); - - } - - savePreference( featureSwitchName:string, value:boolean ) { - - const preferences = JSON.parse( localStorage.getItem( "featureSwitches" ) || "{}" ); - - if ( preferences.hasOwnProperty( featureSwitchName ) ) { - preferences[ featureSwitchName ] = value; - } - - localStorage.setItem("featureSwitches", JSON.stringify(preferences)); - - } - -} \ No newline at end of file diff --git a/client/src/features/toggleablefeature.ts b/client/src/features/toggleablefeature.ts deleted file mode 100644 index 09f1723e..00000000 --- a/client/src/features/toggleablefeature.ts +++ /dev/null @@ -1,35 +0,0 @@ -export abstract class ToggleableFeature { - - #status: boolean = false; - - - constructor(defaultStatus: boolean) { - - this.#status = defaultStatus; - - this.onStatusUpdate(); - - } - - - getStatus(): boolean { - return this.#status; - } - - - protected onStatusUpdate() { } - - - toggleStatus(force?: boolean): void { - - if (force) { - this.#status = force; - } else { - this.#status = !this.#status; - } - - this.onStatusUpdate(); - - } - -} \ No newline at end of file diff --git a/client/src/index.ts b/client/src/index.ts index 504c4462..23136d15 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -1,114 +1,30 @@ -import { Map } from "./map/map" -import { UnitsManager } from "./unit/unitsmanager"; -import { UnitInfoPanel } from "./panels/unitinfopanel"; -import { ConnectionStatusPanel } from "./panels/connectionstatuspanel"; -import { MissionManager } from "./mission/missionmanager"; -import { UnitControlPanel } from "./panels/unitcontrolpanel"; -import { MouseInfoPanel } from "./panels/mouseinfopanel"; -import { LogPanel } from "./panels/logpanel"; import { getConfig, getPaused, setAddress, setCredentials, setPaused, startUpdate, toggleDemoEnabled } from "./server/server"; -import { Popup } from "./popups/popup"; -import { HotgroupPanel } from "./panels/hotgrouppanel"; import { SVGInjector } from "@tanem/svg-injector"; -import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants"; -import { ServerStatusPanel } from "./panels/serverstatuspanel"; -import { WeaponsManager } from "./weapon/weaponsmanager"; -import { ConfigParameters } from "./@types/dom"; -import { IndexApp } from "./indexapp"; -import { FeatureSwitches } from "./features/featureswitches"; -import { PrimaryToolbar } from "./toolbars/primarytoolbar"; -import { CommandModeToolbar } from "./toolbars/commandmodetoolbar"; -import { OlympusApp } from "./olympusapp"; +import { OlympusApp } from "./app"; import { ShortcutKeyboard } from "./shortcut/shortcut"; -/* Global data */ -var activeCoalition: string = "blue"; - -/* Main leaflet map, extended by custom methods */ -var map: Map; - -/* Managers */ -var unitsManager: UnitsManager; -var weaponsManager: WeaponsManager; -var missionManager: MissionManager; - -/* UI Panels */ -var unitInfoPanel: UnitInfoPanel; -var connectionStatusPanel: ConnectionStatusPanel; -var serverStatusPanel: ServerStatusPanel; -var unitControlPanel: UnitControlPanel; -var mouseInfoPanel: MouseInfoPanel; -var logPanel: LogPanel; -var hotgroupPanel: HotgroupPanel; - -/* UI Toolbars */ -var primaryToolbar: PrimaryToolbar; -var commandModeToolbar: CommandModeToolbar; - -/* Popups */ -var infoPopup: Popup; +var app: OlympusApp; function setup() { - - /* Initialize base functionalitites */ - map = new Map('map-container'); - - unitsManager = new UnitsManager(); - weaponsManager = new WeaponsManager(); - missionManager = new MissionManager(); - - /* Panels */ - unitInfoPanel = new UnitInfoPanel("unit-info-panel"); - unitControlPanel = new UnitControlPanel("unit-control-panel"); - connectionStatusPanel = new ConnectionStatusPanel("connection-status-panel"); - serverStatusPanel = new ServerStatusPanel("server-status-panel"); - mouseInfoPanel = new MouseInfoPanel("mouse-info-panel"); - hotgroupPanel = new HotgroupPanel("hotgroup-panel"); - logPanel = new LogPanel("log-panel"); - - /* Toolbars */ - primaryToolbar = new PrimaryToolbar("primary-toolbar"); - commandModeToolbar = new CommandModeToolbar("command-mode-toolbar"); - - /* Popups */ - infoPopup = new Popup("info-popup"); - /* Load the config file from the app server*/ - getConfig((config: ConfigParameters) => readConfig(config)); + getConfig((config: ConfigurationOptions) => readConfig(config)); - /* - This is done like this for now as a way to make it work in the new and old world. - Over time/at some point, we'll need to start migrating the pre-existing code to an "app" format - */ - - const indexApp = new IndexApp({ - "featureSwitches": new FeatureSwitches(), - "map": map, - "panels": { - "connectionStatus": connectionStatusPanel, - "hotgroup": hotgroupPanel, - "infoPopup": infoPopup, - "log": logPanel, - "mouseInfo": mouseInfoPanel, - "serverStatus": serverStatusPanel, - "unitControl": unitControlPanel, - "unitInfo": unitInfoPanel - }, - "unitsManager": unitsManager - }); + app = new OlympusApp(); + app.start(); /* Setup event handlers */ - setupEvents( indexApp ); - - indexApp.start(); + setupEvents(app); +} +export function getApp() { + return app; } /** Loads the configuration parameters * * @param config ConfigParameters, defines the address and port of the Olympus REST server */ -function readConfig(config: ConfigParameters) { +function readConfig(config: ConfigurationOptions) { if (config && config.address != undefined && config.port != undefined) { const address = config.address; const port = config.port; @@ -120,8 +36,7 @@ function readConfig(config: ConfigParameters) { } } -function setupEvents( indexApp:OlympusApp ) { - +function setupEvents(app: OlympusApp) { /* Generic clicks */ document.addEventListener("click", (ev) => { if (ev instanceof MouseEvent && ev.target instanceof HTMLElement) { @@ -147,18 +62,15 @@ function setupEvents( indexApp:OlympusApp ) { } }); - - const shortcutManager = indexApp.getShortcutManager(); - - - shortcutManager.add( "toggleDemo", new ShortcutKeyboard({ - "callback": () => { - toggleDemoEnabled(); - }, - "code": "KeyT" - }) + const shortcutManager = app.getShortcutManager(); + shortcutManager.add("toggleDemo", new ShortcutKeyboard({ + "callback": () => { + toggleDemoEnabled(); + }, + "code": "KeyT" + }) ) - .add( "togglePause", new ShortcutKeyboard({ + .add("togglePause", new ShortcutKeyboard({ "altKey": false, "callback": () => { setPaused(!getPaused()); @@ -166,45 +78,43 @@ function setupEvents( indexApp:OlympusApp ) { "code": "Space", "ctrlKey": false }) - ); + ); - [ "KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown" ].forEach( code => { - shortcutManager.add( `pan${code}keydown`, new ShortcutKeyboard({ + ["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => { + shortcutManager.add(`pan${code}keydown`, new ShortcutKeyboard({ "altKey": false, - "callback": ( ev:KeyboardEvent ) => { - getMap().handleMapPanning(ev); + "callback": (ev: KeyboardEvent) => { + getApp().getMap().handleMapPanning(ev); }, "code": code, "ctrlKey": false, "event": "keydown" })); }); - - [ "KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown" ].forEach( code => { - shortcutManager.add( `pan${code}keyup`, new ShortcutKeyboard({ - "callback": ( ev:KeyboardEvent ) => { - getMap().handleMapPanning(ev); + + ["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => { + shortcutManager.add(`pan${code}keyup`, new ShortcutKeyboard({ + "callback": (ev: KeyboardEvent) => { + getApp().getMap().handleMapPanning(ev); }, "code": code })); }); - [ "Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9" ].forEach( code => { - shortcutManager.add( `hotgroup${code}`, new ShortcutKeyboard({ - "callback": ( ev:KeyboardEvent ) => { + ["Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9"].forEach(code => { + shortcutManager.add(`hotgroup${code}`, new ShortcutKeyboard({ + "callback": (ev: KeyboardEvent) => { if (ev.ctrlKey && ev.shiftKey) - getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5))); + getApp().getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5))); else if (ev.ctrlKey && !ev.shiftKey) - getUnitsManager().selectedUnitsSetHotgroup(parseInt(ev.code.substring(5))); + getApp().getUnitsManager().selectedUnitsSetHotgroup(parseInt(ev.code.substring(5))); else - getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5))); + getApp().getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5))); }, "code": code })); }); - - // TODO: move from here in dedicated class document.addEventListener("closeDialog", (ev: CustomEventInit) => { ev.detail._element.closest(".ol-dialog").classList.add("hide"); @@ -222,7 +132,7 @@ function setupEvents( indexApp:OlympusApp ) { /* Start periodically requesting updates */ startUpdate(); - setLoginStatus("connecting"); + getApp().setLoginStatus("connecting"); }) /* Reload the page, used to mimic a restart of the app */ @@ -234,7 +144,7 @@ function setupEvents( indexApp:OlympusApp ) { document.querySelectorAll("[inject-svg]").forEach((el: Element) => { var img = el as HTMLImageElement; var isLoaded = img.complete; - if (isLoaded) + if (isLoaded) SVGInjector(img); else img.addEventListener("load", () => { @@ -243,90 +153,4 @@ function setupEvents( indexApp:OlympusApp ) { }) } -/* Getters */ -export function getMap() { - return map; -} - -export function getUnitsManager() { - return unitsManager; -} - -export function getWeaponsManager() { - return weaponsManager; -} - -export function getMissionHandler() { - return missionManager; -} - -export function getUnitInfoPanel() { - return unitInfoPanel; -} - -export function getUnitControlPanel() { - return unitControlPanel; -} - -export function getMouseInfoPanel() { - return mouseInfoPanel; -} - -export function getLogPanel() { - return logPanel; -} - -export function getConnectionStatusPanel() { - return connectionStatusPanel; -} - -export function getServerStatusPanel() { - return serverStatusPanel; -} - -export function getHotgroupPanel() { - return hotgroupPanel; -} - -export function getInfoPopup() { - return infoPopup; -} - -/** Set the active coalition, i.e. the currently controlled coalition. A game master can change the active coalition, while a commander is bound to his/her coalition - * - * @param newActiveCoalition - */ -export function setActiveCoalition(newActiveCoalition: string) { - if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER) - activeCoalition = newActiveCoalition; -} - -/** - * - * @returns The active coalition - */ -export function getActiveCoalition() { - if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER) - return activeCoalition; - else { - if (getMissionHandler().getCommandModeOptions().commandMode == BLUE_COMMANDER) - return "blue"; - else if (getMissionHandler().getCommandModeOptions().commandMode == RED_COMMANDER) - return "red"; - else - return "neutral"; - } -} - -/** Set a message in the login splash screen - * - * @param status The message to show in the login splash screen - */ -export function setLoginStatus(status: string) { - const el = document.querySelector("#login-status") as HTMLElement; - if (el) - el.dataset["status"] = status; -} - -window.onload = setup; - +window.onload = setup; \ No newline at end of file diff --git a/client/src/indexapp.ts b/client/src/indexapp.ts deleted file mode 100644 index 61c28b85..00000000 --- a/client/src/indexapp.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { FeatureSwitches } from "./features/featureswitches"; -import { IOlympusApp, OlympusApp } from "./olympusapp"; -import { ConnectionStatusPanel } from "./panels/connectionstatuspanel"; -import { HotgroupPanel } from "./panels/hotgrouppanel"; -import { LogPanel } from "./panels/logpanel"; -import { MouseInfoPanel } from "./panels/mouseinfopanel"; -import { Panel } from "./panels/panel"; -import { ServerStatusPanel } from "./panels/serverstatuspanel"; -import { UnitControlPanel } from "./panels/unitcontrolpanel"; -import { UnitInfoPanel } from "./panels/unitinfopanel"; -import { Popup } from "./popups/popup"; -import { ControlTips } from "./shortcut/controltips"; -import { UnitsManager } from "./unit/unitsmanager"; - -export interface IIndexApp extends IOlympusApp { - "featureSwitches": FeatureSwitches, - "panels": IIndexAppPanels, - "unitsManager": UnitsManager -} - -export interface IIndexAppPanels { - "connectionStatus": ConnectionStatusPanel, - "hotgroup": HotgroupPanel, - "infoPopup": Popup, - "log": LogPanel, - "mouseInfo": MouseInfoPanel, - "serverStatus": ServerStatusPanel, - "unitControl": UnitControlPanel, - "unitInfo": UnitInfoPanel -} - -export class IndexApp extends OlympusApp { - - constructor( config:IIndexApp ) { - - super( config ); - - // Panels - this.getPanelsManager() - .add( "connectionStatus", config.panels.connectionStatus ) - .add( "hotgroup", config.panels.hotgroup ) - .add( "log", config.panels.log ) - .add( "mouseInfo", config.panels.mouseInfo ) - .add( "serverStatus", config.panels.serverStatus ) - .add( "unitControl", config.panels.unitControl ) - .add( "unitInfo", config.panels.unitInfo ); - - // Popup - this.getPanelsManager().add( "unitPopup", config.panels.infoPopup ); - - // Retrofitting - Object.values( this.getPanelsManager().getAll() ).forEach( ( panel:Panel ) => { - panel.setOlympusApp( this ); - }); - } - - - start() { - - super.start(); - - new ControlTips( "control-tips-panel", this ); - - } - -} \ No newline at end of file diff --git a/client/src/map/coalitionarea/coalitionarea.ts b/client/src/map/coalitionarea/coalitionarea.ts index fb3682c6..e5e71ae0 100644 --- a/client/src/map/coalitionarea/coalitionarea.ts +++ b/client/src/map/coalitionarea/coalitionarea.ts @@ -1,5 +1,5 @@ import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet"; -import { getMap, getMissionHandler, getUnitsManager } from "../.."; +import { getApp } from "../.."; import { CoalitionAreaHandle } from "./coalitionareahandle"; import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle"; import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants"; @@ -23,8 +23,8 @@ export class CoalitionArea extends Polygon { this.#setColors(); this.#registerCallbacks(); - if ([BLUE_COMMANDER, RED_COMMANDER].includes(getMissionHandler().getCommandModeOptions().commandMode)) - this.setCoalition(getMissionHandler().getCommandedCoalition()); + if ([BLUE_COMMANDER, RED_COMMANDER].includes(getApp().getMissionManager().getCommandModeOptions().commandMode)) + this.setCoalition(getApp().getMissionManager().getCommandedCoalition()); } setCoalition(coalition: string) { @@ -61,7 +61,7 @@ export class CoalitionArea extends Polygon { /* Remove areas with less than 2 vertexes */ if (latlngs.length <= 2) - getMap().deleteCoalitionArea(this); + getApp().getMap().deleteCoalitionArea(this); } getEditing() { @@ -89,7 +89,7 @@ export class CoalitionArea extends Polygon { onRemove(map: Map): this { super.onRemove(map); - this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getMap())); + this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getApp().getMap())); return this; } @@ -99,14 +99,14 @@ export class CoalitionArea extends Polygon { } #setHandles() { - this.#handles.forEach((handle: CoalitionAreaHandle) => handle.removeFrom(getMap())); + this.#handles.forEach((handle: CoalitionAreaHandle) => handle.removeFrom(getApp().getMap())); this.#handles = []; if (this.getSelected()) { var latlngs = this.getLatLngs()[0] as LatLng[]; latlngs.forEach((latlng: LatLng, idx: number) => { /* Add the polygon vertex handle (for moving the vertex) */ const handle = new CoalitionAreaHandle(latlng); - handle.addTo(getMap()); + handle.addTo(getApp().getMap()); handle.on("drag", (e: any) => { var latlngs = this.getLatLngs()[0] as LatLng[]; latlngs[idx] = e.target.getLatLng(); @@ -120,7 +120,7 @@ export class CoalitionArea extends Polygon { } #setMiddleHandles() { - this.#middleHandles.forEach((handle: CoalitionAreaMiddleHandle) => handle.removeFrom(getMap())); + this.#middleHandles.forEach((handle: CoalitionAreaMiddleHandle) => handle.removeFrom(getApp().getMap())); this.#middleHandles = []; var latlngs = this.getLatLngs()[0] as LatLng[]; if (this.getSelected() && latlngs.length >= 2) { @@ -128,13 +128,13 @@ export class CoalitionArea extends Polygon { latlngs.concat([latlngs[0]]).forEach((latlng: LatLng, idx: number) => { /* Add the polygon middle point handle (for adding new vertexes) */ if (lastLatLng != null) { - const handle1Point = getMap().latLngToLayerPoint(latlng); - const handle2Point = getMap().latLngToLayerPoint(lastLatLng); + const handle1Point = getApp().getMap().latLngToLayerPoint(latlng); + const handle2Point = getApp().getMap().latLngToLayerPoint(lastLatLng); const middlePoint = new Point((handle1Point.x + handle2Point.x) / 2, (handle1Point.y + handle2Point.y) / 2); - const middleLatLng = getMap().layerPointToLatLng(middlePoint); + const middleLatLng = getApp().getMap().layerPointToLatLng(middlePoint); const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng); - middleHandle.addTo(getMap()); + middleHandle.addTo(getApp().getMap()); middleHandle.on("click", (e: any) => { this.#activeIndex = idx - 1; this.addTemporaryLatLng(middleLatLng); @@ -148,7 +148,7 @@ export class CoalitionArea extends Polygon { #registerCallbacks() { this.on("click", (e: any) => { - getMap().deselectAllCoalitionAreas(); + getApp().getMap().deselectAllCoalitionAreas(); if (!this.getSelected()) { this.setSelected(true); } @@ -156,7 +156,7 @@ export class CoalitionArea extends Polygon { this.on("contextmenu", (e: any) => { if (!this.getEditing()) { - getMap().deselectAllCoalitionAreas(); + getApp().getMap().deselectAllCoalitionAreas(); this.setSelected(true); } else diff --git a/client/src/map/map.ts b/client/src/map/map.ts index fd8745f4..6dc77a01 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -1,5 +1,5 @@ import * as L from "leaflet" -import { getInfoPopup, getMissionHandler, getUnitsManager } from ".."; +import { getApp } from ".."; import { BoxSelect } from "./boxselect"; import { MapContextMenu } from "../contextmenus/mapcontextmenu"; import { UnitContextMenu } from "../contextmenus/unitcontextmenu"; @@ -18,7 +18,7 @@ import { CoalitionArea } from "./coalitionarea/coalitionarea"; import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu"; import { DrawingCursor } from "./coalitionarea/drawingcursor"; import { AirbaseSpawnContextMenu } from "../contextmenus/airbasespawnmenu"; -import { OlympusApp } from "../olympusapp"; +import { Popup } from "../popups/popup"; L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); @@ -71,8 +71,6 @@ export class Map extends L.Map { #visibilityOptions: { [key: string]: boolean } = {} #hiddenTypes: string[] = []; - #olympusApp!:OlympusApp; - /** * * @param ID - the ID of the HTML element which will contain the context menu @@ -126,17 +124,17 @@ export class Map extends L.Map { const el = ev.detail._element; el?.classList.toggle("off"); this.setHiddenType(ev.detail.coalition, !el?.classList.contains("off")); - Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); + Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); document.addEventListener("toggleMarkerVisibility", (ev: CustomEventInit) => { const el = ev.detail._element; el?.classList.toggle("off"); ev.detail.types.forEach((type: string) => this.setHiddenType(type, !el?.classList.contains("off"))); - Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); + Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); if (ev.detail.types.includes("airbase")) { - Object.values(getMissionHandler().getAirbases()).forEach((airbase: Airbase) => { + Object.values(getApp().getMissionManager().getAirbases()).forEach((airbase: Airbase) => { if (el?.classList.contains("off")) airbase.removeFrom(this); else @@ -163,7 +161,7 @@ export class Map extends L.Map { document.addEventListener("mapVisibilityOptionsChanged", () => { this.getContainer().toggleAttribute("data-hide-labels", !this.getVisibilityOptions()[SHOW_UNIT_LABELS]); - this.getOlympusApp().getControlTips().toggle( !this.getVisibilityOptions()[SHOW_CONTROL_TIPS] ); + // TODO this.getControlTips().toggle( !this.getVisibilityOptions()[SHOW_CONTROL_TIPS] ); }); /* Pan interval */ @@ -188,7 +186,7 @@ export class Map extends L.Map { this.#visibilityOptions[SHOW_UNIT_TARGETS] = true; this.#visibilityOptions[SHOW_UNIT_LABELS] = true; - // Manual until we use the OlympusApp approach + // Manual until we use the App approach this.#visibilityOptions[SHOW_CONTROL_TIPS] = JSON.parse( localStorage.getItem( "featureSwitches" ) || "{}" )?.controlTips || true; this.#mapVisibilityOptionsDropdown.setOptionsElements(Object.keys(this.#visibilityOptions).map((option: string) => { @@ -260,7 +258,7 @@ export class Map extends L.Map { else { this.#hiddenTypes.push(key); } - Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); + Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); } getHiddenTypes() { @@ -367,7 +365,7 @@ export class Map extends L.Map { centerOnUnit(ID: number | null) { if (ID != null) { this.options.scrollWheelZoom = 'center'; - this.#centerUnit = getUnitsManager().getUnitByID(ID); + this.#centerUnit = getApp().getUnitsManager().getUnitByID(ID); } else { this.options.scrollWheelZoom = undefined; @@ -487,7 +485,7 @@ export class Map extends L.Map { } else { this.setState(IDLE); - getUnitsManager().deselectAllUnits(); + getApp().getUnitsManager().deselectAllUnits(); } } } @@ -525,9 +523,9 @@ export class Map extends L.Map { } else if (this.#state === MOVE_UNIT) { if (!e.originalEvent.ctrlKey) { - getUnitsManager().selectedUnitsClearDestinations(); + getApp().getUnitsManager().selectedUnitsClearDestinations(); } - getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation) + getApp().getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation) this.#destinationGroupRotation = 0; this.#destinationRotationCenter = null; @@ -550,7 +548,7 @@ export class Map extends L.Map { this.#leftClickTimer = window.setTimeout(() => { this.#preventLeftClick = false; }, 200); - getUnitsManager().selectFromBounds(e.selectionBounds); + getApp().getUnitsManager().selectFromBounds(e.selectionBounds); this.#updateCursor(); } @@ -575,25 +573,25 @@ export class Map extends L.Map { this.#longPressHandled = true; var options: { [key: string]: { text: string, tooltip: string } } = {}; - const selectedUnits = getUnitsManager().getSelectedUnits(); - const selectedUnitTypes = getUnitsManager().getSelectedUnitsCategories(); + const selectedUnits = getApp().getUnitsManager().getSelectedUnits(); + const selectedUnitTypes = getApp().getUnitsManager().getSelectedUnitsCategories(); if (selectedUnitTypes.length === 1 && ["Aircraft", "Helicopter"].includes(selectedUnitTypes[0])) { if (selectedUnits.every((unit: Unit) => { return unit.canFulfillRole(["CAS", "Strike"]) })) { options["bomb"] = { text: "Precision bombing", tooltip: "Precision bombing of a specific point" }; options["carpet-bomb"] = { text: "Carpet bombing", tooltip: "Carpet bombing close to a point" }; } else { - getInfoPopup().setText(`Selected units can not perform point actions.`); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Selected units can not perform point actions.`); } } else if (selectedUnitTypes.length === 1 && ["GroundUnit", "NavyUnit"].includes(selectedUnitTypes[0])) { if (selectedUnits.every((unit: Unit) => { return ["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank", "Cruiser", "Destroyer", "Frigate"].includes(unit.getType()) })) options["fire-at-area"] = { text: "Fire at area", tooltip: "Fire at a large area" }; else - getInfoPopup().setText(`Selected units can not perform point actions.`); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Selected units can not perform point actions.`); } else if(selectedUnitTypes.length > 1) { - getInfoPopup().setText(`Multiple unit types selected, no common actions available.`); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Multiple unit types selected, no common actions available.`); } if (Object.keys(options).length > 0) { @@ -601,16 +599,16 @@ export class Map extends L.Map { this.getUnitContextMenu().setOptions(options, (option: string) => { this.hideUnitContextMenu(); if (option === "bomb") { - getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE); - getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates()); + getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE); + getApp().getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates()); } else if (option === "carpet-bomb") { - getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE); - getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates()); + getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE); + getApp().getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates()); } else if (option === "fire-at-area") { - getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE); - getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates()); + getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE); + getApp().getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates()); } }); } @@ -703,7 +701,7 @@ export class Map extends L.Map { #showDestinationCursors() { const singleCursor = !this.#shiftKey; - const selectedUnitsCount = getUnitsManager().getSelectedUnits({ excludeHumans: false, onlyOnePerGroup: true }).length; + const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: false, onlyOnePerGroup: true }).length; if (selectedUnitsCount > 0) { if (singleCursor && this.#destinationPreviewCursors.length != 1) { this.#hideDestinationCursors(); @@ -733,7 +731,7 @@ export class Map extends L.Map { if (this.#destinationPreviewCursors.length == 1) this.#destinationPreviewCursors[0].setLatLng(this.getMouseCoordinates()); else { - Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { + Object.values(getApp().getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { if (idx < this.#destinationPreviewCursors.length) this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey ? latlng : this.getMouseCoordinates()); }) @@ -803,15 +801,5 @@ export class Map extends L.Map { this.#visibilityOptions[option] = ev.currentTarget.checked; document.dispatchEvent(new CustomEvent("mapVisibilityOptionsChanged")); } - - getOlympusApp() { - return this.#olympusApp; - } - - setOlympusApp( olympusApp:OlympusApp ) { - - this.#olympusApp = olympusApp; - - } } diff --git a/client/src/map/markers/smokemarker.ts b/client/src/map/markers/smokemarker.ts index 39cf9f82..d7ec80cf 100644 --- a/client/src/map/markers/smokemarker.ts +++ b/client/src/map/markers/smokemarker.ts @@ -1,7 +1,7 @@ import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet"; import { CustomMarker } from "./custommarker"; import { SVGInjector } from "@tanem/svg-injector"; -import { getMap } from "../.."; +import { getApp } from "../.."; export class SmokeMarker extends CustomMarker { #color: string; @@ -10,7 +10,7 @@ export class SmokeMarker extends CustomMarker { super(latlng, options); this.setZIndexOffset(9999); this.#color = color; - window.setTimeout(() => { this.removeFrom(getMap()); }, 300000) /* Remove the smoke after 5 minutes */ + window.setTimeout(() => { this.removeFrom(getApp().getMap()); }, 300000) /* Remove the smoke after 5 minutes */ } createIcon() { diff --git a/client/src/map/markers/temporaryunitmarker.ts b/client/src/map/markers/temporaryunitmarker.ts index 0f8e55ee..1d480a13 100644 --- a/client/src/map/markers/temporaryunitmarker.ts +++ b/client/src/map/markers/temporaryunitmarker.ts @@ -3,7 +3,7 @@ import { DivIcon, LatLng } from "leaflet"; import { SVGInjector } from "@tanem/svg-injector"; import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../../other/utils"; import { isCommandExecuted } from "../../server/server"; -import { getMap } from "../.."; +import { getApp } from "../.."; export class TemporaryUnitMarker extends CustomMarker { #name: string; @@ -27,7 +27,7 @@ export class TemporaryUnitMarker extends CustomMarker { if (this.#commandHash !== undefined) { isCommandExecuted((res: any) => { if (res.commandExecuted) { - this.removeFrom(getMap()); + this.removeFrom(getApp().getMap()); window.clearInterval(this.#timer); } }, this.#commandHash) diff --git a/client/src/mission/airbase.ts b/client/src/mission/airbase.ts index 6e85a997..dc74fee5 100644 --- a/client/src/mission/airbase.ts +++ b/client/src/mission/airbase.ts @@ -2,30 +2,6 @@ import { DivIcon } from 'leaflet'; import { CustomMarker } from '../map/markers/custommarker'; import { SVGInjector } from '@tanem/svg-injector'; -export interface AirbaseOptions { - name: string, - position: L.LatLng -} - - -export interface AirbaseChartData { - elevation: string, - ICAO: string, - TACAN: string, - runways: AirbaseChartRunwayData[] -} - -export interface AirbaseChartRunwayData { - "headings": AirbaseChartRunwayHeadingData[], - "length": string -} - -export interface AirbaseChartRunwayHeadingData { - [index: string]: { - "magHeading": string, - "ILS": string - } -} export class Airbase extends CustomMarker { #name: string = ""; diff --git a/client/src/mission/missionmanager.ts b/client/src/mission/missionmanager.ts index ffe298ee..1b6f2c85 100644 --- a/client/src/mission/missionmanager.ts +++ b/client/src/mission/missionmanager.ts @@ -1,5 +1,5 @@ import { LatLng } from "leaflet"; -import { getInfoPopup, getMap } from ".."; +import { getApp } from ".."; import { Airbase } from "./airbase"; import { Bullseye } from "./bullseye"; import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants"; @@ -10,6 +10,7 @@ import { createCheckboxOption, getCheckboxOptions } from "../other/utils"; import { aircraftDatabase } from "../unit/databases/aircraftdatabase"; import { helicopterDatabase } from "../unit/databases/helicopterdatabase"; import { navyUnitDatabase } from "../unit/databases/navyunitdatabase"; +import { Popup } from "../popups/popup"; /** The MissionManager */ export class MissionManager { @@ -38,7 +39,7 @@ export class MissionManager { for (let idx in data.bullseyes) { const bullseye = data.bullseyes[idx]; if (!(idx in this.#bullseyes)) - this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getMap()); + this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getApp().getMap()); if (bullseye.latitude && bullseye.longitude && bullseye.coalition) { this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude)); @@ -54,7 +55,7 @@ export class MissionManager { this.#airbases[airbase.callsign] = new Airbase({ position: new LatLng(airbase.latitude, airbase.longitude), name: airbase.callsign - }).addTo(getMap()); + }).addTo(getApp().getMap()); this.#airbases[airbase.callsign].on('contextmenu', (e) => this.#onAirbaseClick(e)); this.#loadAirbaseChartData(airbase.callsign); } @@ -72,8 +73,8 @@ export class MissionManager { /* Set the mission theatre */ if (data.mission.theatre != this.#theatre) { this.#theatre = data.mission.theatre; - getMap().setTheatre(this.#theatre); - getInfoPopup().setText("Map set to " + this.#theatre); + getApp().getMap().setTheatre(this.#theatre); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Map set to " + this.#theatre); } /* Set the date and time data */ @@ -232,7 +233,7 @@ export class MissionManager { } #onAirbaseClick(e: any) { - getMap().showAirbaseContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng, e.sourceTarget); + getApp().getMap().showAirbaseContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng, e.sourceTarget); } #loadAirbaseChartData(callsign: string) { diff --git a/client/src/olympusapp.ts b/client/src/olympusapp.ts deleted file mode 100644 index bf99cfe7..00000000 --- a/client/src/olympusapp.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { FeatureSwitches } from "./features/featureswitches"; -import { Map } from "./map/map"; -import { PanelsManager } from "./panels/panelsmanager"; -import { ControlTips } from "./shortcut/controltips"; -import { ShortcutManager } from "./shortcut/shortcutmanager"; -import { UnitsManager } from "./unit/unitsmanager"; - - -export interface IOlympusApp { - featureSwitches: FeatureSwitches; - map: Map, - unitsManager: UnitsManager; -} - -export abstract class OlympusApp { - - #controlTips: ControlTips; - #featureSwitches: FeatureSwitches; - #map: Map; - #panelsManager: PanelsManager = new PanelsManager( this ); - #shortcutManager: ShortcutManager = new ShortcutManager( this ); - #unitsManager: UnitsManager; - - constructor( config:IOlympusApp ) { - - this.#controlTips = new ControlTips( "control-tips-panel", this ); - this.#featureSwitches = config.featureSwitches; - this.#map = config.map; - this.#unitsManager = config.unitsManager; - - this.getMap().setOlympusApp( this ); - - } - - getControlTips() { - return this.#controlTips; - } - - getFeatureSwitches() { - return this.#featureSwitches; - } - - getMap() { - return this.#map; - } - - getPanelsManager() { - return this.#panelsManager; - } - - getShortcutManager() { - return this.#shortcutManager; - } - - getUnitsManager() { - return this.#unitsManager; - } - - getWeaponsManager() { - return this.getWeaponsManager; - } - - start() { - - // Start the app - - - } - -} \ No newline at end of file diff --git a/client/src/other/eventsmanager.ts b/client/src/other/eventsmanager.ts index 6bdc504c..9173b2b1 100644 --- a/client/src/other/eventsmanager.ts +++ b/client/src/other/eventsmanager.ts @@ -1,10 +1,7 @@ -import { OlympusApp } from "../olympusapp"; import { Manager } from "./manager"; export abstract class EventsManager extends Manager { - - constructor( olympusApp:OlympusApp ) { - super( olympusApp ); + constructor() { + super(); } - } \ No newline at end of file diff --git a/client/src/other/manager.ts b/client/src/other/manager.ts index 92bfd949..6ac10ea8 100644 --- a/client/src/other/manager.ts +++ b/client/src/other/manager.ts @@ -1,54 +1,33 @@ -import { OlympusApp } from "../olympusapp"; - -export interface IManager { - add:CallableFunction; -} - -export abstract class Manager { - - #items: {[key:string]: any } = {}; - #olympusApp: OlympusApp; - - constructor( olympusApp:OlympusApp ) { - - this.#olympusApp = olympusApp; +export class Manager { + #items: { [key: string]: any } = {}; + constructor() { } - add( name:string, item:any ) { - - const regex = new RegExp( "^[a-z][a-z0-9]{2,}$", "i" ); - - if ( regex.test( name ) === false ) { - throw new Error( `Item name "${name}" does not match regex: ${regex.toString()}.` ); + add(name: string, item: any) { + const regex = new RegExp("^[a-z][a-z0-9]{2,}$", "i"); + if (regex.test(name) === false) { + throw new Error(`Item name "${name}" does not match regex: ${regex.toString()}.`); } - if ( this.#items.hasOwnProperty( name ) ) { - throw new Error( `Item with name "${name}" already exists.` ); + if (this.#items.hasOwnProperty(name)) { + throw new Error(`Item with name "${name}" already exists.`); } - this.#items[ name ] = item; - + this.#items[name] = item; return this; - } - get( name:string ) { - - if ( this.#items.hasOwnProperty( name ) ) { - return this.#items[ name ]; + get(name: string) { + if (this.#items.hasOwnProperty(name)) { + return this.#items[name]; } else { return false; } - } getAll() { return this.#items; } - getOlympusApp() { - return this.#olympusApp; - } - } \ No newline at end of file diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index 4d46d202..78d025e2 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -7,7 +7,6 @@ import { groundUnitDatabase } from "../unit/databases/groundunitdatabase"; import { Buffer } from "buffer"; import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants"; import { Dropdown } from "../controls/dropdown"; -import { UnitBlueprint } from "../@types/unitdatabase"; import { navyUnitDatabase } from "../unit/databases/navyunitdatabase"; export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) { diff --git a/client/src/panels/hotgrouppanel.ts b/client/src/panels/hotgrouppanel.ts index 94643fa0..007e5b92 100644 --- a/client/src/panels/hotgrouppanel.ts +++ b/client/src/panels/hotgrouppanel.ts @@ -1,4 +1,4 @@ -import { getUnitsManager } from ".."; +import { getApp } from ".."; import { Unit } from "../unit/unit"; import { Panel } from "./panel"; @@ -15,7 +15,7 @@ export class HotgroupPanel extends Panel { refreshHotgroups() { for (let hotgroup = 1; hotgroup <= 9; hotgroup++){ this.removeHotgroup(hotgroup); - if (getUnitsManager().getUnitsByHotgroup(hotgroup).length > 0) + if (getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).length > 0) this.addHotgroup(hotgroup); } @@ -32,7 +32,7 @@ export class HotgroupPanel extends Panel { // Hotgroup unit count var countDiv = document.createElement("div"); - countDiv.innerText = `x${getUnitsManager().getUnitsByHotgroup(hotgroup).length}`; + countDiv.innerText = `x${getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).length}`; var el = document.createElement("div"); el.appendChild(hotgroupDiv); @@ -43,15 +43,15 @@ export class HotgroupPanel extends Panel { this.getElement().appendChild(el); el.addEventListener("click", () => { - getUnitsManager().selectUnitsByHotgroup(hotgroup); + getApp().getUnitsManager().selectUnitsByHotgroup(hotgroup); }); el.addEventListener("mouseover", () => { - getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(true)); + getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(true)); }); el.addEventListener("mouseout", () => { - getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(false)); + getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(false)); }); } diff --git a/client/src/panels/logpanel.ts b/client/src/panels/logpanel.ts index a0a38b7c..f41017f2 100644 --- a/client/src/panels/logpanel.ts +++ b/client/src/panels/logpanel.ts @@ -1,4 +1,5 @@ -import { getMouseInfoPanel } from ".."; +import { getApp } from ".."; +import { MouseInfoPanel } from "./mouseinfopanel"; import { Panel } from "./panel"; export class LogPanel extends Panel { @@ -37,7 +38,7 @@ export class LogPanel extends Panel { }); - const mouseInfoPanel = getMouseInfoPanel(); + const mouseInfoPanel = getApp().getPanelsManager().get("mouseInfo") as MouseInfoPanel; new ResizeObserver(() => this.#calculateHeight()).observe(mouseInfoPanel.getElement()) } @@ -89,7 +90,7 @@ export class LogPanel extends Panel { } #calculateHeight() { - const mouseInfoPanel = getMouseInfoPanel(); + const mouseInfoPanel = getApp().getPanelsManager().get("mouseInfo"); if (this.#open) this.getElement().style.height = `${mouseInfoPanel.getElement().offsetTop - this.getElement().offsetTop - 10}px`; else diff --git a/client/src/panels/mouseinfopanel.ts b/client/src/panels/mouseinfopanel.ts index e5561c68..90ba077c 100644 --- a/client/src/panels/mouseinfopanel.ts +++ b/client/src/panels/mouseinfopanel.ts @@ -1,5 +1,5 @@ import { Icon, LatLng, Marker, Polyline } from "leaflet"; -import { getMap, getMissionHandler, getUnitsManager } from ".."; +import { getApp } from ".."; import { distance, bearing, zeroAppend, mToNm, nmToFt } from "../other/utils"; import { Unit } from "../unit/unit"; import { Panel } from "./panel"; @@ -22,19 +22,19 @@ export class MouseInfoPanel extends Panel { this.#measureBox.classList.add("ol-measure-box", "hide"); document.body.appendChild(this.#measureBox); - getMap()?.on("click", (e: any) => this.#onMapClick(e)); - getMap()?.on('zoom', (e: any) => this.#onZoom(e)); - getMap()?.on('mousemove', (e: any) => this.#onMouseMove(e)); + getApp().getMap()?.on("click", (e: any) => this.#onMapClick(e)); + getApp().getMap()?.on('zoom', (e: any) => this.#onZoom(e)); + getApp().getMap()?.on('mousemove', (e: any) => this.#onMouseMove(e)); document.addEventListener('unitsSelection', (e: CustomEvent) => this.#update()); document.addEventListener('clearSelection', () => this.#update()); } #update() { - const mousePosition = getMap().getMouseCoordinates(); + const mousePosition = getApp().getMap().getMouseCoordinates(); var selectedUnitPosition = null; - var selectedUnits = getUnitsManager().getSelectedUnits(); + var selectedUnits = getApp().getUnitsManager().getSelectedUnits(); if (selectedUnits && selectedUnits.length == 1) selectedUnitPosition = new LatLng(selectedUnits[0].getPosition().lat, selectedUnits[0].getPosition().lng); @@ -44,7 +44,7 @@ export class MouseInfoPanel extends Panel { this.getElement().querySelector(`#measuring-tool`)?.classList.toggle("hide", this.#measurePoint === null && selectedUnitPosition === null); - var bullseyes = getMissionHandler().getBullseyes(); + var bullseyes = getApp().getMissionManager().getBullseyes(); for (let idx in bullseyes) this.#drawMeasure(null, `bullseye-${idx}`, bullseyes[idx].getLatLng(), mousePosition); @@ -61,19 +61,19 @@ export class MouseInfoPanel extends Panel { this.#measureBox.classList.toggle("hide", false); this.#measurePoint = e.latlng; this.#measureMarker.setLatLng(e.latlng); - this.#measureMarker.addTo(getMap()); - if (!getMap().hasLayer(this.#measureLine)) - this.#measureLine.addTo(getMap()); + this.#measureMarker.addTo(getApp().getMap()); + if (!getApp().getMap().hasLayer(this.#measureLine)) + this.#measureLine.addTo(getApp().getMap()); } else { this.#measureBox.classList.toggle("hide", true); this.#measurePoint = null; - if (getMap().hasLayer(this.#measureMarker)) - getMap().removeLayer(this.#measureMarker); + if (getApp().getMap().hasLayer(this.#measureMarker)) + getApp().getMap().removeLayer(this.#measureMarker); this.#measureLine.setLatLngs([]); - if (getMap().hasLayer(this.#measureLine)) - getMap().removeLayer(this.#measureLine); + if (getApp().getMap().hasLayer(this.#measureLine)) + getApp().getMap().removeLayer(this.#measureLine); } } @@ -81,15 +81,15 @@ export class MouseInfoPanel extends Panel { } #drawMeasureLine() { - var mouseLatLng = getMap().containerPointToLatLng(getMap().getMousePosition()); + var mouseLatLng = getApp().getMap().containerPointToLatLng(getApp().getMap().getMousePosition()); if (this.#measurePoint != null) { var points = [this.#measurePoint, mouseLatLng]; this.#measureLine.setLatLngs(points); var dist = distance(this.#measurePoint.lat, this.#measurePoint.lng, mouseLatLng.lat, mouseLatLng.lng); var bear = bearing(this.#measurePoint.lat, this.#measurePoint.lng, mouseLatLng.lat, mouseLatLng.lng); - var startXY = getMap().latLngToContainerPoint(this.#measurePoint); - var dx = (getMap().getMousePosition().x - startXY.x); - var dy = (getMap().getMousePosition().y - startXY.y); + var startXY = getApp().getMap().latLngToContainerPoint(this.#measurePoint); + var dx = (getApp().getMap().getMousePosition().x - startXY.x); + var dy = (getApp().getMap().getMousePosition().y - startXY.y); var angle = Math.atan2(dy, dx); if (angle > Math.PI / 2) @@ -108,8 +108,8 @@ export class MouseInfoPanel extends Panel { let data = [`${bng}°`, `${str} ${unit}`]; this.#measureBox.innerText = data.join(" / "); - this.#measureBox.style.left = (getMap().getMousePosition().x + startXY.x) / 2 - this.#measureBox.offsetWidth / 2 + "px"; - this.#measureBox.style.top = (getMap().getMousePosition().y + startXY.y) / 2 - this.#measureBox.offsetHeight / 2 + "px"; + this.#measureBox.style.left = (getApp().getMap().getMousePosition().x + startXY.x) / 2 - this.#measureBox.offsetWidth / 2 + "px"; + this.#measureBox.style.top = (getApp().getMap().getMousePosition().y + startXY.y) / 2 - this.#measureBox.offsetHeight / 2 + "px"; this.#measureBox.style.rotate = angle + "rad"; } } diff --git a/client/src/panels/panel.ts b/client/src/panels/panel.ts index 8899ac7e..a315ea16 100644 --- a/client/src/panels/panel.ts +++ b/client/src/panels/panel.ts @@ -1,35 +1,31 @@ -import { OlympusApp } from "../olympusapp"; import { PanelEventsManager } from "./paneleventsmanager"; export abstract class Panel { #element: HTMLElement #eventsManager!: PanelEventsManager; - #olympusApp!: OlympusApp; - constructor(ID: string, olympusApp?:OlympusApp ) { + constructor(ID: string) { this.#element = document.getElementById(ID); - - if ( olympusApp ) { - this.setOlympusApp( olympusApp ); - } + + this.#eventsManager = new PanelEventsManager(); } show() { this.#element.classList.toggle("hide", false); - this.getEventsManager()?.trigger( "show", {} ); + this.getEventsManager()?.trigger("show", {}); } hide() { this.#element.classList.toggle("hide", true); - this.getEventsManager()?.trigger( "hide", {} ); + this.getEventsManager()?.trigger("hide", {}); } toggle() { // Simple way to track if currently visible if (this.getVisible()) this.hide(); - else + else this.show(); } @@ -37,21 +33,11 @@ export abstract class Panel { return this.#element; } - getVisible(){ - return (!this.getElement().classList.contains( "hide" ) ); + getVisible() { + return (!this.getElement().classList.contains("hide")); } getEventsManager() { return this.#eventsManager; } - - getOlympusApp() { - return this.#olympusApp; - } - - setOlympusApp( olympusApp:OlympusApp ) { - this.#olympusApp = olympusApp; - this.#eventsManager = new PanelEventsManager( this.getOlympusApp() ); - } - } \ No newline at end of file diff --git a/client/src/panels/paneleventsmanager.ts b/client/src/panels/paneleventsmanager.ts index d98cf10a..906f169b 100644 --- a/client/src/panels/paneleventsmanager.ts +++ b/client/src/panels/paneleventsmanager.ts @@ -1,50 +1,29 @@ -import { OlympusApp } from "../olympusapp"; import { EventsManager } from "../other/eventsmanager"; -interface IListener { - callback: CallableFunction; - name?: string -} - export class PanelEventsManager extends EventsManager { - - constructor( olympusApp:OlympusApp ) { + constructor() { + super(); - super( olympusApp ); - - this.add( "hide", [] ); - this.add( "show", [] ); - + this.add("hide", []); + this.add("show", []); } - on( eventName:string, listener:IListener ) { - - const event = this.get( eventName ); - - if ( !event ) { - throw new Error( `Event name "${eventName}" is not valid.` ); + on(eventName: string, listener: Listener) { + const event = this.get(eventName); + if (!event) { + throw new Error(`Event name "${eventName}" is not valid.`); } - - this.get( eventName ).push({ + this.get(eventName).push({ "callback": listener.callback }); - } - trigger( eventName:string, contextData:object ) { - - const listeners = this.get( eventName ); - - if ( listeners ) { - - listeners.forEach( ( listener:IListener ) => { - - listener.callback( contextData ); - + trigger(eventName: string, contextData: object) { + const listeners = this.get(eventName); + if (listeners) { + listeners.forEach((listener: Listener) => { + listener.callback(contextData); }); - } - } - } \ No newline at end of file diff --git a/client/src/panels/panelsmanager.ts b/client/src/panels/panelsmanager.ts deleted file mode 100644 index 9153753b..00000000 --- a/client/src/panels/panelsmanager.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { OlympusApp } from "../olympusapp"; -import { Manager } from "../other/manager"; -import { Panel } from "./panel"; - -export class PanelsManager extends Manager { - - #panels: { [key:string]: Panel } = {} - - constructor( olympusApp:OlympusApp ) { - super( olympusApp ); - } - - get( name:string ): Panel { - return super.get( name ); - } - -} \ No newline at end of file diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index e8631f1a..24f441b8 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -1,5 +1,5 @@ import { SVGInjector } from "@tanem/svg-injector"; -import { getUnitsManager } from ".."; +import { getApp } from ".."; import { Dropdown } from "../controls/dropdown"; import { Slider } from "../controls/slider"; import { aircraftDatabase } from "../unit/databases/aircraftdatabase"; @@ -8,7 +8,6 @@ import { Panel } from "./panel"; import { Switch } from "../controls/switch"; import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants"; import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils"; -import { GeneralSettings, Radio, TACAN } from "../@types/unit"; export class UnitControlPanel extends Panel { #altitudeSlider: Slider; @@ -33,24 +32,24 @@ export class UnitControlPanel extends Panel { super(ID); /* Unit control sliders */ - this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getUnitsManager().selectedUnitsSetAltitude(ftToM(value)); }); - this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetAltitudeType(value? "ASL": "AGL"); }); + this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getApp().getUnitsManager().selectedUnitsSetAltitude(ftToM(value)); }); + this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getApp().getUnitsManager().selectedUnitsSetAltitudeType(value? "ASL": "AGL"); }); - this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getUnitsManager().selectedUnitsSetSpeed(knotsToMs(value)); }); - this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "CAS": "GS"); }); + this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getApp().getUnitsManager().selectedUnitsSetSpeed(knotsToMs(value)); }); + this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getApp().getUnitsManager().selectedUnitsSetSpeedType(value? "CAS": "GS"); }); /* Option buttons */ // Reversing the ROEs so that the least "aggressive" option is always on the left this.#optionButtons["ROE"] = ROEs.slice(0).reverse().map((option: string, index: number) => { - return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions.slice(0).reverse()[index], () => { getUnitsManager().selectedUnitsSetROE(option); }); + return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions.slice(0).reverse()[index], () => { getApp().getUnitsManager().selectedUnitsSetROE(option); }); }).filter((button: HTMLButtonElement, index: number) => {return ROEs[index] !== "";}); this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string, index: number) => { - return this.#createOptionButton(option, `threat/${option.toLowerCase()}.svg`, reactionsToThreatDescriptions[index],() => { getUnitsManager().selectedUnitsSetReactionToThreat(option); }); + return this.#createOptionButton(option, `threat/${option.toLowerCase()}.svg`, reactionsToThreatDescriptions[index],() => { getApp().getUnitsManager().selectedUnitsSetReactionToThreat(option); }); }); this.#optionButtons["emissionsCountermeasures"] = emissionsCountermeasures.map((option: string, index: number) => { - return this.#createOptionButton(option, `emissions/${option.toLowerCase()}.svg`, emissionsCountermeasuresDescriptions[index],() => { getUnitsManager().selectedUnitsSetEmissionsCountermeasures(option); }); + return this.#createOptionButton(option, `emissions/${option.toLowerCase()}.svg`, emissionsCountermeasuresDescriptions[index],() => { getApp().getUnitsManager().selectedUnitsSetEmissionsCountermeasures(option); }); }); this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]); @@ -59,12 +58,12 @@ export class UnitControlPanel extends Panel { /* On off switch */ this.#onOffSwitch = new Switch("on-off-switch", (value: boolean) => { - getUnitsManager().selectedUnitsSetOnOff(value); + getApp().getUnitsManager().selectedUnitsSetOnOff(value); }); /* Follow roads switch */ this.#followRoadsSwitch = new Switch("follow-roads-switch", (value: boolean) => { - getUnitsManager().selectedUnitsSetFollowRoads(value); + getApp().getUnitsManager().selectedUnitsSetFollowRoads(value); }); /* Advanced settings dialog */ @@ -84,7 +83,7 @@ export class UnitControlPanel extends Panel { document.addEventListener("clearSelection", () => { this.hide() }); document.addEventListener("applyAdvancedSettings", () => {this.#applyAdvancedSettings();}) document.addEventListener("showAdvancedSettings", () => { - this.#updateAdvancedSettingsDialog(getUnitsManager().getSelectedUnits()); + this.#updateAdvancedSettingsDialog(getApp().getUnitsManager().getSelectedUnits()); this.#advancedSettingsDialog.classList.remove("hide"); }); @@ -108,8 +107,8 @@ export class UnitControlPanel extends Panel { } addButtons() { - this.#units = getUnitsManager().getSelectedUnits(); - this.#selectedUnitsTypes = getUnitsManager().getSelectedUnitsCategories(); + this.#units = getApp().getUnitsManager().getSelectedUnits(); + this.#selectedUnitsTypes = getApp().getUnitsManager().getSelectedUnitsCategories(); if (this.#units.length < 20) { this.getElement().querySelector("#selected-units-container")?.replaceChildren(...this.#units.map((unit: Unit, index: number) => { @@ -127,12 +126,12 @@ export class UnitControlPanel extends Panel { button.addEventListener("click", ( ev:MouseEventInit ) => { // Ctrl-click deselection if ( ev.ctrlKey === true && ev.shiftKey === false && ev.altKey === false ) { - getUnitsManager().deselectUnit( unit.ID ); + getApp().getUnitsManager().deselectUnit( unit.ID ); button.remove(); // Deselect all } else { - getUnitsManager().deselectAllUnits(); - getUnitsManager().selectUnit(unit.ID, true); + getApp().getUnitsManager().deselectAllUnits(); + getApp().getUnitsManager().selectUnit(unit.ID, true); } }); return (button); @@ -161,12 +160,12 @@ export class UnitControlPanel extends Panel { if (this.#selectedUnitsTypes.length == 1) { /* Flight controls */ - var desiredAltitude = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()}); - var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()}); - var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()}); - var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()}); - var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()}); - var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()}); + var desiredAltitude = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()}); + var desiredAltitudeType = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()}); + var desiredSpeed = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()}); + var desiredSpeedType = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()}); + var onOff = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()}); + var followRoads = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()}); this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "ASL": undefined, false); this.#speedTypeSwitch.setValue(desiredSpeedType != undefined? desiredSpeedType == "CAS": undefined, false); @@ -328,7 +327,7 @@ export class UnitControlPanel extends Panel { } /* Send command and close */ - var units = getUnitsManager().getSelectedUnits(); + var units = getApp().getUnitsManager().getSelectedUnits(); if (units.length > 0) units[0].setAdvancedOptions(isTanker, isAWACS, TACAN, radio, generalSettings); diff --git a/client/src/panels/unitinfopanel.ts b/client/src/panels/unitinfopanel.ts index 36e779c2..206afe94 100644 --- a/client/src/panels/unitinfopanel.ts +++ b/client/src/panels/unitinfopanel.ts @@ -1,4 +1,3 @@ -import { Ammo } from "../@types/unit"; import { aircraftDatabase } from "../unit/databases/aircraftdatabase"; import { Unit } from "../unit/unit"; import { Panel } from "./panel"; diff --git a/client/src/plugin/plugin.ts b/client/src/plugin/plugin.ts deleted file mode 100644 index 97204563..00000000 --- a/client/src/plugin/plugin.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { OlympusApp } from "../olympusapp"; -const templateParser = require( "ejs" ); - -export abstract class Plugin { - - #olympusApp!:OlympusApp; - protected name = ""; - #templateParser:any; - - constructor( olympusApp:OlympusApp, pluginName:string ) { - - const regex = "^[a-zA-Z][a-zA-Z\d]{4,}" - - if ( new RegExp( regex ).test( pluginName ) === false ) { - throw new Error( `Plugin names must match regex: ${regex}` ); - } - - this.name = pluginName; - this.#olympusApp = olympusApp; - this.#templateParser = templateParser; - - } - - getName() { - return this.name; - } - - getOlympusApp() { - return this.#olympusApp; - } - - getTemplateParser() { - return this.#templateParser; - } - -} \ No newline at end of file diff --git a/client/src/plugin/pluginmanager.ts b/client/src/plugin/pluginmanager.ts index 7e017d5a..3179786b 100644 --- a/client/src/plugin/pluginmanager.ts +++ b/client/src/plugin/pluginmanager.ts @@ -1,13 +1,55 @@ -import { OlympusApp } from "../olympusapp"; +import path from "path"; import { Manager } from "../other/manager"; +import { getApp } from ".."; +export class PluginsManager extends Manager { + constructor() { + super(); -export class PluginManager extends Manager { - - constructor( olympusApp:OlympusApp ) { - - super( olympusApp ); - + var xhr = new XMLHttpRequest(); + xhr.open('GET', "/plugins/list", true); + xhr.responseType = 'json'; + xhr.onload = () => { + var status = xhr.status; + if (status === 200) { + this.#loadPlugins(xhr.response); + } else { + console.error(`Error retrieving plugins`) + } + }; + xhr.send(); + } + + #loadPlugins(pluginsFolders: string[]) { + pluginsFolders.forEach((pluginName: string) => { + var xhr = new XMLHttpRequest(); + xhr.open('GET', path.join("/plugins", pluginName, "index.js"), true); + xhr.responseType = 'text'; + xhr.onload = () => { + var status = xhr.status; + if (status === 200) { + /* Inject the plugin style */ + var link = document.createElement("link"); + link.href = path.join("/plugins", pluginName, "style.css"); + link.type = "text/css"; + link.rel = "stylesheet"; + document.getElementsByTagName("head")[0].appendChild(link); + + /* Evaluate the plugin javascript */ + eval(xhr.response); + const plugin = globalThis.getOlympusPlugin() as OlympusPlugin; + console.log(plugin.getName() + " loaded correctly"); + + if (plugin.initialize(getApp())) { + console.log(plugin.getName() + " initialized correctly"); + this.add(pluginName, plugin); + } + + } else { + console.error(`Error retrieving plugin from ${pluginName}`) + } + }; + xhr.send(); + }) } - } \ No newline at end of file diff --git a/client/src/server/dataextractor.ts b/client/src/server/dataextractor.ts index d22b6201..efd61d8b 100644 --- a/client/src/server/dataextractor.ts +++ b/client/src/server/dataextractor.ts @@ -1,5 +1,4 @@ import { LatLng } from "leaflet"; -import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../@types/unit"; export class DataExtractor { #seekPosition = 0; diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 05695c86..9087abfa 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -1,7 +1,10 @@ import { LatLng } from 'leaflet'; -import { getConnectionStatusPanel, getInfoPopup, getLogPanel, getMissionHandler, getServerStatusPanel, getUnitsManager, getWeaponsManager, setLoginStatus } from '..'; -import { GeneralSettings, Radio, TACAN } from '../@types/unit'; +import { getApp } from '..'; import { AIRBASES_URI, BULLSEYE_URI, COMMANDS_URI, LOGS_URI, MISSION_URI, NONE, ROEs, UNITS_URI, WEAPONS_URI, emissionsCountermeasures, reactionsToThreat } from '../constants/constants'; +import { ServerStatusPanel } from '../panels/serverstatuspanel'; +import { LogPanel } from '../panels/logpanel'; +import { Popup } from '../popups/popup'; +import { ConnectionStatusPanel } from '../panels/connectionstatuspanel'; var connected: boolean = false; var paused: boolean = false; @@ -64,12 +67,12 @@ export function GET(callback: CallableFunction, uri: string, options?: ServerReq lastUpdateTimes[uri] = callback(result); if (result.frameRate !== undefined && result.load !== undefined) - getServerStatusPanel().update(result.frameRate, result.load); + (getApp().getPanelsManager().get("serverStatus") as ServerStatusPanel).update(result.frameRate, result.load); } } else if (xmlHttp.status == 401) { /* Bad credentials */ console.error("Incorrect username/password"); - setLoginStatus("failed"); + getApp().setLoginStatus("failed"); } else { /* Failure, probably disconnected */ setConnected(false); @@ -351,74 +354,74 @@ export function startUpdate() { if (!getPaused()) { getMission((data: MissionData) => { checkSessionHash(data.sessionHash); - getMissionHandler()?.updateMission(data); + getApp().getMissionManager()?.updateMission(data); return data.time; }); } }, 1000); window.setInterval(() => { - if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) { + if (!getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { getAirbases((data: AirbasesData) => { checkSessionHash(data.sessionHash); - getMissionHandler()?.updateAirbases(data); + getApp().getMissionManager()?.updateAirbases(data); return data.time; }); } }, 10000); window.setInterval(() => { - if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE){ + if (!getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE){ getBullseye((data: BullseyesData) => { checkSessionHash(data.sessionHash); - getMissionHandler()?.updateBullseyes(data); + getApp().getMissionManager()?.updateBullseyes(data); return data.time; }); } }, 10000); window.setInterval(() => { - if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) { + if (!getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { getLogs((data: any) => { checkSessionHash(data.sessionHash); - getLogPanel().appendLogs(data.logs) + (getApp().getPanelsManager().get("log") as LogPanel).appendLogs(data.logs) return data.time; }); } }, 1000); window.setInterval(() => { - if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) { + if (!getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { getUnits((buffer: ArrayBuffer) => { - var time = getUnitsManager()?.update(buffer); + var time = getApp().getUnitsManager()?.update(buffer); return time; }, false); } }, 250); window.setInterval(() => { - if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) { + if (!getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { getWeapons((buffer: ArrayBuffer) => { - var time = getWeaponsManager()?.update(buffer); + var time = getApp().getWeaponsManager()?.update(buffer); return time; }, false); } }, 250); window.setInterval(() => { - if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) { + if (!getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { getUnits((buffer: ArrayBuffer) => { - var time = getUnitsManager()?.update(buffer); + var time = getApp().getUnitsManager()?.update(buffer); return time; }, true); - getConnectionStatusPanel()?.update(getConnected()); + (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel).update(getConnected()); } }, 5000); window.setInterval(() => { - if (!getPaused() && getMissionHandler().getCommandModeOptions().commandMode != NONE) { + if (!getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { getWeapons((buffer: ArrayBuffer) => { - var time = getWeaponsManager()?.update(buffer); + var time = getApp().getWeaponsManager()?.update(buffer); return time; }, true); } @@ -428,29 +431,29 @@ export function startUpdate() { export function refreshAll() { getAirbases((data: AirbasesData) => { checkSessionHash(data.sessionHash); - getMissionHandler()?.updateAirbases(data); + getApp().getMissionManager()?.updateAirbases(data); return data.time; }); getBullseye((data: BullseyesData) => { checkSessionHash(data.sessionHash); - getMissionHandler()?.updateBullseyes(data); + getApp().getMissionManager()?.updateBullseyes(data); return data.time; }); getLogs((data: any) => { checkSessionHash(data.sessionHash); - getLogPanel().appendLogs(data.logs) + (getApp().getPanelsManager().get("log") as LogPanel).appendLogs(data.logs) return data.time; }); getWeapons((buffer: ArrayBuffer) => { - var time = getWeaponsManager()?.update(buffer); + var time = getApp().getWeaponsManager()?.update(buffer); return time; }, true); getUnits((buffer: ArrayBuffer) => { - var time = getUnitsManager()?.update(buffer); + var time = getApp().getUnitsManager()?.update(buffer); return time; }, true); } @@ -466,7 +469,7 @@ export function checkSessionHash(newSessionHash: string) { export function setConnected(newConnected: boolean) { if (connected != newConnected) - newConnected ? getInfoPopup().setText("Connected to DCS Olympus server") : getInfoPopup().setText("Disconnected from DCS Olympus server"); + newConnected ? (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Connected to DCS Olympus server") : (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Disconnected from DCS Olympus server"); connected = newConnected; if (connected) { @@ -481,7 +484,7 @@ export function getConnected() { export function setPaused(newPaused: boolean) { paused = newPaused; - paused ? getInfoPopup().setText("View paused") : getInfoPopup().setText("View unpaused"); + paused ? (getApp().getPopupsManager().get("infoPopup") as Popup).setText("View paused") : (getApp().getPopupsManager().get("infoPopup") as Popup).setText("View unpaused"); } export function getPaused() { diff --git a/client/src/shortcut/shortcut.ts b/client/src/shortcut/shortcut.ts index a2b9bac0..095ba6cd 100644 --- a/client/src/shortcut/shortcut.ts +++ b/client/src/shortcut/shortcut.ts @@ -1,70 +1,42 @@ import { keyEventWasInInput } from "../other/utils"; -interface IShortcut { - altKey?:boolean; - callback:CallableFunction; - ctrlKey?:boolean; - name?:string; - shiftKey?:boolean; -} - -interface IShortcutKeyboard extends IShortcut { - code:string; - event?:"keydown"|"keyup"; -} - -interface IShortcutMouse extends IShortcut { - button:number; - event:"mousedown"|"mouseup"; -} - export abstract class Shortcut { + #config: ShortcutOptions - #config:IShortcut - - constructor( config:IShortcut ) { + constructor(config: ShortcutOptions) { this.#config = config; } getConfig() { return this.#config; } - } export class ShortcutKeyboard extends Shortcut { - - constructor( config:IShortcutKeyboard ) { - + constructor(config: KeyboardShortcutOptions) { config.event = config.event || "keyup"; - - super( config ); + super(config); - document.addEventListener( config.event, ( ev:any ) => { - - if ( ev instanceof KeyboardEvent === false || keyEventWasInInput( ev )) { + document.addEventListener(config.event, (ev: any) => { + if (ev instanceof KeyboardEvent === false || keyEventWasInInput(ev)) { return; } - if ( config.code !== ev.code ) { + if (config.code !== ev.code) { return; } - if ( ( ( typeof config.altKey !== "boolean" ) || ( typeof config.altKey === "boolean" && ev.altKey === config.altKey ) ) - && ( ( typeof config.ctrlKey !== "boolean" ) || ( typeof config.ctrlKey === "boolean" && ev.ctrlKey === config.ctrlKey ) ) - && ( ( typeof config.shiftKey !== "boolean" ) || ( typeof config.shiftKey === "boolean" && ev.shiftKey === config.shiftKey ) ) ) { - config.callback( ev ); + if (((typeof config.altKey !== "boolean") || (typeof config.altKey === "boolean" && ev.altKey === config.altKey)) + && ((typeof config.ctrlKey !== "boolean") || (typeof config.ctrlKey === "boolean" && ev.ctrlKey === config.ctrlKey)) + && ((typeof config.shiftKey !== "boolean") || (typeof config.shiftKey === "boolean" && ev.shiftKey === config.shiftKey))) { + config.callback(ev); } }); - } - } export class ShortcutMouse extends Shortcut { - - constructor( config:IShortcutMouse ) { - super( config ); + constructor(config: MouseShortcutOptions) { + super(config); } - } \ No newline at end of file diff --git a/client/src/shortcut/shortcutmanager.ts b/client/src/shortcut/shortcutmanager.ts index 54e6386a..6f1acd21 100644 --- a/client/src/shortcut/shortcutmanager.ts +++ b/client/src/shortcut/shortcutmanager.ts @@ -1,33 +1,32 @@ -import { OlympusApp } from "../olympusapp"; import { Manager } from "../other/manager"; import { Shortcut } from "./shortcut"; export class ShortcutManager extends Manager { - #keysBeingHeld:string[] = []; - #keyDownCallbacks:CallableFunction[] = []; - #keyUpCallbacks:CallableFunction[] = []; + #keysBeingHeld: string[] = []; + #keyDownCallbacks: CallableFunction[] = []; + #keyUpCallbacks: CallableFunction[] = []; - constructor( olympusApp:OlympusApp ) { - - super( olympusApp ); + constructor() { - document.addEventListener( "keydown", ( ev:KeyboardEvent ) => { - if ( this.#keysBeingHeld.indexOf( ev.code ) < 0 ) { - this.#keysBeingHeld.push( ev.code ) + super(); + + document.addEventListener("keydown", (ev: KeyboardEvent) => { + if (this.#keysBeingHeld.indexOf(ev.code) < 0) { + this.#keysBeingHeld.push(ev.code) } - this.#keyDownCallbacks.forEach( callback => callback( ev ) ); + this.#keyDownCallbacks.forEach(callback => callback(ev)); }); - document.addEventListener( "keyup", ( ev:KeyboardEvent ) => { - this.#keysBeingHeld = this.#keysBeingHeld.filter( held => held !== ev.code ); - this.#keyUpCallbacks.forEach( callback => callback( ev ) ); + document.addEventListener("keyup", (ev: KeyboardEvent) => { + this.#keysBeingHeld = this.#keysBeingHeld.filter(held => held !== ev.code); + this.#keyUpCallbacks.forEach(callback => callback(ev)); }); } - add( name:string, shortcut:Shortcut ) { - super.add( name, shortcut ); + add(name: string, shortcut: Shortcut) { + super.add(name, shortcut); return this; } @@ -35,24 +34,20 @@ export class ShortcutManager extends Manager { return this.#keysBeingHeld; } - keyComboMatches( combo:string[] ) { - + keyComboMatches(combo: string[]) { const heldKeys = this.getKeysBeingHeld(); - - if ( combo.length !== heldKeys.length ) { + if (combo.length !== heldKeys.length) { return false; } - return combo.every( key => heldKeys.indexOf( key ) > -1 ); - + return combo.every(key => heldKeys.indexOf(key) > -1); } - onKeyDown( callback:CallableFunction ) { - this.#keyDownCallbacks.push( callback ); + onKeyDown(callback: CallableFunction) { + this.#keyDownCallbacks.push(callback); } - onKeyUp( callback:CallableFunction ) { - this.#keyUpCallbacks.push( callback ); + onKeyUp(callback: CallableFunction) { + this.#keyUpCallbacks.push(callback); } - } \ No newline at end of file diff --git a/client/src/unit/databases/aircraftdatabase.ts b/client/src/unit/databases/aircraftdatabase.ts index 31d072cb..74ecf23f 100644 --- a/client/src/unit/databases/aircraftdatabase.ts +++ b/client/src/unit/databases/aircraftdatabase.ts @@ -1,4 +1,4 @@ -import { getMissionHandler } from "../.."; +import { getApp } from "../.."; import { GAME_MASTER } from "../../constants/constants"; import { UnitDatabase } from "./unitdatabase" @@ -12,7 +12,7 @@ export class AircraftDatabase extends UnitDatabase { } getSpawnPointsByName(name: string) { - if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns) + if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns) return 0; const blueprint = this.getByName(name); diff --git a/client/src/unit/databases/groundunitdatabase.ts b/client/src/unit/databases/groundunitdatabase.ts index e34c6313..a4d8213a 100644 --- a/client/src/unit/databases/groundunitdatabase.ts +++ b/client/src/unit/databases/groundunitdatabase.ts @@ -1,4 +1,4 @@ -import { getMissionHandler } from "../.."; +import { getApp } from "../.."; import { GAME_MASTER } from "../../constants/constants"; import { UnitDatabase } from "./unitdatabase" @@ -8,7 +8,7 @@ export class GroundUnitDatabase extends UnitDatabase { } getSpawnPointsByName(name: string) { - if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns) + if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns) return 0; const blueprint = this.getByName(name); diff --git a/client/src/unit/databases/helicopterdatabase.ts b/client/src/unit/databases/helicopterdatabase.ts index a39de1c8..d105d282 100644 --- a/client/src/unit/databases/helicopterdatabase.ts +++ b/client/src/unit/databases/helicopterdatabase.ts @@ -1,4 +1,4 @@ -import { getMissionHandler } from "../.."; +import { getApp } from "../.."; import { GAME_MASTER } from "../../constants/constants"; import { UnitDatabase } from "./unitdatabase" @@ -8,7 +8,7 @@ export class HelicopterDatabase extends UnitDatabase { } getSpawnPointsByName(name: string) { - if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns) + if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns) return 0; const blueprint = this.getByName(name); diff --git a/client/src/unit/databases/navyunitdatabase.ts b/client/src/unit/databases/navyunitdatabase.ts index c45f07c2..6f5111eb 100644 --- a/client/src/unit/databases/navyunitdatabase.ts +++ b/client/src/unit/databases/navyunitdatabase.ts @@ -1,4 +1,4 @@ -import { getMissionHandler } from "../.."; +import { getApp } from "../.."; import { GAME_MASTER } from "../../constants/constants"; import { UnitDatabase } from "./unitdatabase" @@ -8,7 +8,7 @@ export class NavyUnitDatabase extends UnitDatabase { } getSpawnPointsByName(name: string) { - if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns) + if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns) return 0; const blueprint = this.getByName(name); diff --git a/client/src/unit/databases/unitdatabase.ts b/client/src/unit/databases/unitdatabase.ts index 92316d90..b64a6e01 100644 --- a/client/src/unit/databases/unitdatabase.ts +++ b/client/src/unit/databases/unitdatabase.ts @@ -1,7 +1,6 @@ import { LatLng } from "leaflet"; -import { getMissionHandler, getUnitsManager } from "../.."; +import { getApp } from "../.."; import { GAME_MASTER } from "../../constants/constants"; -import { UnitBlueprint } from "../../@types/unitdatabase"; export class UnitDatabase { blueprints: { [key: string]: UnitBlueprint } = {}; @@ -44,15 +43,15 @@ export class UnitDatabase { } getBlueprints() { - if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns) + if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns) return this.blueprints; else { var filteredBlueprints: { [key: string]: UnitBlueprint } = {}; for (let unit in this.blueprints) { const blueprint = this.blueprints[unit]; - if (this.getSpawnPointsByName(blueprint.name) <= getMissionHandler().getAvailableSpawnPoints() && - getMissionHandler().getCommandModeOptions().eras.includes(blueprint.era) && - (!getMissionHandler().getCommandModeOptions().restrictToCoalition || blueprint.coalition === getMissionHandler().getCommandedCoalition() || blueprint.coalition === undefined)) { + if (this.getSpawnPointsByName(blueprint.name) <= getApp().getMissionManager().getAvailableSpawnPoints() && + getApp().getMissionManager().getCommandModeOptions().eras.includes(blueprint.era) && + (!getApp().getMissionManager().getCommandModeOptions().restrictToCoalition || blueprint.coalition === getApp().getMissionManager().getCommandedCoalition() || blueprint.coalition === undefined)) { filteredBlueprints[unit] = blueprint; } } @@ -201,7 +200,7 @@ export class UnitDatabase { var row = Math.floor(idx / gridSize); var col = idx - row * gridSize; var location = new LatLng(initialPosition.lat + col * step, initialPosition.lng + row * step) - getUnitsManager().spawnUnits(this.getCategory(), [{unitType: unitBlueprint.name, location: location, altitude: 1000, loadout: "", liveryID: ""}]); + getApp().getUnitsManager().spawnUnits(this.getCategory(), [{unitType: unitBlueprint.name, location: location, altitude: 1000, loadout: "", liveryID: ""}]); }) } diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index 56c4c54e..7ca1d5d8 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -1,5 +1,5 @@ import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point } from 'leaflet'; -import { getMap, getMissionHandler, getUnitsManager, getWeaponsManager } from '..'; +import { getApp } from '..'; import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM } from '../other/utils'; import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server'; import { CustomMarker } from '../map/markers/custommarker'; @@ -7,12 +7,10 @@ import { SVGInjector } from '@tanem/svg-injector'; import { UnitDatabase } from './databases/unitdatabase'; import { TargetMarker } from '../map/markers/targetmarker'; import { DLINK, DataIndexes, 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, ObjectIconOptions, UnitData } from '../@types/unit'; import { DataExtractor } from '../server/dataextractor'; import { groundUnitDatabase } from './databases/groundunitdatabase'; import { navyUnitDatabase } from './databases/navyunitdatabase'; import { Weapon } from '../weapon/weapon'; -import { LoadoutBlueprint } from '../@types/unitdatabase'; var pathIcon = new Icon({ iconUrl: '/resources/theme/images/markers/marker-icon.png', @@ -147,7 +145,7 @@ export class Unit extends CustomMarker { this.#selectable = true; this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 }); - this.#pathPolyline.addTo(getMap()); + this.#pathPolyline.addTo(getApp().getMap()); this.#contactsPolylines = []; this.#targetPositionMarker = new TargetMarker(new LatLng(0, 0)); this.#targetPositionPolyline = new Polyline([], { color: '#FF0000', weight: 3, opacity: 0.5, smoothFactor: 1 }); @@ -165,7 +163,7 @@ export class Unit extends CustomMarker { this.setHighlighted(false); document.dispatchEvent(new CustomEvent("unitMouseout", { detail: this })); }); - getMap().on("zoomend", () => { this.#onZoom(); }) + getApp().getMap().on("zoomend", () => { this.#onZoom(); }) /* Deselect units if they are hidden */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { @@ -190,7 +188,7 @@ export class Unit extends CustomMarker { /********************** Unit data *************************/ setData(dataExtractor: DataExtractor) { - var updateMarker = !getMap().hasLayer(this); + var updateMarker = !getApp().getMap().hasLayer(this); var datumIndex = 0; while (datumIndex != DataIndexes.endOfData) { @@ -243,7 +241,7 @@ export class Unit extends CustomMarker { if (updateMarker) this.#updateMarker(); - if (this.getSelected() || getMap().getCenterUnit() === this) + if (this.getSelected() || getApp().getMap().getCenterUnit() === this) document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); } @@ -342,7 +340,7 @@ export class Unit extends CustomMarker { } this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected); - if (this.getCategory() === "GroundUnit" && getMap().getZoom() < 13) { + if (this.getCategory() === "GroundUnit" && getApp().getMap().getZoom() < 13) { if (this.#isLeader) this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected)); else @@ -393,11 +391,11 @@ export class Unit extends CustomMarker { } getGroupMembers() { - return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => { return unit != this && unit.#groupName === this.#groupName; }); + return Object.values(getApp().getUnitsManager().getUnits()).filter((unit: Unit) => { return unit != this && unit.#groupName === this.#groupName; }); } belongsToCommandedCoalition() { - if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER && getMissionHandler().getCommandedCoalition() !== this.#coalition) + if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER && getApp().getMissionManager().getCommandedCoalition() !== this.#coalition) return false; return true; } @@ -523,13 +521,13 @@ export class Unit extends CustomMarker { /********************** Visibility *************************/ updateVisibility() { - const hiddenUnits = getMap().getHiddenTypes(); + const hiddenUnits = getApp().getMap().getHiddenTypes(); var hidden = ((this.#human && hiddenUnits.includes("human")) || (this.#controlled == false && hiddenUnits.includes("dcs")) || (hiddenUnits.includes(this.getMarkerCategory())) || (hiddenUnits.includes(this.#coalition)) || (!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 && (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) && + (getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getApp().getMap().getZoom() < 13 && (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) && !(this.getSelected()); this.setHidden(hidden || !this.#alive); @@ -539,16 +537,16 @@ export class Unit extends CustomMarker { this.#hidden = hidden; /* Add the marker if not present */ - if (!getMap().hasLayer(this) && !this.getHidden()) { - if (getMap().isZooming()) - this.once("zoomend", () => { this.addTo(getMap()) }) + if (!getApp().getMap().hasLayer(this) && !this.getHidden()) { + if (getApp().getMap().isZooming()) + this.once("zoomend", () => { this.addTo(getApp().getMap()) }) else - this.addTo(getMap()); + this.addTo(getApp().getMap()); } /* Hide the marker if necessary*/ - if (getMap().hasLayer(this) && this.getHidden()) { - getMap().removeLayer(this); + if (getApp().getMap().hasLayer(this) && this.getHidden()) { + getApp().getMap().removeLayer(this); } } @@ -573,7 +571,7 @@ export class Unit extends CustomMarker { } getLeader() { - return getUnitsManager().getUnitByID(this.#leaderID); + return getApp().getUnitsManager().getUnitByID(this.#leaderID); } canFulfillRole(roles: string | string[]) { @@ -591,7 +589,7 @@ export class Unit extends CustomMarker { isInViewport() { - const mapBounds = getMap().getBounds(); + const mapBounds = getApp().getMap().getBounds(); const unitPos = this.getPosition(); return (unitPos.lng > mapBounds.getWest() @@ -739,9 +737,9 @@ export class Unit extends CustomMarker { /***********************************************/ #onClick(e: any) { if (!this.#preventClick) { - if (getMap().getState() === IDLE || getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) { + if (getApp().getMap().getState() === IDLE || getApp().getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) { if (!e.originalEvent.ctrlKey) - getUnitsManager().deselectAllUnits(); + getApp().getUnitsManager().deselectAllUnits(); this.setSelected(!this.getSelected()); const detail = { "detail": { "unit": this } }; @@ -756,7 +754,7 @@ export class Unit extends CustomMarker { } #onDoubleClick(e: any) { - const unitsManager = getUnitsManager(); + const unitsManager = getApp().getUnitsManager(); Object.values(unitsManager.getUnits()).forEach((unit: Unit) => { if (unit.getAlive() === true && unit.getName() === this.getName() && unit.isInViewport()) unitsManager.selectUnit(unit.ID, false); @@ -768,14 +766,14 @@ export class Unit extends CustomMarker { #onContextMenu(e: any) { var options: { [key: string]: { text: string, tooltip: string } } = {}; - const selectedUnits = getUnitsManager().getSelectedUnits(); - const selectedUnitTypes = getUnitsManager().getSelectedUnitsCategories(); + const selectedUnits = getApp().getUnitsManager().getSelectedUnits(); + const selectedUnitTypes = getApp().getUnitsManager().getSelectedUnitsCategories(); options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it" }; if (selectedUnits.length > 0 && !(selectedUnits.length == 1 && (selectedUnits.includes(this)))) { options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons" }; - if (getUnitsManager().getSelectedUnitsCategories().length == 1 && getUnitsManager().getSelectedUnitsCategories()[0] === "Aircraft") + if (getApp().getUnitsManager().getSelectedUnitsCategories().length == 1 && getApp().getUnitsManager().getSelectedUnitsCategories()[0] === "Aircraft") options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position" };; } else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) { @@ -784,13 +782,13 @@ export class Unit extends CustomMarker { } } - 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]) && getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) !== undefined) options["group"] = { text: "Create group", tooltip: "Create a group from the selected units." }; if (Object.keys(options).length > 0) { - getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng); - getMap().getUnitContextMenu().setOptions(options, (option: string) => { - getMap().hideUnitContextMenu(); + getApp().getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng); + getApp().getMap().getUnitContextMenu().setOptions(options, (option: string) => { + getApp().getMap().hideUnitContextMenu(); this.#executeAction(e, option); }); } @@ -798,13 +796,13 @@ export class Unit extends CustomMarker { #executeAction(e: any, action: string) { if (action === "center-map") - getMap().centerOnUnit(this.ID); + getApp().getMap().centerOnUnit(this.ID); if (action === "attack") - getUnitsManager().selectedUnitsAttackUnit(this.ID); + getApp().getUnitsManager().selectedUnitsAttackUnit(this.ID); else if (action === "refuel") - getUnitsManager().selectedUnitsRefuel(); + getApp().getUnitsManager().selectedUnitsRefuel(); else if (action === "group") - getUnitsManager().selectedUnitsCreateGroup(); + getApp().getUnitsManager().selectedUnitsCreateGroup(); else if (action === "follow") this.#showFollowOptions(e); } @@ -823,12 +821,12 @@ export class Unit extends CustomMarker { 'custom': { text: "Custom", tooltip: "Set a custom formation position" }, } - getMap().getUnitContextMenu().setOptions(options, (option: string) => { - getMap().hideUnitContextMenu(); + getApp().getMap().getUnitContextMenu().setOptions(options, (option: string) => { + getApp().getMap().hideUnitContextMenu(); this.#applyFollowOptions(option); }); - getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng); + getApp().getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng); } #applyFollowOptions(action: string) { @@ -856,12 +854,12 @@ export class Unit extends CustomMarker { var y = upDown; var z = distance * Math.sin(angleRad); - getUnitsManager().selectedUnitsFollowUnit(this.ID, { "x": x, "y": y, "z": z }); + getApp().getUnitsManager().selectedUnitsFollowUnit(this.ID, { "x": x, "y": y, "z": z }); } }); } else { - getUnitsManager().selectedUnitsFollowUnit(this.ID, undefined, action); + getApp().getUnitsManager().selectedUnitsFollowUnit(this.ID, undefined, action); } } @@ -879,7 +877,7 @@ export class Unit extends CustomMarker { this.#miniMapMarker.setStyle({ color: "#ff5858" }); else this.#miniMapMarker.setStyle({ color: "#247be2" }); - this.#miniMapMarker.addTo(getMap().getMiniMapLayerGroup()); + this.#miniMapMarker.addTo(getApp().getMap().getMiniMapLayerGroup()); this.#miniMapMarker.bringToBack(); } else { @@ -890,8 +888,8 @@ export class Unit extends CustomMarker { } } else { - if (this.#miniMapMarker != null && getMap().getMiniMapLayerGroup().hasLayer(this.#miniMapMarker)) { - getMap().getMiniMapLayerGroup().removeLayer(this.#miniMapMarker); + if (this.#miniMapMarker != null && getApp().getMap().getMiniMapLayerGroup().hasLayer(this.#miniMapMarker)) { + getApp().getMap().getMiniMapLayerGroup().removeLayer(this.#miniMapMarker); this.#miniMapMarker = null; } } @@ -976,25 +974,25 @@ export class Unit extends CustomMarker { } /* Set vertical offset for altitude stacking */ - var pos = getMap().latLngToLayerPoint(this.getLatLng()).round(); + var pos = getApp().getMap().latLngToLayerPoint(this.getLatLng()).round(); this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0)); } } #drawPath() { - if (this.#activePath != undefined && getMap().getVisibilityOptions()[SHOW_UNIT_PATHS]) { + if (this.#activePath != undefined && getApp().getMap().getVisibilityOptions()[SHOW_UNIT_PATHS]) { var points = []; points.push(new LatLng(this.#position.lat, this.#position.lng)); /* Add markers if missing */ while (this.#pathMarkers.length < Object.keys(this.#activePath).length) { - var marker = new Marker([0, 0], { icon: pathIcon }).addTo(getMap()); + var marker = new Marker([0, 0], { icon: pathIcon }).addTo(getApp().getMap()); this.#pathMarkers.push(marker); } /* Remove markers if too many */ while (this.#pathMarkers.length > Object.keys(this.#activePath).length) { - getMap().removeLayer(this.#pathMarkers[this.#pathMarkers.length - 1]); + getApp().getMap().removeLayer(this.#pathMarkers[this.#pathMarkers.length - 1]); this.#pathMarkers.splice(this.#pathMarkers.length - 1, 1) } @@ -1016,7 +1014,7 @@ export class Unit extends CustomMarker { #clearPath() { for (let WP in this.#pathMarkers) { - getMap().removeLayer(this.#pathMarkers[WP]); + getApp().getMap().removeLayer(this.#pathMarkers[WP]); } this.#pathMarkers = []; this.#pathPolyline.setLatLngs([]); @@ -1024,25 +1022,25 @@ export class Unit extends CustomMarker { #drawContacts() { this.#clearContacts(); - if (getMap().getVisibilityOptions()[SHOW_CONTACT_LINES]) { + if (getApp().getMap().getVisibilityOptions()[SHOW_CONTACT_LINES]) { for (let index in this.#contacts) { var contactData = this.#contacts[index]; var contact: Unit | Weapon | null; - if (contactData.ID in getUnitsManager().getUnits()) - contact = getUnitsManager().getUnitByID(contactData.ID); + if (contactData.ID in getApp().getUnitsManager().getUnits()) + contact = getApp().getUnitsManager().getUnitByID(contactData.ID); else - contact = getWeaponsManager().getWeaponByID(contactData.ID); + contact = getApp().getWeaponsManager().getWeaponByID(contactData.ID); if (contact != null && contact.getAlive()) { var startLatLng = new LatLng(this.#position.lat, this.#position.lng); var endLatLng: LatLng; if (contactData.detectionMethod === RWR) { var bearingToContact = bearing(this.#position.lat, this.#position.lng, contact.getPosition().lat, contact.getPosition().lng); - var startXY = getMap().latLngToContainerPoint(startLatLng); + var startXY = getApp().getMap().latLngToContainerPoint(startLatLng); var endX = startXY.x + 80 * Math.sin(deg2rad(bearingToContact)); var endY = startXY.y - 80 * Math.cos(deg2rad(bearingToContact)); - endLatLng = getMap().containerPointToLatLng(new Point(endX, endY)); + endLatLng = getApp().getMap().containerPointToLatLng(new Point(endX, endY)); } else endLatLng = new LatLng(contact.getPosition().lat, contact.getPosition().lng); @@ -1057,7 +1055,7 @@ export class Unit extends CustomMarker { else color = "#FFFFFF"; var contactPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 1, smoothFactor: 1, dashArray: "4, 8" }); - contactPolyline.addTo(getMap()); + contactPolyline.addTo(getApp().getMap()); this.#contactsPolylines.push(contactPolyline) } } @@ -1066,17 +1064,17 @@ export class Unit extends CustomMarker { #clearContacts() { for (let index in this.#contactsPolylines) { - getMap().removeLayer(this.#contactsPolylines[index]) + getApp().getMap().removeLayer(this.#contactsPolylines[index]) } } #drawTarget() { - if (this.#targetPosition.lat != 0 && this.#targetPosition.lng != 0 && getMap().getVisibilityOptions()[SHOW_UNIT_PATHS]) { + if (this.#targetPosition.lat != 0 && this.#targetPosition.lng != 0 && getApp().getMap().getVisibilityOptions()[SHOW_UNIT_PATHS]) { this.#drawTargetPosition(this.#targetPosition); } - else if (this.#targetID != 0 && getMap().getVisibilityOptions()[SHOW_UNIT_TARGETS]) { - const target = getUnitsManager().getUnitByID(this.#targetID); - if (target && (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || (this.belongsToCommandedCoalition() && getUnitsManager().getUnitDetectedMethods(target).some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))))) { + else if (this.#targetID != 0 && getApp().getMap().getVisibilityOptions()[SHOW_UNIT_TARGETS]) { + const target = getApp().getUnitsManager().getUnitByID(this.#targetID); + if (target && (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || (this.belongsToCommandedCoalition() && getApp().getUnitsManager().getUnitDetectedMethods(target).some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))))) { this.#drawTargetPosition(target.getPosition()); } } @@ -1085,20 +1083,20 @@ export class Unit extends CustomMarker { } #drawTargetPosition(targetPosition: LatLng) { - if (!getMap().hasLayer(this.#targetPositionMarker)) - this.#targetPositionMarker.addTo(getMap()); - if (!getMap().hasLayer(this.#targetPositionPolyline)) - this.#targetPositionPolyline.addTo(getMap()); + if (!getApp().getMap().hasLayer(this.#targetPositionMarker)) + this.#targetPositionMarker.addTo(getApp().getMap()); + if (!getApp().getMap().hasLayer(this.#targetPositionPolyline)) + this.#targetPositionPolyline.addTo(getApp().getMap()); this.#targetPositionMarker.setLatLng(new LatLng(targetPosition.lat, targetPosition.lng)); this.#targetPositionPolyline.setLatLngs([new LatLng(this.#position.lat, this.#position.lng), new LatLng(targetPosition.lat, targetPosition.lng)]) } #clearTarget() { - if (getMap().hasLayer(this.#targetPositionMarker)) - this.#targetPositionMarker.removeFrom(getMap()); + if (getApp().getMap().hasLayer(this.#targetPositionMarker)) + this.#targetPositionMarker.removeFrom(getApp().getMap()); - if (getMap().hasLayer(this.#targetPositionPolyline)) - this.#targetPositionPolyline.removeFrom(getMap()); + if (getApp().getMap().hasLayer(this.#targetPositionPolyline)) + this.#targetPositionPolyline.removeFrom(getApp().getMap()); } #onZoom() { diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index cf1808a3..c0a0f6d5 100644 --- a/client/src/unit/unitsmanager.ts +++ b/client/src/unit/unitsmanager.ts @@ -1,18 +1,19 @@ import { LatLng, LatLngBounds } from "leaflet"; -import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitsManager, getWeaponsManager } from ".."; +import { getApp } from ".."; import { Unit } from "./unit"; -import { cloneUnits, deleteUnit, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server"; +import { cloneUnits, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server"; import { bearingAndDistanceToLatLng, deg2rad, getUnitDatabaseByCategory, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils"; import { CoalitionArea } from "../map/coalitionarea/coalitionarea"; import { groundUnitDatabase } from "./databases/groundunitdatabase"; import { DataIndexes, GAME_MASTER, IADSDensities, IDLE, MOVE_UNIT } from "../constants/constants"; import { DataExtractor } from "../server/dataextractor"; -import { Contact, UnitData, UnitSpawnTable } from "../@types/unit"; import { citiesDatabase } from "./citiesDatabase"; import { aircraftDatabase } from "./databases/aircraftdatabase"; import { helicopterDatabase } from "./databases/helicopterdatabase"; import { navyUnitDatabase } from "./databases/navyunitdatabase"; import { TemporaryUnitMarker } from "../map/markers/temporaryunitmarker"; +import { Popup } from "../popups/popup"; +import { HotgroupPanel } from "../panels/hotgrouppanel"; /** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only * result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows @@ -124,12 +125,12 @@ export class UnitsManager { /* If we are not in Game Master mode, visibility of units by the user is determined by the detections of the units themselves. This is performed here. This operation is computationally expensive, therefore it is only performed when #requestDetectionUpdate is true. This happens whenever a change in the detectionUpdates is detected */ - if (this.#requestDetectionUpdate && getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) { + if (this.#requestDetectionUpdate && getApp().getMissionManager().getCommandModeOptions().commandMode != GAME_MASTER) { /* Create a dictionary of empty detection methods arrays */ var detectionMethods: { [key: string]: number[] } = {}; for (let ID in this.#units) detectionMethods[ID] = []; - for (let ID in getWeaponsManager().getWeapons()) + for (let ID in getApp().getWeaponsManager().getWeapons()) detectionMethods[ID] = []; /* Fill the array with the detection methods */ @@ -152,8 +153,8 @@ export class UnitsManager { } /* Set the detection methods for every weapon (weapons must be detected too) */ - for (let ID in getWeaponsManager().getWeapons()) { - const weapon = getWeaponsManager().getWeaponByID(parseInt(ID)); + for (let ID in getApp().getWeaponsManager().getWeapons()) { + const weapon = getApp().getWeaponsManager().getWeaponByID(parseInt(ID)); weapon?.setDetectionMethods(detectionMethods[ID]); } @@ -636,7 +637,7 @@ export class UnitsManager { } cloneUnits(units, true, 0 /* No spawn points, we delete the original units */); } else { - getInfoPopup().setText(`Groups can only be created from units of the same category`); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Groups can only be created from units of the same category`); } } @@ -659,7 +660,7 @@ export class UnitsManager { selectedUnits[idx].setHotgroup(hotgroup); } this.#showActionMessage(selectedUnits, `added to hotgroup ${hotgroup}`); - getHotgroupPanel().refreshHotgroups(); + (getApp().getPanelsManager().get("hotgroup") as HotgroupPanel).refreshHotgroups(); } /** Delete the selected units @@ -730,7 +731,7 @@ export class UnitsManager { selectedUnitsCopy() { /* A JSON is used to deepcopy the units, creating a "snapshot" of their properties at the time of the copy */ this.#copiedUnits = JSON.parse(JSON.stringify(this.getSelectedUnits().map((unit: Unit) => { return unit.getData() }))); /* Can be applied to humans too */ - getInfoPopup().setText(`${this.#copiedUnits.length} units copied`); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${this.#copiedUnits.length} units copied`); } /*********************** Unit manipulation functions ************************/ @@ -742,9 +743,9 @@ export class UnitsManager { let spawnPoints = 0; /* If spawns are restricted, check that the user has the necessary spawn points */ - if (getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) { - if (getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0) { - getInfoPopup().setText(`Units can be pasted only during SETUP phase`); + if (getApp().getMissionManager().getCommandModeOptions().commandMode != GAME_MASTER) { + if (getApp().getMissionManager().getCommandModeOptions().restrictSpawns && getApp().getMissionManager().getRemainingSetupTime() < 0) { + (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Units can be pasted only during SETUP phase`); return false; } @@ -754,8 +755,8 @@ export class UnitsManager { spawnPoints += unitSpawnPoints; }) - if (spawnPoints > getMissionHandler().getAvailableSpawnPoints()) { - getInfoPopup().setText("Not enough spawn points available!"); + if (spawnPoints > getApp().getMissionManager().getAvailableSpawnPoints()) { + (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Not enough spawn points available!"); return false; } } @@ -784,8 +785,8 @@ export class UnitsManager { var units: { ID: number, location: LatLng }[] = []; let markers: TemporaryUnitMarker[] = []; groups[groupName].forEach((unit: UnitData) => { - var position = new LatLng(getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getMap().getMouseCoordinates().lng + unit.position.lng - avgLng); - markers.push(getMap().addTemporaryMarker(position, unit.name, unit.coalition)); + var position = new LatLng(getApp().getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getApp().getMap().getMouseCoordinates().lng + unit.position.lng - avgLng); + markers.push(getApp().getMap().addTemporaryMarker(position, unit.name, unit.coalition)); units.push({ ID: unit.ID, location: position }); }); @@ -797,10 +798,10 @@ export class UnitsManager { } }); } - getInfoPopup().setText(`${this.#copiedUnits.length} units pasted`); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${this.#copiedUnits.length} units pasted`); } else { - getInfoPopup().setText("No units copied!"); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText("No units copied!"); } } @@ -888,7 +889,7 @@ export class UnitsManager { var units = aliveUnits.map((unit: UnitData) => { return { unitType: unit.name, location: unit.position, liveryID: "" } }); - getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true); + getApp().getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true); } } }; @@ -911,18 +912,18 @@ export class UnitsManager { spawnUnits(category: string, units: UnitSpawnTable[], coalition: string = "blue", immediate: boolean = true, airbase: string = "", country: string = "", callback: CallableFunction = () => {}) { var spawnPoints = 0; var spawnFunction = () => {}; - var spawnsRestricted = getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER; + var spawnsRestricted = getApp().getMissionManager().getCommandModeOptions().restrictSpawns && getApp().getMissionManager().getRemainingSetupTime() < 0 && getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER; if (category === "Aircraft") { if (airbase == "" && spawnsRestricted) { - getInfoPopup().setText("Aircrafts can be air spawned during the SETUP phase only"); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Aircrafts can be air spawned during the SETUP phase only"); return false; } spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {return points + aircraftDatabase.getSpawnPointsByName(unit.unitType)}, 0); spawnFunction = () => spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints, callback); } else if (category === "Helicopter") { if (airbase == "" && spawnsRestricted) { - getInfoPopup().setText("Helicopters can be air spawned during the SETUP phase only"); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Helicopters can be air spawned during the SETUP phase only"); return false; } spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {return points + helicopterDatabase.getSpawnPointsByName(unit.unitType)}, 0); @@ -930,7 +931,7 @@ export class UnitsManager { } else if (category === "GroundUnit") { if (spawnsRestricted) { - getInfoPopup().setText("Ground units can be spawned during the SETUP phase only"); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Ground units can be spawned during the SETUP phase only"); return false; } spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0); @@ -938,19 +939,19 @@ export class UnitsManager { } else if (category === "NavyUnit") { if (spawnsRestricted) { - getInfoPopup().setText("Navy units can be spawned during the SETUP phase only"); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Navy units can be spawned during the SETUP phase only"); return false; } spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {return points + navyUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0); spawnFunction = () => spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback); } - if (spawnPoints <= getMissionHandler().getAvailableSpawnPoints()) { - getMissionHandler().setSpentSpawnPoints(spawnPoints); + if (spawnPoints <= getApp().getMissionManager().getAvailableSpawnPoints()) { + getApp().getMissionManager().setSpentSpawnPoints(spawnPoints); spawnFunction(); return true; } else { - getInfoPopup().setText("Not enough spawn points available!"); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Not enough spawn points available!"); return false; } } @@ -969,7 +970,7 @@ export class UnitsManager { if (this.getSelectedUnits().length > 0) { /* 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) { - getMap().setState(MOVE_UNIT); + getApp().getMap().setState(MOVE_UNIT); window.setTimeout(() => { document.dispatchEvent(new CustomEvent("unitsSelection", { detail: this.getSelectedUnits() })); this.#selectionEventDisabled = false; @@ -978,14 +979,14 @@ export class UnitsManager { } } else { - getMap().setState(IDLE); + getApp().getMap().setState(IDLE); document.dispatchEvent(new CustomEvent("clearSelection")); } } #onUnitDeselection(unit: Unit) { if (this.getSelectedUnits().length == 0) { - getMap().setState(IDLE); + getApp().getMap().setState(IDLE); document.dispatchEvent(new CustomEvent("clearSelection")); } else @@ -994,8 +995,8 @@ export class UnitsManager { #showActionMessage(units: Unit[], message: string) { if (units.length == 1) - getInfoPopup().setText(`${units[0].getUnitName()} ${message}`); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${units[0].getUnitName()} ${message}`); else if (units.length > 1) - getInfoPopup().setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`); + (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`); } } \ No newline at end of file diff --git a/client/src/weapon/weapon.ts b/client/src/weapon/weapon.ts index 777afea3..139a1651 100644 --- a/client/src/weapon/weapon.ts +++ b/client/src/weapon/weapon.ts @@ -1,10 +1,9 @@ import { LatLng, DivIcon, Map } from 'leaflet'; -import { getMap, getMissionHandler, getUnitsManager } from '..'; +import { getApp } from '..'; import { enumToCoalition, mToFt, msToKnots, rad2deg } from '../other/utils'; import { CustomMarker } from '../map/markers/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 { @@ -58,7 +57,7 @@ export class Weapon extends CustomMarker { /********************** Unit data *************************/ setData(dataExtractor: DataExtractor) { - var updateMarker = !getMap().hasLayer(this); + var updateMarker = !getApp().getMap().hasLayer(this); var datumIndex = 0; while (datumIndex != DataIndexes.endOfData) { @@ -116,7 +115,7 @@ export class Weapon extends CustomMarker { } belongsToCommandedCoalition() { - if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER && getMissionHandler().getCommandedCoalition() !== this.#coalition) + if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER && getApp().getMissionManager().getCommandedCoalition() !== this.#coalition) return false; return true; } @@ -166,7 +165,7 @@ export class Weapon extends CustomMarker { /********************** Visibility *************************/ updateVisibility() { - const hiddenUnits = getMap().getHiddenTypes(); + const hiddenUnits = getApp().getMap().getHiddenTypes(); var hidden = (hiddenUnits.includes(this.getMarkerCategory())) || (hiddenUnits.includes(this.#coalition)) || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0); @@ -178,16 +177,16 @@ export class Weapon extends CustomMarker { this.#hidden = hidden; /* Add the marker if not present */ - if (!getMap().hasLayer(this) && !this.getHidden()) { - if (getMap().isZooming()) - this.once("zoomend", () => {this.addTo(getMap())}) + if (!getApp().getMap().hasLayer(this) && !this.getHidden()) { + if (getApp().getMap().isZooming()) + this.once("zoomend", () => {this.addTo(getApp().getMap())}) else - this.addTo(getMap()); + this.addTo(getApp().getMap()); } /* Hide the marker if necessary*/ - if (getMap().hasLayer(this) && this.getHidden()) { - getMap().removeLayer(this); + if (getApp().getMap().hasLayer(this) && this.getHidden()) { + getApp().getMap().removeLayer(this); } } @@ -250,7 +249,7 @@ export class Weapon extends CustomMarker { } /* Set vertical offset for altitude stacking */ - var pos = getMap().latLngToLayerPoint(this.getLatLng()).round(); + var pos = getApp().getMap().latLngToLayerPoint(this.getLatLng()).round(); this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y); } } diff --git a/client/src/weapon/weaponsmanager.ts b/client/src/weapon/weaponsmanager.ts index 57b6e48d..4de95b38 100644 --- a/client/src/weapon/weaponsmanager.ts +++ b/client/src/weapon/weaponsmanager.ts @@ -1,8 +1,7 @@ -import { getMissionHandler, getUnitsManager } from ".."; +import { getApp } from ".."; import { Weapon } from "./weapon"; import { DataIndexes, GAME_MASTER } from "../constants/constants"; import { DataExtractor } from "../server/dataextractor"; -import { Contact } from "../@types/unit"; /** The WeaponsManager handles the creation and update of weapons. Data is strictly updated by the server ONLY. */ export class WeaponsManager { @@ -93,7 +92,7 @@ export class WeaponsManager { */ getWeaponDetectedMethods(weapon: Weapon) { var detectionMethods: number[] = []; - var units = getUnitsManager().getUnits(); + var units = getApp().getUnitsManager().getUnits(); for (let idx in units) { if (units[idx].getAlive() && units[idx].getIsLeader() && units[idx].getCoalition() !== "neutral" && units[idx].getCoalition() != weapon.getCoalition()) { diff --git a/client/tsconfig.json b/client/tsconfig.json index c293f5f2..63da637e 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -23,19 +23,22 @@ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ "module": "commonjs", /* Specify what module code is generated. */ - "rootDir": "./src", /* Specify the root folder within your source files. */ + "rootDirs": ["./src", "./@types"], /* Specify the root folder within your source files. */ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ "typeRoots": [ - "./node_modules/@types" + "./node_modules/@types", + "./@types" ], /* Specify multiple folders that act like './node_modules/@types'. */ "types": [ "leaflet", "geojson", "node", - "formatcoords" + "formatcoords", + "olympus", + "dom" ], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ @@ -100,6 +103,7 @@ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, "include": [ - "src/**/*.ts" + "src/**/*.ts", + "@types/*.d.ts" ] } \ No newline at end of file diff --git a/client/views/aic/aic.ejs b/client/views/aic/aic.ejs deleted file mode 100644 index 229a5a82..00000000 --- a/client/views/aic/aic.ejs +++ /dev/null @@ -1,52 +0,0 @@ -
-
-
-
- -
-
×
-
AIC Help
-
-

How to be a good AIC and get people to do stuff good, too.

-
-
[DCS with Volvo video]
-
-
-
- -
- -
- -
-

My callsign

-
Magic
-
- -
- - -
- -
-

Control

-
- - -
-
- - -
-
- -
- -

Formations

- -
- -
- -
\ No newline at end of file diff --git a/client/views/atc/addflight.ejs b/client/views/atc/addflight.ejs deleted file mode 100644 index 97e1ae5b..00000000 --- a/client/views/atc/addflight.ejs +++ /dev/null @@ -1,5 +0,0 @@ -
-
- - -
\ No newline at end of file diff --git a/client/views/atc/atc.ejs b/client/views/atc/atc.ejs deleted file mode 100644 index 65984a3a..00000000 --- a/client/views/atc/atc.ejs +++ /dev/null @@ -1,11 +0,0 @@ -<%- include('board.ejs', { - "boardId": "strip-board-tower", - "boardType": "tower", - "headers": [ "Flight", "a. Alt", "alt", "a. Speed", "Speed" ] -}) %> - -<%- include('board.ejs', { - "boardId": "strip-board-ground", - "boardType": "ground", - "headers": [ "Flight", "Status", "T/O Time", "TTG" ] -}) %> \ No newline at end of file diff --git a/client/views/atc/board.ejs b/client/views/atc/board.ejs deleted file mode 100644 index 58d3dfcc..00000000 --- a/client/views/atc/board.ejs +++ /dev/null @@ -1,22 +0,0 @@ -
- -
- -
-

<%= boardType %>

- <%- include('addflight.ejs') %> -
-
- -
-
-
- <% headers.forEach( header => { %> -
<%= header %>
- <% }); %> -
-
-
-
- -
\ No newline at end of file diff --git a/client/views/atc/unitdatatable.ejs b/client/views/atc/unitdatatable.ejs deleted file mode 100644 index 0c090652..00000000 --- a/client/views/atc/unitdatatable.ejs +++ /dev/null @@ -1,13 +0,0 @@ -
- -
- -
-

Unit list

-
- -
- -
- -
\ No newline at end of file diff --git a/client/views/index.ejs b/client/views/index.ejs index e78b0a48..2c08cc48 100644 --- a/client/views/index.ejs +++ b/client/views/index.ejs @@ -23,9 +23,6 @@ - - <%- include('other/controltips.ejs') %> - <%- include('panels/unitcontrol.ejs') %> <%- include('panels/unitinfo.ejs') %> <%- include('panels/mouseinfo.ejs') %> diff --git a/client/views/other/controltips.ejs b/client/views/other/controltips.ejs deleted file mode 100644 index 463b5c1d..00000000 --- a/client/views/other/controltips.ejs +++ /dev/null @@ -1 +0,0 @@ -
\ No newline at end of file