Implemented basic Plugin handling

This commit is contained in:
Pax1601 2023-09-15 17:05:26 +02:00
parent ad06117b78
commit 588228c050
75 changed files with 1920 additions and 1657 deletions

View File

@ -29,17 +29,8 @@ declare global {
listener: (this: Document, ev: CustomEventMap[K]) => void): void;
dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void;
}
function getOlympusPlugin(): OlympusPlugin;
}
export interface ConfigParameters {
port: number;
address: string;
}
export interface ContextMenuOption {
tooltip: string;
src: string;
callback: CallableFunction;
}
export { };
export { };

267
client/@types/olympus.d.ts vendored Normal file
View File

@ -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;
}

View File

@ -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);

View File

@ -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";

View File

@ -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

View File

@ -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<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _ControlTips_instances, _ControlTips_element, _ControlTips_app, _ControlTips_shortcutManager, _ControlTips_cursorIsHoveringOverUnit, _ControlTips_cursorIsHoveringOverAirbase, _ControlTips_updateTips;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ControlTips = void 0;
class ControlTips {
constructor() {
_ControlTips_instances.add(this);
_ControlTips_element.set(this, void 0);
_ControlTips_app.set(this, void 0);
_ControlTips_shortcutManager.set(this, void 0);
_ControlTips_cursorIsHoveringOverUnit.set(this, false);
_ControlTips_cursorIsHoveringOverAirbase.set(this, false);
__classPrivateFieldSet(this, _ControlTips_element, document.createElement("div"), "f");
__classPrivateFieldGet(this, _ControlTips_element, "f").id = "control-tips-panel";
document.body.appendChild(__classPrivateFieldGet(this, _ControlTips_element, "f"));
console.log("HELLO");
}
getName() {
return "Control Tips Plugin";
}
initialize(app) {
__classPrivateFieldSet(this, _ControlTips_app, app, "f");
__classPrivateFieldSet(this, _ControlTips_shortcutManager, __classPrivateFieldGet(this, _ControlTips_app, "f").getShortcutManager(), "f");
__classPrivateFieldGet(this, _ControlTips_shortcutManager, "f").onKeyDown(() => {
__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 += `<div><span class="key">${tip.key}</span><span class="action">${tip.action}</span></div>`;
});
};
},{}],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]);

View File

@ -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=="
}
}
}

View File

@ -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": {}
}

View File

@ -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"
}

View File

@ -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 = <HTMLElement>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<object> = [
const combos: Array<object> = [
{
"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 += `<div><span class="key">${tip.key}</span><span class="action">${tip.action}</span></div>`
});
}
}

View File

@ -0,0 +1,5 @@
import { ControlTipsPlugin } from "./controltips";
globalThis.getOlympusPlugin = () => {
return new ControlTipsPlugin();
}

View File

@ -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;
}

View File

@ -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 '<reference>'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"
]
}

View File

@ -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<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _ControlTips_instances, _ControlTips_element, _ControlTips_app, _ControlTips_shortcutManager, _ControlTips_cursorIsHoveringOverUnit, _ControlTips_cursorIsHoveringOverAirbase, _ControlTips_updateTips;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ControlTips = void 0;
class ControlTips {
constructor() {
_ControlTips_instances.add(this);
_ControlTips_element.set(this, void 0);
_ControlTips_app.set(this, void 0);
_ControlTips_shortcutManager.set(this, void 0);
_ControlTips_cursorIsHoveringOverUnit.set(this, false);
_ControlTips_cursorIsHoveringOverAirbase.set(this, false);
__classPrivateFieldSet(this, _ControlTips_element, document.createElement("div"), "f");
__classPrivateFieldGet(this, _ControlTips_element, "f").id = "control-tips-panel";
document.body.appendChild(__classPrivateFieldGet(this, _ControlTips_element, "f"));
console.log("HELLO");
}
getName() {
return "Control Tips Plugin";
}
initialize(app) {
__classPrivateFieldSet(this, _ControlTips_app, app, "f");
__classPrivateFieldSet(this, _ControlTips_shortcutManager, __classPrivateFieldGet(this, _ControlTips_app, "f").getShortcutManager(), "f");
__classPrivateFieldGet(this, _ControlTips_shortcutManager, "f").onKeyDown(() => {
__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 += `<div><span class="key">${tip.key}</span><span class="action">${tip.action}</span></div>`;
});
};
},{}],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]);

View File

@ -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"
}

View File

@ -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;
}

View File

@ -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 {

View File

@ -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("");
});

23
client/routes/plugins.js Normal file
View File

@ -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;

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

151
client/src/app.ts Normal file
View File

@ -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();
}
}

View File

@ -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 &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
attribution: "Tiles &copy; Esri &mdash; 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}',

View File

@ -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" );

View File

@ -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

View File

@ -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())

View File

@ -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();

View File

@ -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"));

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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 );
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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)

View File

@ -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 = "";

View File

@ -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) {

View File

@ -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
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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));
});
}

View File

@ -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

View File

@ -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<Unit[]>) => 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";
}
}

View File

@ -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 = <HTMLElement>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() );
}
}

View File

@ -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);
});
}
}
}

View File

@ -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 );
}
}

View File

@ -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);

View File

@ -1,4 +1,3 @@
import { Ammo } from "../@types/unit";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";

View File

@ -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;
}
}

View File

@ -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();
})
}
}

View File

@ -1,5 +1,4 @@
import { LatLng } from "leaflet";
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../@types/unit";
export class DataExtractor {
#seekPosition = 0;

View File

@ -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() {

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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: ""}]);
})
}

View File

@ -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() {

View File

@ -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}`);
}
}

View File

@ -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);
}
}

View File

@ -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())
{

View File

@ -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"
]
}

View File

@ -1,52 +0,0 @@
<div class="ol-panel aic-panel" id="aic-control-panel" data-feature-switch="aic">
<div class="olympus-button" id="toggle-aic-button"></div>
<div class="olympus-button" id="aic-help-button"></div>
</div>
<div id="aic-help" class="olympus-dialog hide" data-feature-switch="aic">
<div class="olympus-dialog-close">&times;</div>
<div class="olympus-dialog-header">AIC Help</div>
<div class="olympus-dialog-content">
<p>How to be a good AIC and get people to do stuff good, too.</p>
<div
style="align-items: center; background:black; color:white; display:flex; height:250px; justify-content: center; justify-self: center; width:450px;">
<div>[DCS with Volvo video]</div>
</div>
</div>
</div>
<div id="aic-teleprompt" data-feature-switch="aic"></div>
<div id="aic-callsign-panel" class="aic-panel" data-feature-switch="aic">
<div class="aic-panel">
<h2>My callsign</h2>
<div>Magic</div>
</div>
</div>
<div id="aic-toolbox" class="aic-panel" data-feature-switch="aic">
<div id="aic-control-type" class="aic-toolbox-panel">
<h2>Control</h2>
<div>
<input type="radio" name="control-type" id="control-type-broadcast" value="broadcast" checked="checked" />
<label for="control-type-broadcast">Broadcast</label>
</div>
<div>
<input type="radio" name="control-type" id="control-type-tactical" value="tactical" />
<label for="control-type-tactical">Tactical</label>
</div>
</div>
<div id="aic-formation-panel" class="aic-toolbox-panel">
<h2>Formations</h2>
<div id="aic-formation-list"></div>
</div>
</div>

View File

@ -1,5 +0,0 @@
<form class="ol-strip-board-add-flight">
<div class="ol-auto-suggest"></div>
<input type="text" name="unitName" placeholder="Flight search" />
<button class="add-flight-by-click" title="Add unit via click"><img src="/resources/theme/images/icons/crosshairs-solid.svg" /></button>
</form>

View File

@ -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" ]
}) %>

View File

@ -1,22 +0,0 @@
<div id="<%= boardId %>" class="ol-panel ol-dialog ol-strip-board ol-draggable hide" data-board-type="<%= boardType %>" data-feature-switch="atc">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3><%= boardType %></h3>
<%- include('addflight.ejs') %>
<div class="ol-strip-board-clock"></div>
</div>
<div class="ol-dialog-content">
<div class="ol-strip-board-headers">
<div><!-- handles --></div>
<% headers.forEach( header => { %>
<div><%= header %></div>
<% }); %>
<div><!-- delete --></div>
</div>
<div class="ol-strip-board-strips ol-sortable"></div>
</div>
</div>

View File

@ -1,13 +0,0 @@
<div id="unit-data-table" class="ol-panel ol-dialog scrollable" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h4>Unit list</h4>
</div>
<div id="unit-list" class="ol-dialog-content">
<!-- Here the list of units is shown -->
</div>
</div>

View File

@ -23,9 +23,6 @@
</div>
<!-- Panels -->
<%- include('other/controltips.ejs') %>
<%- include('panels/unitcontrol.ejs') %>
<%- include('panels/unitinfo.ejs') %>
<%- include('panels/mouseinfo.ejs') %>

View File

@ -1 +0,0 @@
<div id="control-tips-panel"></div>