Removed unused website folder

This commit is contained in:
Pax1601 2024-10-08 08:32:30 +02:00
parent 88cf20a4d9
commit 84a1375663
123 changed files with 0 additions and 26852 deletions

View File

@ -1,20 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/public/",
"sourceMapPathOverrides": {
"src/*": "${workspaceFolder}/src/*"
},
"preLaunchTask": "watch",
"port": 9222
}
]
}

View File

@ -1,29 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "check-setup",
"type": "shell",
"command": "cd .. ; ./check_setup.bat",
"isBackground": false
},
{
"label": "watch",
"type": "shell",
"command": "./scripts/watch.bat",
"isBackground": true,
"problemMatcher":{
"owner": "custom",
"base": "$tsc-watch",
"background": {
"activeOnStart": true,
"beginsPattern": "watchify",
"endsPattern": "bytes written"
}
},
"dependsOn": ["check-setup"]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,44 +0,0 @@
{
"name": "DCSOlympus Website",
"version": "{{OLYMPUS_VERSION_NUMBER}}",
"private": true,
"scripts": {
"emit-declarations": "call ./scripts/emit-declarations.bat",
"build-release": "call ./scripts/build-release.bat",
"build-debug": "call ./scripts/build-debug.bat",
"watch": "call ./scripts/watch.bat"
},
"devDependencies": {
"@babel/preset-env": "^7.21.4",
"@tanem/svg-injector": "^10.1.68",
"@turf/turf": "^6.5.0",
"@types/formatcoords": "^1.1.0",
"@types/geojson": "^7946.0.10",
"@types/leaflet": "^1.9.0",
"@types/node": "^18.16.1",
"@types/sortablejs": "^1.15.0",
"@types/svg-injector": "^0.0.29",
"ajv": "^8.12.0",
"babelify": "^10.0.0",
"browserify": "^17.0.0",
"concurrently": "^7.6.0",
"cp": "^0.2.0",
"esmify": "^2.1.1",
"formatcoords": "^1.1.3",
"geodesy": "^1.1.2",
"js-sha256": "^0.10.1",
"leaflet": "^1.9.3",
"leaflet-control-mini-map": "^0.4.0",
"leaflet-gesture-handling": "^1.2.2",
"leaflet-path-drag": "*",
"leaflet.nauticscale": "^1.1.0",
"nodemon": "^3.0.2",
"requirejs": "^2.3.6",
"sortablejs": "^1.15.0",
"tsify": "^5.0.4",
"tslib": "latest",
"typescript": "^4.9.4",
"usng.js": "^0.4.5",
"watchify": "^4.0.0"
}
}

View File

@ -1,11 +0,0 @@
set plugin-name=boilerplateplugin
mkdir .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%
copy .\\index.js .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\index.js
copy .\\plugin.json .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\plugin.json
copy .\\style.css .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\style.css
mkdir .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\images
copy .\\images\\*.* .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\images\\

Binary file not shown.

Before

Width:  |  Height:  |  Size: 928 B

View File

@ -1,14 +0,0 @@
{
"name": "boilerplateplugin",
"version": "v0.0.1",
"private": true,
"scripts": {
"build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat"
},
"devDependencies": {
"@types/sortablejs": "^1.15.4",
"browserify": "^17.0.0",
"sortablejs": "^1.15.0",
"tsify": "^5.0.4"
}
}

View File

@ -1,7 +0,0 @@
{
"name": "Boilerplate",
"version": "0.0.1",
"description": "Base plugin starter",
"authorName": "",
"authorContact": ""
}

View File

@ -1,3 +0,0 @@
# Boilerplate plugin
See: https://github.com/Pax1601/DCSOlympus/wiki/Developer-Guide

View File

@ -1,27 +0,0 @@
import { OlympusPlugin } from "interfaces";
import { OlympusApp } from "olympusapp";
export class BoilerplatePlugin implements OlympusPlugin {
#app!: OlympusApp;
constructor() {
}
/**
* @param app <OlympusApp>
*
* @returns boolean on success/fail
*/
initialize(app: OlympusApp) : boolean {
this.#app = app;
return true; // Return true on success
}
getName() {
return "Boilerplate";
}
}

View File

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

View File

@ -1,104 +0,0 @@
{
"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",
"../DCSOlympus/client/@types/olympus/index.d.ts"
]
}

View File

@ -1,7 +0,0 @@
set plugin-name=controltipsplugin
mkdir .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%
copy .\\index.js .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\index.js
copy .\\plugin.json .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\plugin.json
copy .\\style.css .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\style.css

View File

@ -1,29 +0,0 @@
{
"name": "ControlTipsPlugin",
"version": "v0.0.1",
"private": true,
"scripts": {
"build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat",
"build-release": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat"
},
"dependencies": {},
"devDependencies": {
"@babel/preset-env": "^7.21.4",
"@types/node": "^18.16.1",
"@types/sortablejs": "^1.15.0",
"babelify": "^10.0.0",
"browserify": "^17.0.0",
"concurrently": "^7.6.0",
"cp": "^0.2.0",
"esmify": "^2.1.1",
"nodemon": "^2.0.20",
"requirejs": "^2.3.6",
"sortablejs": "^1.15.0",
"tinyify": "^4.0.0",
"tsify": "^5.0.4",
"tslib": "latest",
"typescript": "^4.9.4",
"usng.js": "^0.4.5",
"watchify": "^4.0.0"
}
}

View File

@ -1,6 +0,0 @@
{
"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,396 +0,0 @@
import { OlympusPlugin } from "interfaces";
import { OlympusApp } from "olympusapp";
import { Unit } from "unit/unit";
const SHOW_CONTROL_TIPS = "Show control tips"
export class ControlTipsPlugin implements OlympusPlugin {
#element: HTMLElement;
#app: any;
#shortcutManager: any;
#cursorIsHoveringOverUnit: boolean = false;
#cursorIsHoveringOverAirbase: boolean = false;
#mouseoverElement!: HTMLElement;
constructor() {
this.#element = document.createElement("div");
this.#element.id = "control-tips-panel";
document.body.appendChild(this.#element);
}
getName() {
return "Control Tips Plugin"
}
initialize(app: any) {
this.#app = app;
this.#shortcutManager = this.#app.getShortcutManager();
this.#shortcutManager.onKeyDown(() => {
this.#updateTips()
});
this.#shortcutManager.onKeyUp(() => {
this.#updateTips()
});
document.addEventListener("airbaseMouseover", (ev: CustomEventInit) => {
this.#cursorIsHoveringOverAirbase = true;
this.#updateTips();
});
document.addEventListener("airbaseMouseout", (ev: CustomEventInit) => {
this.#cursorIsHoveringOverAirbase = false;
this.#updateTips();
});
document.addEventListener("unitDeselection", (ev: CustomEventInit ) => {
this.#updateTips();
});
document.addEventListener("unitMouseover", (ev: CustomEventInit) => {
this.#cursorIsHoveringOverUnit = true;
this.#updateTips();
});
document.addEventListener("unitMouseout", (ev: CustomEventInit) => {
this.#cursorIsHoveringOverUnit = false;
this.#updateTips();
});
document.addEventListener("unitsSelection", (ev: CustomEventInit ) => {
this.#updateTips();
});
document.addEventListener("mapOptionsChanged", () => {
this.toggle( !this.#app.getMap().getVisibilityOptions()[SHOW_CONTROL_TIPS] );
});
document.addEventListener( "mouseover", ( ev: MouseEvent ) => {
if ( ev.target instanceof HTMLElement ) {
this.#mouseoverElement = <HTMLElement>ev.target;
}
this.#updateTips();
});
document.addEventListener( "mouseup", ( ev: MouseEvent ) => {
this.#updateTips();
});
this.#updateTips();
this.#app.getMap().addVisibilityOption(SHOW_CONTROL_TIPS, true);
return true;
}
getElement() {
return this.#element;
}
toggle(bool?: boolean) {
this.getElement().classList.toggle("hide", bool);
}
#updateTips() {
const combos: Array<object> = [
{
"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": `Airbase menu`,
"showIfUnitSelected": false,
"showIfHoveringOverAirbase": true,
"showIfHoveringOverUnit": false
},
{
"key": `Mouse2`,
"action": `Set first waypoint`,
"showIfHoveringOverAirbase": false,
"showIfUnitSelected": true,
"unitsMustBeControlled": true
},
{
"key": `Mouse2 (hold)`,
"action": `Interact (ground)`,
"showIfUnitSelected": true,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": false,
"unitsMustBeControlled": true
},
{
"key": `Shift`,
"action": "<em> in formation...</em>",
"showIfUnitSelected": true,
"minSelectedUnits": 2
},
{
"key": "CTRL",
"action": "<em> ... more</em>",
"showIfUnitSelected": true,
"showIfHoveringOverAirbase": 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": `Mouse1`,
"action": "Toggle Blue/Red",
"mouseoverSelector": "#coalition-switch .ol-switch-fill"
},
{
"key": `Mouse2`,
"action": "Set Neutral",
"mouseoverSelector": "#coalition-switch .ol-switch-fill"
},
{
"key": `Mouse1`,
"action": "Toggle time display",
"mouseoverSelector": "#connection-status-panel[data-is-connected] #connection-status-message abbr"
},
{
"key": `Mouse1 or Z`,
"action": "Change location system",
"mouseoverSelector": "#coordinates-tool, #coordinates-tool *"
},
{
"key": `Comma`,
"action": "Decrease precision",
"mouseoverSelector": `#coordinates-tool[data-location-system="MGRS"], #coordinates-tool[data-location-system="MGRS"] *`
},
{
"key": `Period`,
"action": "Increase precision",
"mouseoverSelector": `#coordinates-tool[data-location-system="MGRS"], #coordinates-tool[data-location-system="MGRS"] *`
}
]
},
{
"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,
"showIfHoveringOverUnit": false,
"showIfUnitSelected": true,
"unitsMustBeControlled": true
},
{
"key": `Mouse2`,
"action": `Interact (airbase)`,
"showIfHoveringOverAirbase": true,
"showIfUnitSelected": true,
"unitsMustBeControlled": true
},
{
"key": `Mouse2`,
"action": `Interact (unit)`,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": true,
"showIfUnitSelected": true,
"unitsMustBeControlled": true
},
{
"key": `Shift`,
"action": "<em> in formation...</em>",
"showIfUnitSelected": true,
"minSelectedUnits": 2
},
{
"key": `[Num 1-9]`,
"action": "Set hotgroup",
"showIfUnitSelected": true
}
]
},
{
"keys": ["ShiftLeft"],
"tips": [
{
"key": `Mouse1+drag`,
"action": "Box select",
"showIfUnitSelected": false
},
{
"key": `Mouse2`,
"action": "Set first formation waypoint",
"showIfUnitSelected": true,
"minSelectedUnits": 2
},
{
"key": `[Num 1-9]`,
"action": "Add to hotgroup",
"showIfUnitSelected": true
},
{
"key": "CTRL",
"action": "<em> ... more</em>",
"minSelectedUnits": 2,
"showIfUnitSelected": true,
"showIfHoveringOverAirbase": false,
"unitsMustBeControlled": true
}
]
},
{
"keys": ["ControlLeft", "ShiftLeft"],
"tips": [
{
"key": `Mouse2`,
"action": "Add formation waypoint",
"showIfUnitSelected": true,
"minSelectedUnits": 2,
"unitsMustBeControlled": true
}, {
"key": `[Num 1-9]`,
"action": "Add hotgroup to selection",
"callback": ( tip:object ) => {
return (Object.values<Unit>( this.#app.getUnitsManager().getUnits() ).some( ( unit:Unit ) => {
return unit.getAlive() && unit.getControlled() && unit.getHotgroup();
}));
},
"showIfUnitSelected": true,
"minSelectedUnits": 1
}
]
}
];
const currentCombo: any = combos.find((combo: any) => this.#shortcutManager.keyComboMatches(combo.keys)) || combos[0];
const element = this.getElement();
element.innerHTML = "";
let numSelectedUnits = 0;
let numSelectedControlledUnits = 0;
let unitSelectionContainsControlled = false;
if (this.#app.getUnitsManager()) {
let selectedUnits = Object.values(this.#app.getUnitsManager().getSelectedUnits());
numSelectedUnits = selectedUnits.length;
numSelectedControlledUnits = selectedUnits.filter((unit: any) => unit.getControlled()).length;
unitSelectionContainsControlled = numSelectedControlledUnits > 0;
}
const tipsIncludesActiveMouseover = ( currentCombo.tips.some( ( tip:any ) => {
if ( !tip.mouseoverSelector ) {
return false;
}
if ( this.#mouseoverElement instanceof HTMLElement === false ) {
return false;
}
if ( !this.#mouseoverElement.matches( tip.mouseoverSelector ) ) {
return false;
}
return true;
}));
currentCombo.tips.filter((tip: any) => {
if (numSelectedUnits > 0) {
if (tip.showIfUnitSelected === false) {
return false;
}
if (tip.unitsMustBeControlled === true && unitSelectionContainsControlled === false) {
return false;
}
if ( typeof tip.minSelectedUnits === "number" && numSelectedControlledUnits < tip.minSelectedUnits ) {
return false;
}
}
if (numSelectedUnits === 0 && tip.showIfUnitSelected === true) {
return false;
}
if (typeof tip.showIfHoveringOverAirbase === "boolean") {
if (tip.showIfHoveringOverAirbase !== this.#cursorIsHoveringOverAirbase) {
return false;
}
}
if (typeof tip.showIfHoveringOverUnit === "boolean") {
if (tip.showIfHoveringOverUnit !== this.#cursorIsHoveringOverUnit) {
return false;
}
}
if ( tipsIncludesActiveMouseover && ( typeof tip.mouseoverSelector !== "string" || !this.#mouseoverElement.matches( tip.mouseoverSelector ) ) ) {
return false;
}
if ( !tipsIncludesActiveMouseover && typeof tip.mouseoverSelector === "string" ) {
return false;
}
if ( typeof tip.callback === "function" && !tip.callback( tip ) ) {
return false;
}
element.innerHTML += `<div><span class="key">${tip.key}</span><span class="action">${tip.action}</span></div>`;
});
}
}

View File

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

View File

@ -1,33 +0,0 @@
#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

@ -1,104 +0,0 @@
{
"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/olympus/*.d.ts"
]
}

View File

@ -1,20 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Chrome",
"port": 9222,
"urlFilter": "http://localhost:3000/*",
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}../../public/",
"sourceMapPathOverrides": {
"src/*": "src/*"
},
"preLaunchTask": "start"
}
]
}

View File

@ -1,13 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "start",
"type": "shell",
"command": "npm run start",
"isBackground": true
}
]
}

View File

@ -1,7 +0,0 @@
set plugin-name=databasemanager
mkdir .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%
copy .\\index.js .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\index.js
copy .\\plugin.json .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\plugin.json
copy .\\style.css .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\style.css

File diff suppressed because one or more lines are too long

View File

@ -1,32 +0,0 @@
{
"name": "DatabaseManagerPlugin",
"version": "v0.0.1",
"private": true,
"scripts": {
"build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat",
"build-release": "browserify ./src/index.ts -p [ tsify --noImplicitAny] -p [ tinyify ] > index.js && copy.bat",
"start": "npm run copy & concurrently --kill-others \"npm run watch\"",
"copy": "copy.bat",
"watch": "watchify ./src/index.ts --debug -o ../../public/plugins/databasemanager/index.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js']"
},
"dependencies": {},
"devDependencies": {
"@babel/preset-env": "^7.21.4",
"@types/node": "^18.16.1",
"@types/sortablejs": "^1.15.0",
"babelify": "^10.0.0",
"browserify": "^17.0.0",
"concurrently": "^7.6.0",
"cp": "^0.2.0",
"esmify": "^2.1.1",
"nodemon": "^2.0.20",
"requirejs": "^2.3.6",
"sortablejs": "^1.15.0",
"tinyify": "^4.0.0",
"tsify": "^5.0.4",
"tslib": "latest",
"typescript": "^4.9.4",
"usng.js": "^0.4.5",
"watchify": "^4.0.0"
}
}

View File

@ -1,6 +0,0 @@
{
"name": "Database Manager",
"version": "0.0.1",
"description": "This plugin allows to edit the unit databases",
"author": "DCSOlympus team"
}

View File

@ -1,110 +0,0 @@
import { LoadoutBlueprint, UnitBlueprint } from "interfaces";
import { UnitEditor } from "./uniteditor";
import { LoadoutEditor } from "./loadouteditor";
import { addCheckboxInput, addDropdownInput, addLoadoutsScroll, addNewElementInput, addStringInput } from "./utils";
/** Database editor for Air Units, both Aircraft and Helicopter since they are identical in terms of datbase entries.
*
*/
export class AirUnitEditor extends UnitEditor {
#loadoutEditor: LoadoutEditor | null = null;
constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) {
super(contentDiv1, contentDiv2, contentDiv3);
/* The loadout editor allows to edit the loadout (who could have thought eh?) */
this.#loadoutEditor = new LoadoutEditor(this.contentDiv3);
/* Refresh the loadout editor if needed */
this.contentDiv3.addEventListener("refresh", () => {
if (this.visible)
this.#loadoutEditor?.show();
});
}
/** Sets a unit blueprint as the currently active one
*
* @param blueprint The blueprint to edit
*/
setBlueprint(blueprint: UnitBlueprint) {
this.blueprint = blueprint;
if (this.blueprint !== null) {
this.contentDiv2.replaceChildren();
var title = document.createElement("label");
title.innerText = "Unit properties";
this.contentDiv2.appendChild(title);
addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => { blueprint.name = value; }, true);
addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => { blueprint.label = value; });
addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => { blueprint.shortLabel = value; });
addDropdownInput(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value: string) => {blueprint.coalition = value; });
addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value: string) => {blueprint.era = value; });
addStringInput(this.contentDiv2, "Filename", blueprint.filename ?? "", "text", (value: string) => { blueprint.filename = value; });
addStringInput(this.contentDiv2, "Cost", String(blueprint.cost) ?? "", "number", (value: string) => { blueprint.cost = parseFloat(value); });
addCheckboxInput(this.contentDiv2, "Can target point", blueprint.canTargetPoint ?? false, (value: boolean) => {blueprint.canTargetPoint = value;})
addStringInput(this.contentDiv2, "Description", blueprint.description ?? "", "text", (value: string) => {blueprint.description = value; });
addStringInput(this.contentDiv2, "Tags", blueprint.tags ?? "", "text", (value: string) => {blueprint.tags = value; });
/* Add a scrollable list of loadouts that the user can edit */
var title = document.createElement("label");
title.innerText = "Loadouts";
this.contentDiv2.appendChild(title);
addLoadoutsScroll(this.contentDiv2, blueprint.loadouts ?? [], (loadout: LoadoutBlueprint) => {
this.#loadoutEditor?.setLoadout(loadout);
this.#loadoutEditor?.show();
});
addNewElementInput(this.contentDiv2, (ev: MouseEvent, input: HTMLInputElement) => { this.addLoadout(input.value); });
this.#loadoutEditor?.hide();
}
}
/** Add a new empty blueprint
*
* @param key Blueprint key
*/
addBlueprint(key: string) {
if (this.database != null) {
this.database.blueprints[key] = {
name: key,
coalition: "",
label: "",
shortLabel: "",
era: "",
loadouts: [],
enabled: true
}
this.show();
this.setBlueprint(this.database.blueprints[key]);
}
}
/** Add a new empty loadout to the currently active blueprint
*
* @param loadoutName The name of the new loadout
*/
addLoadout(loadoutName: string) {
if (loadoutName && this.blueprint !== null) {
this.blueprint.loadouts?.push({
name: loadoutName,
code: "",
fuel: 1,
items: [],
roles: [],
enabled: true
})
this.setBlueprint(this.blueprint);
}
}
/** Hide the editor
*
*/
hide() {
super.hide();
this.#loadoutEditor?.hide();
}
}

View File

@ -1,424 +0,0 @@
import { OlympusPlugin, UnitBlueprint } from "interfaces";
import { AirUnitEditor } from "./airuniteditor";
import { OlympusApp } from "olympusapp";
import { GroundUnitEditor } from "./grounduniteditor";
import { PrimaryToolbar } from "toolbars/primarytoolbar";
import { NavyUnitEditor } from "./navyuniteditor";
/** Database Manager
*
* This database provides a user interface to allow easier and convenient unit databases manipulation. It allows to edit all the fields of the units databases, save them
* on the server, and restore the defaults.
*
* TODO:
* Add ability to manage liveries
*
*/
export class DatabaseManagerPlugin implements OlympusPlugin {
#app!: OlympusApp;
#element: HTMLElement;
#mainContentContainer: HTMLElement;
#contentDiv1: HTMLElement;
#contentDiv2: HTMLElement;
#contentDiv3: HTMLElement;
/* Upper tab buttons */
#button1: HTMLButtonElement;
#button2: HTMLButtonElement;
#button3: HTMLButtonElement;
#button4: HTMLButtonElement;
/* Lower operation buttons */
#button5: HTMLButtonElement;
#button6: HTMLButtonElement;
#button7: HTMLButtonElement;
#button8: HTMLButtonElement;
#button9: HTMLButtonElement;
/* Database editors */
#aircraftEditor: AirUnitEditor;
#helicopterEditor: AirUnitEditor;
#groundUnitEditor: GroundUnitEditor;
#navyUnitEditor: NavyUnitEditor;
constructor() {
/* Create main HTML element */
this.#element = document.createElement("div");
this.#element.id = "database-manager-panel";
this.#element.oncontextmenu = () => { return false; }
this.#element.classList.add("ol-dialog");
document.body.appendChild(this.#element);
/* Start hidden */
this.toggle(false);
/* Create the top tab buttons container and buttons */
let topButtonContainer = document.createElement("div");
this.#button1 = document.createElement("button");
this.#button1.classList.add("tab-button");
this.#button1.textContent = "Aircraft database";
this.#button1.onclick = () => { this.#hideAll(); this.#aircraftEditor.show(); this.#button1.classList.add("selected"); };
topButtonContainer.appendChild(this.#button1);
this.#button2 = document.createElement("button");
this.#button2.classList.add("tab-button");
this.#button2.textContent = "Helicopter database";
this.#button2.onclick = () => { this.#hideAll(); this.#helicopterEditor.show(); this.#button2.classList.add("selected"); };
topButtonContainer.appendChild(this.#button2);
this.#button3 = document.createElement("button");
this.#button3.classList.add("tab-button");
this.#button3.textContent = "Ground Unit database";
this.#button3.onclick = () => { this.#hideAll(); this.#groundUnitEditor.show(); this.#button3.classList.add("selected"); };
topButtonContainer.appendChild(this.#button3);
this.#button4 = document.createElement("button");
this.#button4.classList.add("tab-button");
this.#button4.textContent = "Navy Unit database";
this.#button4.onclick = () => { this.#hideAll(); this.#navyUnitEditor.show(); this.#button4.classList.add("selected"); };
topButtonContainer.appendChild(this.#button4);
this.#element.appendChild(topButtonContainer);
/* Create the container for the database editor elements and the elements themselves */
this.#mainContentContainer = document.createElement("div");
this.#mainContentContainer.classList.add("dm-container");
this.#element.appendChild(this.#mainContentContainer);
this.#contentDiv1 = document.createElement("div");
this.#contentDiv1.classList.add("dm-content-container", "ol-scrollable");
this.#mainContentContainer.appendChild(this.#contentDiv1);
this.#contentDiv2 = document.createElement("div");
this.#contentDiv2.classList.add("dm-content-container", "ol-scrollable");
this.#mainContentContainer.appendChild(this.#contentDiv2);
this.#contentDiv3 = document.createElement("div");
this.#contentDiv3.classList.add("dm-content-container", "ol-scrollable");
this.#mainContentContainer.appendChild(this.#contentDiv3);
/* Create the database editors, which use the three divs created before */
this.#aircraftEditor = new AirUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3);
this.#helicopterEditor = new AirUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3);
this.#groundUnitEditor = new GroundUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3);
this.#navyUnitEditor = new NavyUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3);
/* Create the bottom buttons container. These buttons allow to save, restore, reset, and discard the changes */
let bottomButtonContainer = document.createElement("div");
this.#button5 = document.createElement("button");
this.#button5.textContent = "Save";
this.#button5.title = "Save the changes on the server"
this.#button5.onclick = () => { this.#saveDatabases();};
bottomButtonContainer.appendChild(this.#button5);
this.#button6 = document.createElement("button");
this.#button6.textContent = "Discard";
this.#button6.title = "Discard all changes and reload the database from the server";
this.#button6.onclick = () => { this.#loadDatabases(); };
bottomButtonContainer.appendChild(this.#button6);
this.#button7 = document.createElement("button");
this.#button7.textContent = "Reset defaults";
this.#button7.onclick = () => { this.#resetToDefaultDatabases(); };
this.#button7.title = "Reset the databases to the default values";
bottomButtonContainer.appendChild(this.#button7);
this.#button8 = document.createElement("button");
this.#button8.textContent = "Restore previous";
this.#button8.onclick = () => { this.#restoreToPreviousDatabases(); };
this.#button8.title = "Restore the previously saved databases. Use this if you saved a database by mistake.";
bottomButtonContainer.appendChild(this.#button8);
this.#button9 = document.createElement("button");
this.#button9.textContent = "Close";
this.#button9.title = "Close the Database Manager"
this.#button9.onclick = () => { this.toggle(false); };
bottomButtonContainer.appendChild(this.#button9);
this.#element.appendChild(bottomButtonContainer);
}
/**
*
* @returns The name of the plugin
*/
getName() {
return "Database Control Plugin"
}
/** Initialize the plugin
*
* @param app The OlympusApp singleton
* @returns True if successfull
*/
initialize(app: any) {
this.#app = app;
const contextManager = this.#app.getContextManager();
contextManager.add( "databaseManager", {
"allowUnitCopying": false,
"allowUnitPasting": false,
"useSpawnMenu": false,
"useUnitControlPanel": false,
"useUnitInfoPanel": false
});
/* Load the databases and initialize the editors */
this.#loadDatabases();
/* Add a button to the main Olympus App to allow the users to open the dialog */
var mainButtonDiv = document.createElement("div");
var mainButton = document.createElement("button");
mainButton.textContent = "Database manager";
mainButtonDiv.appendChild(mainButton);
var toolbar: PrimaryToolbar = this.#app?.getToolbarsManager().get("primaryToolbar") as PrimaryToolbar;
var elements = toolbar.getMainDropdown().getOptionElements();
var arr = Array.prototype.slice.call(elements);
arr.splice(arr.length - 3, 0, mainButtonDiv);
toolbar.getMainDropdown().setOptionsElements(arr);
mainButton.onclick = () => {
toolbar.getMainDropdown().close();
if (this.#app?.getMissionManager().getCommandModeOptions().commandMode === "Game master")
this.toggle();
}
return true;
}
/**
*
* @returns The main container element
*/
getElement() {
return this.#element;
}
/** Toggles the visibility of the dialog
*
* @param bool Force a specific visibility state
*/
toggle(bool?: boolean) {
if (bool)
this.getElement().classList.toggle("hide", !bool);
else
this.getElement().classList.toggle("hide");
if ( this.#app )
this.#app.getContextManager().setContext( this.getElement().classList.contains("hide") ? "olympus" : "databaseManager" );
}
/** Hide all the editors
*
*/
#hideAll() {
this.#aircraftEditor.hide();
this.#helicopterEditor.hide();
this.#groundUnitEditor.hide();
this.#navyUnitEditor.hide();
this.#button1.classList.remove("selected");
this.#button2.classList.remove("selected");
this.#button3.classList.remove("selected");
this.#button4.classList.remove("selected");
}
/** Load the databases from the app to the editor. Note, this does not reload the databases from the server to the app
*
*/
#loadDatabases() {
var aircraftDatabase = this.#app?.getAircraftDatabase();
if (aircraftDatabase != null)
this.#aircraftEditor.setDatabase(aircraftDatabase);
var helicopterDatabase = this.#app?.getHelicopterDatabase();
if (helicopterDatabase != null)
this.#helicopterEditor.setDatabase(helicopterDatabase);
var groundUnitDatabase = this.#app?.getGroundUnitDatabase();
if (groundUnitDatabase != null)
this.#groundUnitEditor.setDatabase(groundUnitDatabase);
var navyUnitDatabase = this.#app?.getNavyUnitDatabase();
if (navyUnitDatabase != null)
this.#navyUnitEditor.setDatabase(navyUnitDatabase);
this.#hideAll();
this.#aircraftEditor.show();
this.#button1.classList.add("selected");
}
/** Save the databases on the server and reloads it to apply the changes
*
*/
#saveDatabases() {
var aircraftDatabase = this.#aircraftEditor.getDatabase();
if (aircraftDatabase){
this.#uploadDatabase(aircraftDatabase, "aircraftdatabase", "Aircraft database", () => {
var helicopterDatabase = this.#helicopterEditor.getDatabase();
if (helicopterDatabase) {
this.#uploadDatabase(helicopterDatabase, "helicopterDatabase", "Helicopter database", () => {
var groundUnitDatabase = this.#groundUnitEditor.getDatabase();
if (groundUnitDatabase) {
this.#uploadDatabase(groundUnitDatabase, "groundUnitDatabase", "Ground Unit database", () => {
var navyUnitDatabase = this.#navyUnitEditor.getDatabase();
if (navyUnitDatabase) {
this.#uploadDatabase(navyUnitDatabase, "navyUnitDatabase", "Navy Unit database", () => {
this.#app?.getAircraftDatabase().load(() => {});
this.#app?.getHelicopterDatabase().load(() => {});
this.#app?.getGroundUnitDatabase().load(() => {});
this.#app?.getNavyUnitDatabase().load(() => {});
this.#app?.getServerManager().reloadDatabases(() => {
this.#app?.getPopupsManager().get("infoPopup")?.setText("Olympus core databases reloaded");
})
});
}
});
}
});
}
});
}
}
/** Resets the databases to the default values
*
*/
#resetToDefaultDatabases() {
this.#resetToDefaultDatabase("aircraftdatabase", "Aircraft database", () => {
this.#app?.getAircraftDatabase().load(() => {
this.#resetToDefaultDatabase("helicopterdatabase", "Helicopter database", () => {
this.#app?.getHelicopterDatabase().load(() => {
this.#resetToDefaultDatabase("groundunitdatabase", "Ground Unit database", () => {
this.#app?.getGroundUnitDatabase().load(() => {
this.#resetToDefaultDatabase("navyunitdatabase", "Navy Unit database", () => {
this.#app?.getNavyUnitDatabase().load(() => {
this.#loadDatabases();
this.#app?.getServerManager().reloadDatabases(() => {
this.#app?.getPopupsManager().get("infoPopup")?.setText("Olympus core databases reloaded");
})
this.#hideAll();
this.#aircraftEditor.show();
this.#button1.classList.add("selected");
});
});
});
});
});
});
});
});
}
/** Restores the databases to the previous saved values. This is useful if you saved the databases by mistake and want to undo the error.
*
*/
#restoreToPreviousDatabases() {
this.#restoreToPreviousDatabase("aircraftdatabase", "Aircraft database", () => {
this.#app?.getAircraftDatabase().load(() => {
this.#restoreToPreviousDatabase("helicopterdatabase", "Helicopter database", () => {
this.#app?.getHelicopterDatabase().load(() => {
this.#restoreToPreviousDatabase("groundunitdatabase", "Ground Unit database", () => {
this.#app?.getGroundUnitDatabase().load(() => {
this.#restoreToPreviousDatabase("navyunitdatabase", "Navy Unit database", () => {
this.#app?.getNavyUnitDatabase().load(() => {
this.#loadDatabases();
this.#app?.getServerManager().reloadDatabases(() => {
this.#app?.getPopupsManager().get("infoPopup")?.setText("Olympus core databases reloaded");
})
this.#hideAll();
this.#aircraftEditor.show();
this.#button1.classList.add("selected");
});
});
});
});
});
});
});
});
}
/** Upload a single database to the server
*
* @param database The database
* @param name The name of the database as it will be saved on the server
* @param label A label used in the info popup
*/
#uploadDatabase(database: { blueprints: { [key: string]: UnitBlueprint } }, name: string, label: string, callback: CallableFunction) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("PUT", "/api/databases/save/units/" + name);
xmlHttp.setRequestHeader("Content-Type", "application/json");
xmlHttp.onload = (res: any) => {
if (xmlHttp.status == 200) {
this.#app?.getPopupsManager().get("infoPopup")?.setText(label + " saved successfully");
callback();
}
else {
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while saving the " + label);
}
};
xmlHttp.onerror = (res: any) => {
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while saving the " + label);
}
xmlHttp.send(JSON.stringify(database));
}
/** Resets a database to its default values on the server. NOTE: this only resets the database on the server, it will not reload it in the app.
*
* @param name The name of the database as it is saved on the server
* @param label A label used in the info popup
* @param callback Called when the operation is completed
*/
#resetToDefaultDatabase(name: string, label: string, callback: CallableFunction) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("PUT", "/api/databases/reset/units/" + name);
xmlHttp.setRequestHeader("Content-Type", "application/json");
xmlHttp.onload = (res: any) => {
if (xmlHttp.status == 200) {
this.#app?.getPopupsManager().get("infoPopup")?.setText(label + " reset successfully");
callback();
}
else {
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while resetting the " + label);
}
};
xmlHttp.onerror = (res: any) => {
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while resetting the " + label)
}
xmlHttp.send("");
}
/** Restores a database to its previously saved values on the server. NOTE: this only restores the database on the server, it will not reload it in the app.
*
* @param name The name of the database as it is saved on the server
* @param label A label used in the info popup
* @param callback Called when the operation is completed
*/
#restoreToPreviousDatabase(name: string, label: string, callback: CallableFunction) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("PUT", "/api/databases/restore/units/" + name);
xmlHttp.setRequestHeader("Content-Type", "application/json");
xmlHttp.onload = (res: any) => {
if (xmlHttp.status == 200) {
this.#app?.getPopupsManager().get("infoPopup")?.setText(label + " restored successfully");
callback();
}
else {
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while restoring the " + label);
}
};
xmlHttp.onerror = (res: any) => {
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while restoring the " + label)
}
xmlHttp.send("");
}
}

View File

@ -1,77 +0,0 @@
import { UnitBlueprint } from "interfaces";
import { UnitEditor } from "./uniteditor";
import { addCheckboxInput, addDropdownInput, addStringInput } from "./utils";
/** Database editor for ground units
*
*/
export class GroundUnitEditor extends UnitEditor {
#blueprint: UnitBlueprint | null = null;
constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) {
super(contentDiv1, contentDiv2, contentDiv3);
}
/** Sets a unit blueprint as the currently active one
*
* @param blueprint The blueprint to edit
*/
setBlueprint(blueprint: UnitBlueprint) {
this.#blueprint = blueprint;
if (this.#blueprint !== null) {
this.contentDiv2.replaceChildren();
var title = document.createElement("label");
title.innerText = "Unit properties";
this.contentDiv2.appendChild(title);
addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => {blueprint.name = value; }, true);
addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => {blueprint.label = value; });
addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => {blueprint.shortLabel = value; });
addStringInput(this.contentDiv2, "Type", blueprint.type?? "", "text", (value: string) => {blueprint.type = value; });
addStringInput(this.contentDiv2, "Unit when grouped", blueprint.unitWhenGrouped?? "", "text", (value: string) => {blueprint.unitWhenGrouped = value; });
addDropdownInput(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value: string) => {blueprint.coalition = value; });
addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value: string) => {blueprint.era = value; });
//addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; });
addStringInput(this.contentDiv2, "Cost", String(blueprint.cost)?? "", "number", (value: string) => {blueprint.cost = parseFloat(value); });
addStringInput(this.contentDiv2, "Acquisition range [m]", String(blueprint.acquisitionRange)?? "", "number", (value: string) => {blueprint.acquisitionRange = parseFloat(value); });
addStringInput(this.contentDiv2, "Engagement range [m]", String(blueprint.engagementRange)?? "", "number", (value: string) => {blueprint.engagementRange = parseFloat(value); });
addStringInput(this.contentDiv2, "Targeting range [m]", String(blueprint.targetingRange)?? "", "number", (value: string) => {blueprint.targetingRange = parseFloat(value); });
addStringInput(this.contentDiv2, "Aim method range [m]", String(blueprint.aimMethodRange)?? "", "number", (value: string) => {blueprint.aimMethodRange = parseFloat(value); });
addStringInput(this.contentDiv2, "Barrel height [m]", String(blueprint.barrelHeight)?? "", "number", (value: string) => {blueprint.barrelHeight = parseFloat(value); });
addStringInput(this.contentDiv2, "Muzzle velocity [m/s]", String(blueprint.muzzleVelocity)?? "", "number", (value: string) => {blueprint.muzzleVelocity = parseFloat(value); });
addStringInput(this.contentDiv2, "Aim time [s]", String(blueprint.aimTime)?? "", "number", (value: string) => {blueprint.aimTime = parseFloat(value); });
addStringInput(this.contentDiv2, "Shots to fire", String(blueprint.shotsToFire)?? "", "number", (value: string) => {blueprint.shotsToFire = Math.round(parseFloat(value)); });
addStringInput(this.contentDiv2, "Shots base interval [s]", String(blueprint.shotsBaseInterval)?? "", "number", (value: string) => {blueprint.shotsBaseInterval = Math.round(parseFloat(value)); });
addStringInput(this.contentDiv2, "Shots base scatter [°]", String(blueprint.shotsBaseScatter)?? "", "number", (value: string) => {blueprint.shotsBaseScatter = Math.round(parseFloat(value)); });
addStringInput(this.contentDiv2, "Alertness time constant [s]", String(blueprint.alertnessTimeConstant)?? "", "number", (value: string) => {blueprint.alertnessTimeConstant = Math.round(parseFloat(value)); });
addCheckboxInput(this.contentDiv2, "Can target point", blueprint.canTargetPoint ?? false, (value: boolean) => {blueprint.canTargetPoint = value;})
addCheckboxInput(this.contentDiv2, "Can rearm", blueprint.canRearm ?? false, (value: boolean) => {blueprint.canRearm = value;})
addCheckboxInput(this.contentDiv2, "Can operate as AAA", blueprint.canAAA ?? false, (value: boolean) => {blueprint.canAAA = value;})
addCheckboxInput(this.contentDiv2, "Indirect fire (e.g. mortar)", blueprint.indirectFire ?? false, (value: boolean) => {blueprint.indirectFire = value;})
addStringInput(this.contentDiv2, "Description", blueprint.description ?? "", "text", (value: string) => {blueprint.description = value; });
addStringInput(this.contentDiv2, "Tags", blueprint.tags ?? "", "text", (value: string) => {blueprint.tags = value; });
addStringInput(this.contentDiv2, "Marker file", blueprint.markerFile ?? "", "text", (value: string) => {blueprint.markerFile = value; });
}
}
/** Add a new empty blueprint
*
* @param key Blueprint key
*/
addBlueprint(key: string) {
if (this.database != null) {
this.database.blueprints[key] = {
name: key,
coalition: "",
label: "",
shortLabel: "",
era: "",
enabled: true
}
this.show();
this.setBlueprint(this.database.blueprints[key]);
}
}
}

View File

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

View File

@ -1,55 +0,0 @@
import { LoadoutBlueprint } from "interfaces";
import { addLoadoutItemsEditor, addStringInput, arrayToString, stringToArray } from "./utils";
/** The LoadoutEditor allows the user to edit a loadout
*
*/
export class LoadoutEditor {
#contentDiv: HTMLElement;
#loadout: LoadoutBlueprint | null = null;
#visible: boolean = false;
constructor(contentDiv: HTMLElement) {
this.#contentDiv = contentDiv;
this.#contentDiv.addEventListener("refresh", () => {
if (this.#visible)
this.show();
})
}
/** Set the loadout to edit
*
* @param loadout The loadout to edit
*/
setLoadout(loadout: LoadoutBlueprint) {
this.#loadout = loadout;
}
/** Show the editor
*
*/
show() {
this.#visible = true;
this.#contentDiv.replaceChildren();
var title = document.createElement("label");
title.innerText = "Loadout properties";
this.#contentDiv.appendChild(title);
if (this.#loadout) {
var loadout = this.#loadout;
addStringInput(this.#contentDiv, "Name", loadout.name, "text", (value: string) => {loadout.name = value; this.#contentDiv.dispatchEvent(new Event("refresh"));});
addStringInput(this.#contentDiv, "Code", loadout.code, "text", (value: string) => {loadout.code = value; });
addStringInput(this.#contentDiv, "Roles", arrayToString(loadout.roles), "text", (value: string) => {loadout.roles = stringToArray(value);});
addLoadoutItemsEditor(this.#contentDiv, this.#loadout);
}
}
/** Hide the editor
*
*/
hide() {
this.#visible = false;
this.#contentDiv.replaceChildren();
}
}

View File

@ -1,60 +0,0 @@
import { UnitBlueprint } from "interfaces";
import { UnitEditor } from "./uniteditor";
import { addDropdownInput, addStringInput } from "./utils";
/** Database editor for navy units
*
*/
export class NavyUnitEditor extends UnitEditor {
#blueprint: UnitBlueprint | null = null;
constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) {
super(contentDiv1, contentDiv2, contentDiv3);
}
/** Sets a unit blueprint as the currently active one
*
* @param blueprint The blueprint to edit
*/
setBlueprint(blueprint: UnitBlueprint) {
this.#blueprint = blueprint;
if (this.#blueprint !== null) {
this.contentDiv2.replaceChildren();
var title = document.createElement("label");
title.innerText = "Unit properties";
this.contentDiv2.appendChild(title);
addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => {blueprint.name = value; }, true);
addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => {blueprint.label = value; });
addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => {blueprint.shortLabel = value; });
addStringInput(this.contentDiv2, "Type", blueprint.type?? "", "text", (value: string) => {blueprint.type = value; });
addDropdownInput(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value: string) => {blueprint.coalition = value; });
addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value: string) => {blueprint.era = value; });
//addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; });
addStringInput(this.contentDiv2, "Cost", String(blueprint.cost)?? "", "number", (value: string) => {blueprint.cost = parseFloat(value); });
addStringInput(this.contentDiv2, "Barrel height [m]", String(blueprint.barrelHeight)?? "", "number", (value: string) => {blueprint.barrelHeight = parseFloat(value); });
addStringInput(this.contentDiv2, "Muzzle velocity [m/s]", String(blueprint.muzzleVelocity)?? "", "number", (value: string) => {blueprint.muzzleVelocity = parseFloat(value); });
}
}
/** Add a new empty blueprint
*
* @param key Blueprint key
*/
addBlueprint(key: string) {
if (this.database != null) {
this.database.blueprints[key] = {
name: key,
coalition: "",
label: "",
shortLabel: "",
era: "",
enabled: true
}
this.show();
this.setBlueprint(this.database.blueprints[key]);
}
}
}

View File

@ -1,117 +0,0 @@
import { UnitBlueprint } from "interfaces";
import { UnitDatabase } from "unit/databases/unitdatabase";
import { addBlueprintsScroll, addNewElementInput } from "./utils";
/** Base abstract class of Unit database editors
*
*/
export abstract class UnitEditor {
blueprint: UnitBlueprint | null = null;
database: {blueprints: {[key: string]: UnitBlueprint}} | null = null;
visible: boolean = false;
contentDiv1: HTMLElement;
contentDiv2: HTMLElement;
contentDiv3: HTMLElement;
constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) {
this.contentDiv1 = contentDiv1;
this.contentDiv2 = contentDiv2;
this.contentDiv3 = contentDiv3;
/* Refresh the list of units if it changes */
this.contentDiv1.addEventListener("refresh", () => {
if (this.visible)
this.show();
})
/* If the unit properties or loadout are edited, reload the editor */
this.contentDiv2.addEventListener("refresh", () => {
if (this.visible) {
if (this.blueprint !== null)
this.setBlueprint(this.blueprint);
}
});
this.contentDiv3.addEventListener("refresh", () => {
if (this.visible) {
if (this.blueprint !== null)
this.setBlueprint(this.blueprint);
}
});
}
/**
*
* @param database The database that the editor will operate on
*/
setDatabase(database: UnitDatabase) {
this.database = JSON.parse(JSON.stringify({blueprints: database.getBlueprints(true)}));
}
/** Show the editor
* @param filter String filter
*/
show(filter: string = "") {
this.visible = true;
this.contentDiv1.replaceChildren();
this.contentDiv2.replaceChildren();
this.contentDiv3.replaceChildren();
/* Create the list of units. Each unit is clickable to activate the editor on it */
if (this.database != null) {
var title = document.createElement("label");
title.innerText = "Units list";
this.contentDiv1.appendChild(title);
var filterInput = document.createElement("input");
filterInput.value = filter;
this.contentDiv1.appendChild(filterInput);
filterInput.onchange = (e: Event) => {
this.show((e.target as HTMLInputElement).value);
}
this.addBlueprints(filter);
}
}
/** Hide the editor
*
*/
hide() {
this.visible = false;
this.contentDiv1.replaceChildren();
this.contentDiv2.replaceChildren();
this.contentDiv3.replaceChildren();
}
/**
*
* @returns The edited database
*/
getDatabase() {
return this.database;
}
/**
*
* @param filter String filter
*/
addBlueprints(filter: string = "") {
if (this.database) {
addBlueprintsScroll(this.contentDiv1, this.database, filter, (key: string) => {
if (this.database != null)
this.setBlueprint(this.database.blueprints[key])
});
addNewElementInput(this.contentDiv1, (ev: MouseEvent, input: HTMLInputElement) => {
if (input.value != "")
this.addBlueprint((input).value);
});
}
}
/* Abstract methods which will depend on the specific type of units */
abstract setBlueprint(blueprint: UnitBlueprint): void;
abstract addBlueprint(key: string): void;
}

View File

@ -1,297 +0,0 @@
import { LoadoutBlueprint, LoadoutItemBlueprint, UnitBlueprint } from "interfaces";
/** This file contains a set of utility functions that are reused in the various editors and allows to declutter the classes
*
*/
/** Add a string input in the form of String: [ value ]
*
* @param div The HTMLElement that will contain the input
* @param key The key of the input, which will be used as label
* @param value The initial value of the input
* @param type The type of the input, e.g. "Text" or "Number" as per html standard
* @param callback Callback called when the user enters a new value
* @param disabled If true, the input will be disabled and read only
*/
export function addStringInput(div: HTMLElement, key: string, value: string, type: string, callback: CallableFunction, disabled?: boolean) {
var row = document.createElement("div");
var dt = document.createElement("dt");
var dd = document.createElement("dd");
dt.innerText = key;
var input = document.createElement("input");
input.value = value;
input.textContent = value;
input.type = type?? "text";
input.disabled = disabled?? false;
input.onchange = () => callback(input.value);
dd.appendChild(input);
row.appendChild(dt);
row.appendChild(dd);
row.classList.add("input-row");
div.appendChild(row);
}
/** Add a dropdown (select) input
*
* @param div The HTMLElement that will contain the input
* @param key The key of the input, which will be used as label
* @param value The initial value of the input
* @param options The dropdown options
*/
export function addDropdownInput(div: HTMLElement, key: string, value: string, options: string[], callback: CallableFunction, disabled?: boolean) {
var row = document.createElement("div");
var dt = document.createElement("dt");
var dd = document.createElement("dd");
dt.innerText = key;
var select = document.createElement("select");
options.forEach((option: string) => {
var el = document.createElement("option");
el.value = option;
el.innerText = option;
select.appendChild(el);
});
select.value = value;
select.disabled = disabled?? false;
select.onchange = () => callback(select.value);
dd.appendChild(select);
row.appendChild(dt);
row.appendChild(dd);
row.classList.add("input-row");
div.appendChild(row);
}
/** Add a checkbox input in the form of String: [ value ]
*
* @param div The HTMLElement that will contain the input
* @param key The key of the input, which will be used as label
* @param value The initial value of the input
* @param callback Callback called when the user enters a new value
* @param disabled If true, the input will be disabled and read only
*/
export function addCheckboxInput(div: HTMLElement, key: string, value: boolean, callback: CallableFunction, disabled?: boolean) {
var row = document.createElement("div");
var dt = document.createElement("dt");
var dd = document.createElement("dd");
dt.innerText = key;
var input = document.createElement("input");
input.checked = value;
input.type = "checkbox";
input.disabled = disabled?? false;
input.onchange = () => callback(input.checked);
dd.appendChild(input);
row.appendChild(dt);
row.appendChild(dd);
row.classList.add("input-row");
div.appendChild(row);
}
/** Create a loadout items editor. This editor allows to add or remove loadout items, as well as changing their name and quantity
*
* @param div The HTMLElement that will contain the editor
* @param loadout The loadout to edit
*/
export function addLoadoutItemsEditor(div: HTMLElement, loadout: LoadoutBlueprint) {
var itemsEl = document.createElement("div");
itemsEl.classList.add("dm-scroll-container", "dm-items-container");
/* Create a row for each loadout item to allow and change the name and quantity of the item itself */
loadout.items.sort((a: LoadoutItemBlueprint, b: LoadoutItemBlueprint) => a.name.localeCompare(b.name, undefined, {sensitivity: 'base'}));
loadout.items.forEach((item: LoadoutItemBlueprint, index: number) => {
var rowDiv = document.createElement("div");
var nameLabel = document.createElement("label");
nameLabel.innerText = "Name"
rowDiv.appendChild(nameLabel);
var nameInput = document.createElement("input");
rowDiv.appendChild(nameInput);
nameInput.textContent = item.name;
nameInput.value = item.name
nameInput.onchange = () => { loadout.items[index].name = nameInput.value; }
var quantityLabel = document.createElement("label");
quantityLabel.innerText = "Quantity"
rowDiv.appendChild(quantityLabel);
var quantityInput = document.createElement("input");
rowDiv.appendChild(quantityInput);
quantityInput.textContent = String(item.quantity);
quantityInput.value = String(item.quantity);
quantityInput.type = "number";
quantityInput.step = "1";
quantityInput.onchange = () => { loadout.items[index].quantity = parseInt(quantityInput.value); }
/* This button allows to remove the item */
var button = document.createElement("button");
button.innerText = "X";
button.onclick = () => {
loadout.items.splice(index, 1);
div.dispatchEvent(new Event("refresh"));
}
rowDiv.appendChild(button);
itemsEl.appendChild(rowDiv);
})
div.appendChild(itemsEl);
/* Button to add a new item to the loadout */
var inputDiv = document.createElement("div");
inputDiv.classList.add("dm-new-item-input");
var button = document.createElement("button");
button.innerText = "Add";
inputDiv.appendChild(button);
div.appendChild(inputDiv);
button.addEventListener("click", (ev: MouseEvent) => {
loadout?.items.push({
name: "",
quantity: 1
})
div.dispatchEvent(new Event("refresh"));
});
}
/** Add a input and button to create a new element in a list. It uses a generic callback to actually add the element.
*
* @param div The HTMLElement that will contain the input and button
* @param callback Callback called when the user clicks on "Add"
*/
export function addNewElementInput(div: HTMLElement, callback: CallableFunction) {
var inputDiv = document.createElement("div");
inputDiv.classList.add("dm-new-element-input");
var input = document.createElement("input");
inputDiv.appendChild(input);
var button = document.createElement("button");
button.innerText = "Add";
button.addEventListener("click", (ev: MouseEvent) => callback(ev, input));
inputDiv.appendChild(button);
div.appendChild(inputDiv);
}
/** Add a scrollable list of blueprints
*
* @param div The HTMLElement that will contain the list
* @param database The database that will be used to fill the list of blueprints
* @param filter A string filter that will be executed to filter the blueprints to add
* @param callback Callback called when the user clicks on one of the elements
*/
export function addBlueprintsScroll(div: HTMLElement, database: {blueprints: {[key: string]: UnitBlueprint}}, filter: string, callback: CallableFunction) {
var scrollDiv = document.createElement("div");
scrollDiv.classList.add("dm-scroll-container");
if (database !== null) {
var blueprints: {[key: string]: UnitBlueprint} = database.blueprints;
for (let key of Object.keys(blueprints).sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'}))) {
var addKey = true;
if (filter !== "") {
try {
var blueprint = blueprints[key];
addKey = eval(filter);
} catch {
console.error("An error has occurred evaluating the blueprint filter")
}
}
if (addKey) {
var rowDiv = document.createElement("div");
scrollDiv.appendChild(rowDiv);
let text = document.createElement("div");
text.innerHTML = `<div>${key}</div> <div>${blueprints[key].label}</div>`;
text.onclick = () => {
callback(key);
const collection = document.getElementsByClassName("blueprint-selected");
for (let i = 0; i < collection.length; i++) {
collection[i].classList.remove("blueprint-selected");
}
text.classList.add("blueprint-selected");
}
rowDiv.appendChild(text);
let checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = blueprints[key].enabled;
checkbox.onclick = () => {
console.log(checkbox.checked);
blueprints[key].enabled = checkbox.checked;
}
rowDiv.appendChild(checkbox);
/* This button allows to remove an element from the list. It requires a refresh. */
var button = document.createElement("button");
button.innerText = "X";
button.onclick = () => {
delete blueprints[key];
div.dispatchEvent(new Event("refresh"));
}
rowDiv.appendChild(button);
}
}
}
div.appendChild(scrollDiv);
}
/** Add a scrollable list of loadouts
*
* @param div The HTMLElement that will contain the list
* @param loadouts The loadouts that will be used to fill the list
* @param callback Callback called when the user clicks on one of the elements
*/
export function addLoadoutsScroll(div: HTMLElement, loadouts: LoadoutBlueprint[], callback: CallableFunction) {
var loadoutsEl = document.createElement("div");
loadoutsEl.classList.add("dm-scroll-container", "dm-loadout-container")
loadouts.sort((a: LoadoutBlueprint, b: LoadoutBlueprint) => a.name.localeCompare(b.name, undefined, {sensitivity: 'base'}));
loadouts.forEach((loadout: LoadoutBlueprint, index: number) => {
var rowDiv = document.createElement("div");
loadoutsEl.appendChild(rowDiv);
var text = document.createElement("label");
text.textContent = loadout.name;
text.onclick = () => { callback(loadout) };
rowDiv.appendChild(text);
/* The "Empty loadout" can not be removed */
if (loadout.name !== "Empty loadout") {
let checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = loadout.enabled;
checkbox.onclick = () => {
console.log(checkbox.checked);
loadout.enabled = checkbox.checked;
}
rowDiv.appendChild(checkbox);
/* This button allows to remove an element from the list. It requires a refresh. */
var button = document.createElement("button");
button.innerText = "X";
button.onclick = () => {
loadouts.splice(index, 1);
div.dispatchEvent(new Event("refresh"));
}
rowDiv.appendChild(button);
}
});
div.appendChild(loadoutsEl);
}
/** Converts an array of string into a single string like [val1, val2, val3]
*
* @param array The input array of strings
* @returns The string
*/
export function arrayToString(array: string[]) {
return "[" + array.join( ", " ) + "]";
}
/** Converts an a single string like [val1, val2, val3] into an array
*
* @param input The input string
* @returns The array
*/
export function stringToArray(input: string) {
return input.match( /(\w)+/g ) ?? [];
}

View File

@ -1,295 +0,0 @@
#database-manager-panel {
flex-direction: column;
display: flex;
width: 80%;
height: 80%;
padding: 10px;
border-radius: 5px;
background-color: var(--background-steel) !important;
z-index: 9999999;
}
@media (min-width: 1200px) {
.dm-container {
flex-direction: row;
}
}
@media (max-width: 1200px) {
.dm-container {
flex-direction: column;
overflow-y: auto;
}
}
#database-manager-panel * {
font-size: 13;
font-family: 'Open Sans', sans-serif !important;
user-select: none;
}
#database-manager-panel>div:first-child {
display: flex;
align-items: center;
}
#database-manager-panel>div:last-child {
display: flex;
column-gap: 5px;
align-items: center;
justify-content: end;
justify-items: end;
margin-top: 5px;
}
#database-manager-panel>div:last-child>button {
border: 1px solid white;
}
.dm-container {
background-color: var(--background-grey);
border: 2px solid #777777;
position: relative;
display: flex;
width: 100%;
padding: 5px;
height: calc(100% - 64px - 5px);
border-radius: 0px 5px 5px 5px;
}
.dm-content-container {
position: relative;
margin: 10px;
display: flex;
flex-direction: column;
row-gap: 5px;
max-height: 100%;
padding: 10px;
}
@media (min-width: 1200px) {
.dm-content-container {
height: calc(100% - 20px);
}
.dm-content-container:nth-of-type(1) {
width: 400px;
}
.dm-content-container:nth-of-type(2) {
width: 500px;
}
.dm-content-container:nth-of-type(3) {
flex: 1;
}
}
@media (max-width: 1200px) {
.dm-content-container {
width: calc(100% - 20px);
}
.dm-content-container:nth-of-type(1) {
height: 30%;
}
.dm-content-container:nth-of-type(2) {
height: 50%;
}
.dm-content-container:nth-of-type(3) {
flex: 1;
}
}
.dm-content-container>label {
font-size: 18px !important;
font-weight: bold;
}
.dm-scroll-container {
display: flex;
flex-direction: column;
overflow-y: scroll;
max-height: 100%;
color: black;
font-weight: bold;
}
#database-manager-panel input {
font-weight: bold;
}
.dm-scroll-container>div:nth-child(even) {
background-color: gainsboro;
}
.dm-scroll-container>div:nth-child(odd) {
background-color: white;
}
.dm-scroll-container>div *:nth-child(1) {
height: 100%;
width: calc(100% - 25px);
padding: 2px;
word-wrap: break-word;
}
.dm-scroll-container>div *:nth-child(1):hover {
background-color: var(--accent-dark-blue);
color: white;
cursor: pointer;
}
.blueprint-selected {
background-color: var(--accent-light-blue) !important;
color: white;
}
.dm-scroll-container>div {
display: flex;
align-items: center;
justify-content: space-between;
}
.dm-scroll-container>div>div {
display: flex;
align-items: center;
justify-content: space-between;
}
.dm-scroll-container>div>button {
height: 20px;
width: 20px;
padding: 0px;
}
.dm-scroll-container>div>div>div:nth-child(1) {
width: fit-content;
}
.dm-scroll-container>div>div>div:nth-child(2) {
overflow: hidden;
text-wrap: nowrap;
text-overflow: ellipsis;
font-weight: normal;
}
.input-row {
width: 100%;
display: flex;
flex-direction: row;
}
@media (max-width: 1200px) {
.dm-content-container label {
width: 100%;
}
.input-row {
width: 50%;
}
}
.input-row>dt {
width: 250px;
}
.input-row>dd {
width: 100%;
text-align: right;
}
.input-row>dd>* {
width: 100%;
font-weight: bold;
}
.input-row>dd>*[type="checkbox"] {
width: 20px;
font-weight: bold;
}
.dm-loadout-container {
max-height: 100%;
max-width: 500px;
width: 100%;
}
.dm-items-container {
max-height: 100%;
height: fit-content;
}
.dm-items-container>div {
display: flex;
align-items: center;
column-gap: 2px;
}
.dm-items-container>div>label {
width: 80px !important;
}
.dm-items-container div>input:nth-of-type(1) {
flex: 1;
font-weight: bold;
}
.dm-items-container div>input:nth-of-type(2) {
width: 40px;
font-weight: bold;
}
.dm-new-element-input {
display: flex;
flex-direction: row;
column-gap: 2px;
width: 100%;
align-items: center;
}
.dm-new-element-input>input {
width: 100%;
}
.dm-new-element-input>button {
width: 60px;
}
.dm-new-item-input {
display: flex;
justify-content: end;
}
.dm-new-item-input>button {
width: 60px;
}
.tab-button {
transform: translateY(+3px);
background-color: var(--background-steel);
border-radius: 0;
border-bottom: 2px solid transparent !important;
border-top: 2px solid #777777 !important;
border-left: 2px solid #777777 !important;
border-right: 0px solid #777777 !important;
}
.tab-button.selected {
background-color: var(--background-grey);
z-index: 10;
}
.tab-button:first-of-type {
border-top-left-radius: 5px;
}
.tab-button:last-of-type {
border-top-right-radius: 5px;
border-right: 2px solid #777777 !important;
}
#database-manager-panel button :not(.dm-scroll-container>div) {
border: 1px solid white;
}

View File

@ -1,103 +0,0 @@
{
"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"], /* 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"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 MiB

View File

@ -1 +0,0 @@
call browserify --debug .\src\index.ts -o ..\server\public\javascripts\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]

View File

@ -1 +0,0 @@
call browserify .\src\index.ts -o ..\..\build\frontend\public\javascripts\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]

View File

@ -1 +0,0 @@
tsc --project tsconfig.json --declaration --emitDeclarationOnly --outfile .\@types\olympus\index.d.ts

View File

@ -1 +0,0 @@
watchify .\src\index.ts --debug -o ..\server\public\javascripts\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] -v

View File

@ -1,318 +0,0 @@
import { LatLng, LatLngBounds } from "leaflet";
import { MapMarkerVisibilityControl } from "../map/map";
export const UNITS_URI = "units";
export const WEAPONS_URI = "weapons";
export const LOGS_URI = "logs";
export const AIRBASES_URI = "airbases";
export const BULLSEYE_URI = "bullseyes";
export const MISSION_URI = "mission";
export const COMMANDS_URI = "commands";
export const NONE = "None";
export const GAME_MASTER = "Game master";
export const BLUE_COMMANDER = "Blue commander";
export const RED_COMMANDER = "Red commander";
export const VISUAL = 1;
export const OPTIC = 2;
export const RADAR = 4;
export const IRST = 8;
export const RWR = 16;
export const DLINK = 32;
export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area", "simulate-fire-fight", "scenic-aaa", "miss-on-purpose", "land-at-point"];
export const ROEs: string[] = ["free", "designated", "", "return", "hold"];
export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"];
export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"];
export const ERAS = [{
"name": "Early Cold War",
"chronologicalOrder": 2
}, {
"name": "Late Cold War",
"chronologicalOrder": 4
}, {
"name": "Mid Cold War",
"chronologicalOrder": 3
}, {
"name": "Modern",
"chronologicalOrder": 5
}, {
"name": "WW2",
"chronologicalOrder": 1
}];
export const ROEDescriptions: string[] = [
"Free (Attack anyone)",
"Designated (Attack the designated target only) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.",
"",
"Return (Only fire if fired upon) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.",
"Hold (Never fire)"
];
export const reactionsToThreatDescriptions: string[] = [
"None (No reaction)",
"Manoeuvre (no countermeasures)",
"Passive (Countermeasures only, no manoeuvre)",
"Evade (Countermeasures and manoeuvers)"
];
export const emissionsCountermeasuresDescriptions: string[] = [
"Silent (Radar OFF, no ECM)",
"Attack (Radar only for targeting, ECM only if locked)",
"Defend (Radar for searching, ECM if locked)",
"Always on (Radar and ECM always on)"
];
export const shotsScatterDescriptions: string[] = [
"When performing scenic shooting tasks like simulated firefights, will shoot with a large scatter",
"When performing scenic shooting tasks like simulated firefights, will shoot with a medium scatter",
"When performing scenic shooting tasks like simulated firefights, will shoot with a small scatter (Radar guided units will track shots when the enemy unit is close)"
];
export const shotsIntensityDescriptions: string[] = [
"When performing scenic shooting tasks like simulated firefights, will shoot with a low rate of fire",
"When performing scenic shooting tasks like simulated firefights, will shoot with a medium rate of fire",
"When performing scenic shooting tasks like simulated firefights, will shoot with a high rate of fire"
];
export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
export const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 };
export const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helicopter: 0 };
export const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 };
export const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 };
export const minimapBoundaries = {
"Nevada": [ // NTTR
new LatLng(39.7982463, -119.985425),
new LatLng(34.4037128, -119.7806729),
new LatLng(34.3483316, -112.4529351),
new LatLng(39.7372411, -112.1130805),
new LatLng(39.7982463, -119.985425)
],
"Syria": [ // Syria
new LatLng(37.3630556, 29.2686111),
new LatLng(31.8472222, 29.8975),
new LatLng(32.1358333, 42.1502778),
new LatLng(37.7177778, 42.3716667),
new LatLng(37.3630556, 29.2686111)
],
"Caucasus": [ // Caucasus
new LatLng(39.6170191, 27.634935),
new LatLng(38.8735863, 47.1423108),
new LatLng(47.3907982, 49.3101946),
new LatLng(48.3955879, 26.7753625),
new LatLng(39.6170191, 27.634935)
],
"PersianGulf": [ // Persian Gulf
new LatLng(32.9355285, 46.5623682),
new LatLng(21.729393, 47.572675),
new LatLng(21.8501348, 63.9734737),
new LatLng(33.131584, 64.7313594),
new LatLng(32.9355285, 46.5623682)
],
"MarianaIslands": [ // Marianas
new LatLng(22.09, 135.0572222),
new LatLng(10.5777778, 135.7477778),
new LatLng(10.7725, 149.3918333),
new LatLng(22.5127778, 149.5427778),
new LatLng(22.09, 135.0572222)
],
"Falklands": [ // South Atlantic
new LatLng(-49.097217, -79.418267),
new LatLng(-56.874517, -79.418267),
new LatLng(-56.874517, -43.316433),
new LatLng(-49.097217, -43.316433),
new LatLng(-49.097217, -79.418267)
],
"Normandy": [ // Normandy
new LatLng(50.44, -3.29),
new LatLng(48.12, -3.29),
new LatLng(48.12, 3.70),
new LatLng(50.44, 3.70),
new LatLng(50.44, -3.29)
],
"SinaiMap": [ // Sinai
new LatLng(34.312222, 28.523333),
new LatLng(25.946944, 28.523333),
new LatLng(25.946944, 36.897778),
new LatLng(34.312222, 36.897778),
new LatLng(34.312222, 28.523333)
],
"Kola": [ // Kola
new LatLng(72.055300, 4.0140000),
new LatLng(64.421145, 10.353076),
new LatLng(63.570300, 39.364000),
new LatLng(71.48000, 48.091100),
new LatLng(72.055300, 4.01400003)
],
"Afghanistan": [ // Afghanistan
new LatLng(39.27472222, 59.81138889),
new LatLng(29.41166667, 60.04305556),
new LatLng(28.97722222, 73.87666667),
new LatLng(38.40055556, 75.07638889),
new LatLng(39.27472222, 59.81138889)
]
};
export const mapBounds = {
"Syria": { bounds: new LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]), zoom: 5 },
"MarianaIslands": { bounds: new LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]), zoom: 5 },
"Nevada": { bounds: new LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805]), zoom: 5 },
"PersianGulf": { bounds: new LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]), zoom: 4 },
"Caucasus": { bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]), zoom: 4 },
"Falklands": { bounds: new LatLngBounds([-49.097217, -79.418267], [-56.874517, -43.316433]), zoom: 3 },
"Normandy": { bounds: new LatLngBounds([50.44, -3.29], [48.12, 3.70]), zoom: 5 },
"SinaiMap": { bounds: new LatLngBounds([34.312222, 28.523333], [25.946944, 36.897778]), zoom: 4 },
"Kola": { bounds: new LatLngBounds([61.59999, 4.29982], [75.05179, 44.29982]), zoom: 3},
"Afghanistan": { bounds: new LatLngBounds([29.41166667, 60.04305556], [38.40055556, 75.07638889]), zoom: 3}
}
export const defaultMapMirrors = {}
export const defaultMapLayers = {}
/* Map constants */
export const IDLE = "Idle";
export const MOVE_UNIT = "Move unit";
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "helicopter", "groundunit-sam", "groundunit", "navyunit", "airbase"];
export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["helicopter"], ["groundunit-sam"], ["groundunit"], ["navyunit"], ["airbase"]];
export const visibilityControlsTooltips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle helicopter visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{
"name": "Human",
"image": "visibility/human.svg",
"toggles": ["human"],
"tooltip": "Toggle human players' visibility"
}, {
"image": "visibility/olympus.svg",
"isProtected": false,
"name": "Olympus",
"protectable": false,
"toggles": ["olympus"],
"tooltip": "Toggle Olympus-controlled units' visibility"
}, {
"image": "visibility/dcs.svg",
"isProtected": true,
"name": "DCS",
"protectable": true,
"toggles": ["dcs"],
"tooltip": "Toggle DCS-controlled units' visibility"
}, {
"category": "Aircraft",
"image": "visibility/aircraft.svg",
"name": "Aircraft",
"toggles": ["aircraft"],
"tooltip": "Toggle aircraft's visibility"
}, {
"category": "Helicopter",
"image": "visibility/helicopter.svg",
"name": "Helicopter",
"toggles": ["helicopter"],
"tooltip": "Toggle helicopters' visibility"
}, {
"category": "AirDefence",
"image": "visibility/groundunit-sam.svg",
"name": "Air defence",
"toggles": ["groundunit-sam"],
"tooltip": "Toggle air defence units' visibility"
}, {
"image": "visibility/groundunit.svg",
"name": "Ground units",
"toggles": ["groundunit"],
"tooltip": "Toggle ground units' visibility"
}, {
"category": "GroundUnit",
"image": "visibility/navyunit.svg",
"name": "Naval",
"toggles": ["navyunit"],
"tooltip": "Toggle naval units' visibility"
}, {
"image": "visibility/airbase.svg",
"name": "Airbase",
"toggles": ["airbase"],
"tooltip": "Toggle airbase' visibility"
}];
export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"];
export const IADSDensities: { [key: string]: number } = { "AAA": 0.8, "SAM Site": 0.1, "Radar (EWR)": 0.05 };
export const GROUND_UNIT_AIR_DEFENCE_REGEX: RegExp = /(\b(AAA|SAM|MANPADS?|[mM]anpads?)|[sS]tinger\b)/;
export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out";
export const SHOW_UNIT_LABELS = "Show unit labels (L)";
export const SHOW_UNITS_ENGAGEMENT_RINGS = "Show units threat range rings (Q)";
export const HIDE_UNITS_SHORT_RANGE_RINGS = "Hide short range units threat range rings (R)";
export const SHOW_UNITS_ACQUISITION_RINGS = "Show units detection range rings (E)";
export const FILL_SELECTED_RING = "Fill the threat range rings of selected units (F)";
export const SHOW_UNIT_CONTACTS = "Show selected units contact lines";
export const SHOW_UNIT_PATHS = "Show selected unit paths";
export const SHOW_UNIT_TARGETS = "Show selected unit targets";
export const DCS_LINK_PORT = "DCS Camera link port";
export const DCS_LINK_RATIO = "DCS Camera zoom";
export enum DataIndexes {
startOfData = 0,
category,
alive,
human,
controlled,
coalition,
country,
name,
unitName,
groupName,
state,
task,
hasTask,
position,
speed,
horizontalVelocity,
verticalVelocity,
heading,
track,
isActiveTanker,
isActiveAWACS,
onOff,
followRoads,
fuel,
desiredSpeed,
desiredSpeedType,
desiredAltitude,
desiredAltitudeType,
leaderID,
formationOffset,
targetID,
targetPosition,
ROE,
reactionToThreat,
emissionsCountermeasures,
TACAN,
radio,
generalSettings,
ammo,
contacts,
activePath,
isLeader,
operateAs,
shotsScatter,
shotsIntensity,
health,
endOfData = 255
};
export const MGRS_PRECISION_10KM = 2;
export const MGRS_PRECISION_1KM = 3;
export const MGRS_PRECISION_100M = 4;
export const MGRS_PRECISION_10M = 5;
export const MGRS_PRECISION_1M = 6;
export const DELETE_CYCLE_TIME = 0.05;
export const DELETE_SLOW_THRESHOLD = 50;
export const GROUPING_ZOOM_TRANSITION = 13;
export const MAX_SHOTS_SCATTER = 3;
export const MAX_SHOTS_INTENSITY = 3;
export const SHOTS_SCATTER_DEGREES = 10;

View File

@ -1,310 +0,0 @@
import { LatLng, LatLngBounds } from "leaflet";
import { MapMarkerVisibilityControl } from "../map/map";
export const UNITS_URI = "units";
export const WEAPONS_URI = "weapons";
export const LOGS_URI = "logs";
export const AIRBASES_URI = "airbases";
export const BULLSEYE_URI = "bullseyes";
export const MISSION_URI = "mission";
export const COMMANDS_URI = "commands";
export const NONE = "None";
export const GAME_MASTER = "Game master";
export const BLUE_COMMANDER = "Blue commander";
export const RED_COMMANDER = "Red commander";
export const VISUAL = 1;
export const OPTIC = 2;
export const RADAR = 4;
export const IRST = 8;
export const RWR = 16;
export const DLINK = 32;
export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area", "simulate-fire-fight", "scenic-aaa", "miss-on-purpose", "land-at-point"];
export const ROEs: string[] = ["free", "designated", "", "return", "hold"];
export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"];
export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"];
export const ERAS = [{
"name": "Early Cold War",
"chronologicalOrder": 2
}, {
"name": "Late Cold War",
"chronologicalOrder": 4
}, {
"name": "Mid Cold War",
"chronologicalOrder": 3
}, {
"name": "Modern",
"chronologicalOrder": 5
}, {
"name": "WW2",
"chronologicalOrder": 1
}];
export const ROEDescriptions: string[] = [
"Free (Attack anyone)",
"Designated (Attack the designated target only) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.",
"",
"Return (Only fire if fired upon) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.",
"Hold (Never fire)"
];
export const reactionsToThreatDescriptions: string[] = [
"None (No reaction)",
"Manoeuvre (no countermeasures)",
"Passive (Countermeasures only, no manoeuvre)",
"Evade (Countermeasures and manoeuvers)"
];
export const emissionsCountermeasuresDescriptions: string[] = [
"Silent (Radar OFF, no ECM)",
"Attack (Radar only for targeting, ECM only if locked)",
"Defend (Radar for searching, ECM if locked)",
"Always on (Radar and ECM always on)"
];
export const shotsScatterDescriptions: string[] = [
"When performing scenic shooting tasks like simulated firefights, will shoot with a large scatter",
"When performing scenic shooting tasks like simulated firefights, will shoot with a medium scatter",
"When performing scenic shooting tasks like simulated firefights, will shoot with a small scatter (Radar guided units will track shots when the enemy unit is close)"
];
export const shotsIntensityDescriptions: string[] = [
"When performing scenic shooting tasks like simulated firefights, will shoot with a low rate of fire",
"When performing scenic shooting tasks like simulated firefights, will shoot with a medium rate of fire",
"When performing scenic shooting tasks like simulated firefights, will shoot with a high rate of fire"
];
export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
export const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 };
export const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helicopter: 0 };
export const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 };
export const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 };
export const minimapBoundaries = {
"Nevada": [ // NTTR
new LatLng(39.7982463, -119.985425),
new LatLng(34.4037128, -119.7806729),
new LatLng(34.3483316, -112.4529351),
new LatLng(39.7372411, -112.1130805),
new LatLng(39.7982463, -119.985425)
],
"Syria": [ // Syria
new LatLng(37.3630556, 29.2686111),
new LatLng(31.8472222, 29.8975),
new LatLng(32.1358333, 42.1502778),
new LatLng(37.7177778, 42.3716667),
new LatLng(37.3630556, 29.2686111)
],
"Caucasus": [ // Caucasus
new LatLng(39.6170191, 27.634935),
new LatLng(38.8735863, 47.1423108),
new LatLng(47.3907982, 49.3101946),
new LatLng(48.3955879, 26.7753625),
new LatLng(39.6170191, 27.634935)
],
"PersianGulf": [ // Persian Gulf
new LatLng(32.9355285, 46.5623682),
new LatLng(21.729393, 47.572675),
new LatLng(21.8501348, 63.9734737),
new LatLng(33.131584, 64.7313594),
new LatLng(32.9355285, 46.5623682)
],
"MarianaIslands": [ // Marianas
new LatLng(22.09, 135.0572222),
new LatLng(10.5777778, 135.7477778),
new LatLng(10.7725, 149.3918333),
new LatLng(22.5127778, 149.5427778),
new LatLng(22.09, 135.0572222)
],
"Falklands": [ // South Atlantic
new LatLng(-49.097217, -79.418267),
new LatLng(-56.874517, -79.418267),
new LatLng(-56.874517, -43.316433),
new LatLng(-49.097217, -43.316433),
new LatLng(-49.097217, -79.418267)
],
"Normandy": [ // Normandy
new LatLng(50.44, -3.29),
new LatLng(48.12, -3.29),
new LatLng(48.12, 3.70),
new LatLng(50.44, 3.70),
new LatLng(50.44, -3.29)
],
"SinaiMap": [ // Sinai
new LatLng(34.312222, 28.523333),
new LatLng(25.946944, 28.523333),
new LatLng(25.946944, 36.897778),
new LatLng(34.312222, 36.897778),
new LatLng(34.312222, 28.523333)
],
"Kola": [ // Kola
new LatLng(72.055300, 4.0140000),
new LatLng(64.421145, 10.353076),
new LatLng(63.570300, 39.364000),
new LatLng(71.48000, 48.091100),
new LatLng(72.055300, 4.01400003)
]
};
export const mapBounds = {
"Syria": { bounds: new LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]), zoom: 5 },
"MarianaIslands": { bounds: new LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]), zoom: 5 },
"Nevada": { bounds: new LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805]), zoom: 5 },
"PersianGulf": { bounds: new LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]), zoom: 4 },
"Caucasus": { bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]), zoom: 4 },
"Falklands": { bounds: new LatLngBounds([-49.097217, -79.418267], [-56.874517, -43.316433]), zoom: 3 },
"Normandy": { bounds: new LatLngBounds([50.44, -3.29], [48.12, 3.70]), zoom: 5 },
"SinaiMap": { bounds: new LatLngBounds([34.312222, 28.523333], [25.946944, 36.897778]), zoom: 4 },
"Kola": { bounds: new LatLngBounds([61.59999, 4.29982], [75.05179, 44.29982]), zoom: 3}
}
export const defaultMapMirrors = {}
export const defaultMapLayers = {}
/* Map constants */
export const IDLE = "Idle";
export const MOVE_UNIT = "Move unit";
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "helicopter", "groundunit-sam", "groundunit", "navyunit", "airbase"];
export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["helicopter"], ["groundunit-sam"], ["groundunit"], ["navyunit"], ["airbase"]];
export const visibilityControlsTooltips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle helicopter visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{
"name": "Human",
"image": "visibility/human.svg",
"toggles": ["human"],
"tooltip": "Toggle human players' visibility"
}, {
"image": "visibility/olympus.svg",
"isProtected": false,
"name": "Olympus",
"protectable": false,
"toggles": ["olympus"],
"tooltip": "Toggle Olympus-controlled units' visibility"
}, {
"image": "visibility/dcs.svg",
"isProtected": true,
"name": "DCS",
"protectable": true,
"toggles": ["dcs"],
"tooltip": "Toggle DCS-controlled units' visibility"
}, {
"category": "Aircraft",
"image": "visibility/aircraft.svg",
"name": "Aircraft",
"toggles": ["aircraft"],
"tooltip": "Toggle aircraft's visibility"
}, {
"category": "Helicopter",
"image": "visibility/helicopter.svg",
"name": "Helicopter",
"toggles": ["helicopter"],
"tooltip": "Toggle helicopters' visibility"
}, {
"category": "AirDefence",
"image": "visibility/groundunit-sam.svg",
"name": "Air defence",
"toggles": ["groundunit-sam"],
"tooltip": "Toggle air defence units' visibility"
}, {
"image": "visibility/groundunit.svg",
"name": "Ground units",
"toggles": ["groundunit"],
"tooltip": "Toggle ground units' visibility"
}, {
"category": "GroundUnit",
"image": "visibility/navyunit.svg",
"name": "Naval",
"toggles": ["navyunit"],
"tooltip": "Toggle naval units' visibility"
}, {
"image": "visibility/airbase.svg",
"name": "Airbase",
"toggles": ["airbase"],
"tooltip": "Toggle airbase' visibility"
}];
export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"];
export const IADSDensities: { [key: string]: number } = { "AAA": 0.8, "SAM Site": 0.1, "Radar (EWR)": 0.05 };
export const GROUND_UNIT_AIR_DEFENCE_REGEX: RegExp = /(\b(AAA|SAM|MANPADS?|[mM]anpads?)|[sS]tinger\b)/;
export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out";
export const SHOW_UNIT_LABELS = "Show unit labels (L)";
export const SHOW_UNITS_ENGAGEMENT_RINGS = "Show units threat range rings (Q)";
export const HIDE_UNITS_SHORT_RANGE_RINGS = "Hide short range units threat range rings (R)";
export const SHOW_UNITS_ACQUISITION_RINGS = "Show units detection range rings (E)";
export const FILL_SELECTED_RING = "Fill the threat range rings of selected units (F)";
export const SHOW_UNIT_CONTACTS = "Show selected units contact lines";
export const SHOW_UNIT_PATHS = "Show selected unit paths";
export const SHOW_UNIT_TARGETS = "Show selected unit targets";
export const DCS_LINK_PORT = "DCS Camera link port";
export const DCS_LINK_RATIO = "DCS Camera zoom";
export enum DataIndexes {
startOfData = 0,
category,
alive,
human,
controlled,
coalition,
country,
name,
unitName,
groupName,
state,
task,
hasTask,
position,
speed,
horizontalVelocity,
verticalVelocity,
heading,
track,
isActiveTanker,
isActiveAWACS,
onOff,
followRoads,
fuel,
desiredSpeed,
desiredSpeedType,
desiredAltitude,
desiredAltitudeType,
leaderID,
formationOffset,
targetID,
targetPosition,
ROE,
reactionToThreat,
emissionsCountermeasures,
TACAN,
radio,
generalSettings,
ammo,
contacts,
activePath,
isLeader,
operateAs,
shotsScatter,
shotsIntensity,
health,
endOfData = 255
};
export const MGRS_PRECISION_10KM = 2;
export const MGRS_PRECISION_1KM = 3;
export const MGRS_PRECISION_100M = 4;
export const MGRS_PRECISION_10M = 5;
export const MGRS_PRECISION_1M = 6;
export const DELETE_CYCLE_TIME = 0.05;
export const DELETE_SLOW_THRESHOLD = 50;
export const GROUPING_ZOOM_TRANSITION = 13;
export const MAX_SHOTS_SCATTER = 3;
export const MAX_SHOTS_INTENSITY = 3;
export const SHOTS_SCATTER_DEGREES = 10;

View File

@ -1,44 +0,0 @@
export interface ContextInterface {
allowUnitCopying?: boolean;
allowUnitPasting?: boolean;
useSpawnMenu?: boolean;
useUnitControlPanel?: boolean;
useUnitInfoPanel?: boolean;
}
export class Context {
#allowUnitCopying: boolean;
#allowUnitPasting: boolean;
#useSpawnMenu: boolean;
#useUnitControlPanel: boolean;
#useUnitInfoPanel: boolean;
constructor(config: ContextInterface) {
this.#allowUnitCopying = (config.allowUnitCopying !== false);
this.#allowUnitPasting = (config.allowUnitPasting !== false);
this.#useSpawnMenu = (config.useSpawnMenu !== false);
this.#useUnitControlPanel = (config.useUnitControlPanel !== false);
this.#useUnitInfoPanel = (config.useUnitInfoPanel !== false);
}
getAllowUnitCopying() {
return this.#allowUnitCopying;
}
getAllowUnitPasting() {
return this.#allowUnitPasting;
}
getUseSpawnMenu() {
return this.#useSpawnMenu;
}
getUseUnitControlPanel() {
return this.#useUnitControlPanel;
}
getUseUnitInfoPanel() {
return this.#useUnitInfoPanel;
}
}

View File

@ -1,40 +0,0 @@
import { Manager } from "../other/manager";
import { Context, ContextInterface } from "./context";
export class ContextManager extends Manager {
#currentContext!: string;
constructor() {
super();
}
add(name: string, contextConfig: ContextInterface) {
super.add(name, new Context(contextConfig));
if (Object.values(this.getAll()).length === 1) {
this.#currentContext = name;
}
return this;
}
currentContextIs(contextName: string) {
return contextName === this.#currentContext;
}
getCurrentContext() {
const contexts = this.getAll();
return (contexts.hasOwnProperty(this.#currentContext)) ? contexts[this.#currentContext] : false;
}
setContext(contextName: string) {
if (!this.get(contextName)) {
console.error(`setContext(): context name "${contextName}" does not exist.`);
return false;
}
this.#currentContext = contextName;
console.log(`Setting context to "${this.#currentContext}".`);
}
}

View File

@ -1,15 +0,0 @@
import { ContextMenu } from "../contextmenus/contextmenu";
import { Manager } from "../other/manager";
export class ContextMenuManager extends Manager {
constructor() {
super();
}
add( name:string, contextMenu:ContextMenu ) {
super.add( name, contextMenu );
return this;
}
}

View File

@ -1,176 +0,0 @@
import { getApp } from "..";
import { GAME_MASTER } from "../constants/constants";
import { AirbaseChartRunwayData, AirbaseChartRunwayHeadingData } from "../interfaces";
import { Airbase } from "../mission/airbase";
import { dataPointMap } from "../other/utils";
import { Unit } from "../unit/unit";
import { ContextMenu } from "./contextmenu";
/** This context menu is shown to the user when the airbase marker is right clicked on the map.
* It allows the user to inspect information about the airbase, as well as allowing to spawn units from the airbase itself and land units on it. */
export class AirbaseContextMenu extends ContextMenu {
#airbase: Airbase | null = null;
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
document.addEventListener("contextMenuSpawnAirbase", (e: any) => {
this.#showSpawnMenu();
})
document.addEventListener("contextMenuLandAirbase", (e: any) => {
if (this.#airbase)
getApp().getUnitsManager().landAt(this.#airbase.getLatLng());
this.hide();
})
}
/** Sets the airbase for which data will be shown in the context menu
*
* @param airbase The airbase for which data will be shown in the context menu. Note: the airbase must be present in the public/databases/airbases/<theatre>.json database.
*/
setAirbase(airbase: Airbase) {
this.#airbase = airbase;
this.#setName(this.#airbase.getName());
this.#setProperties(this.#airbase.getProperties());
this.#setParkings(this.#airbase.getParkings());
this.#setCoalition(this.#airbase.getCoalition());
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();
}
/**
*
* @param airbaseName The name of the airbase
*/
#setName(airbaseName: string) {
var nameDiv = <HTMLElement>this.getContainer()?.querySelector("#airbase-name");
if (nameDiv != null)
nameDiv.innerText = airbaseName;
}
/**
*
* @param airbaseProperties The properties of the airbase
*/
#setProperties(airbaseProperties: string[]) {
this.getContainer()?.querySelector("#airbase-properties")?.replaceChildren(...airbaseProperties.map((property: string) => {
var div = document.createElement("div");
div.innerText = property;
return div;
}),);
}
/**
*
* @param airbaseParkings List of available parkings at the airbase
*/
#setParkings(airbaseParkings: string[]) {
this.getContainer()?.querySelector("#airbase-parking")?.replaceChildren(...airbaseParkings.map((parking: string) => {
var div = document.createElement("div");
div.innerText = parking;
return div;
}));
}
/**
*
* @param coalition Coalition to which the airbase belongs
*/
#setCoalition(coalition: string) {
(this.getContainer()?.querySelector("#spawn-airbase-aircraft-button") as HTMLElement).dataset.coalition = coalition;
}
/**
*
* @param showSpawnButton If true, the spawn button will be visibile
*/
#showSpawnButton(showSpawnButton: boolean) {
this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")?.classList.toggle("hide", !showSpawnButton);
}
/**
*
* @param showLandButton If true, the land button will be visible
*/
#showLandButton(showLandButton: boolean) {
this.getContainer()?.querySelector("#land-here-button")?.classList.toggle("hide", !showLandButton);
}
/** Shows the spawn context menu which allows the user to select a unit to ground spawn at the airbase
*
*/
#showSpawnMenu() {
if (this.#airbase != null) {
getApp().setActiveCoalition(this.#airbase.getCoalition());
getApp().getMap().showAirbaseSpawnMenu(this.#airbase, this.getX(), this.getY(), this.getLatLng());
}
}
/** @todo needs commenting
*
*/
#setAirbaseData() {
if (this.#airbase && this.getContainer()) {
dataPointMap(this.getContainer() as HTMLElement, {
"coalition": this.#airbase.getCoalition(),
"airbaseName": this.#airbase.getName()
});
dataPointMap( this.getContainer() as HTMLElement, this.#airbase.getChartData() );
const runwaysContainer = this.getContainer()?.querySelector( "#airbase-runways" ) as HTMLElement;
runwaysContainer.innerHTML = "";
if ( runwaysContainer instanceof HTMLElement ) {
const runways = this.#airbase.getChartData().runways;
if ( runways.length === 0 ) {
runwaysContainer.innerText = "No data";
} else {
runways.forEach( (runway: AirbaseChartRunwayData) => {
let runwayDiv = document.createElement( "div" );
runwayDiv.classList.add( "runway" );
runway.headings.forEach( (headings: AirbaseChartRunwayHeadingData) => {
for ( const [ heading, data ] of Object.entries( headings ) ) {
let headingDiv = document.createElement( "div" );
headingDiv.classList.add( "heading" );
let abbr = document.createElement( "abbr" );
abbr.title = `Mag heading: ${data.magHeading}`;
abbr.innerText = heading;
headingDiv.appendChild( abbr );
runwayDiv.appendChild( headingDiv );
if ( data.ILS ) {
let ilsDiv = document.createElement( "div" );
ilsDiv.classList.add( "ils" );
abbr = document.createElement( "abbr" );
abbr.title = data.ILS;
abbr.innerText = "ILS";
ilsDiv.appendChild( abbr );
headingDiv.appendChild( ilsDiv );
}
}
});
runwaysContainer.appendChild( runwayDiv );
});
}
}
}
}
}

View File

@ -1,105 +0,0 @@
import { LatLng } from "leaflet";
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. */
export class AirbaseSpawnContextMenu extends ContextMenu {
#aircraftSpawnMenu: AircraftSpawnMenu;
#helicopterSpawnMenu: HelicopterSpawnMenu;
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
/* Create the spawn menus for the different unit types */
this.#aircraftSpawnMenu = new AircraftSpawnMenu("airbase-aircraft-spawn-menu");
this.#helicopterSpawnMenu = new HelicopterSpawnMenu("airbase-helicopter-spawn-menu");
this.#aircraftSpawnMenu.getAltitudeSlider().hide();
this.#helicopterSpawnMenu.getAltitudeSlider().hide();
/* Event listeners */
document.addEventListener("mapContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.#showSubMenu(e.detail.type);
else
this.#hideSubMenus();
});
this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.hide();
}
/** Show the context menu
*
* @param x X screen coordinate of the top left corner of the context menu
* @param y Y screen coordinate of the top left corner of the context menu
*/
show(x: number | undefined, y: number | undefined) {
super.show(x, y, new LatLng(0, 0));
this.#aircraftSpawnMenu.setAirbase(undefined);
this.#helicopterSpawnMenu.setAirbase(undefined);
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.setCountries();
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
*
* @param airbase The airbase at which the new unit will be spawned. Note: if the airbase has no suitable parking spots, the airplane may be spawned on the runway, or spawning may fail.
*/
setAirbase(airbase: Airbase) {
this.#aircraftSpawnMenu.setAirbase(airbase);
this.#helicopterSpawnMenu.setAirbase(airbase);
}
/** Shows the submenu depending on unit selection
*
* @param type Submenu type, either "aircraft" or "helicopter"
*/
#showSubMenu(type: string) {
this.getContainer()?.querySelector("#airbase-aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
this.getContainer()?.querySelector("#airbase-aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
this.getContainer()?.querySelector("#airbase-helicopter-spawn-menu")?.classList.toggle("hide", type !== "helicopter");
this.getContainer()?.querySelector("#airbase-helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter");
(this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => { (element as HTMLButtonElement).disabled = true; })
this.#aircraftSpawnMenu.reset();
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.reset();
this.#helicopterSpawnMenu.setCountries();
this.setVisibleSubMenu(type);
this.clip();
}
/** Hide all the open submenus
*
*/
#hideSubMenus() {
this.getContainer()?.querySelector("#airbase-aircraft-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#airbase-aircraft-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#airbase-helicopter-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#airbase-helicopter-spawn-button")?.classList.toggle("is-open", false);
this.#aircraftSpawnMenu.reset();
this.#helicopterSpawnMenu.reset();
this.setVisibleSubMenu(null);
this.clip();
}
}

View File

@ -1,161 +0,0 @@
import { LatLng } from "leaflet";
import { getApp } from "..";
import { GAME_MASTER, IADSTypes } from "../constants/constants";
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
import { ContextMenu } from "./contextmenu";
import { Dropdown } from "../controls/dropdown";
import { Slider } from "../controls/slider";
import { Switch } from "../controls/switch";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
/** This context menu allows the user to edit or delete a CoalitionArea. Moreover, it allows the user to create a IADS automatically using the CoalitionArea as bounds. */
export class CoalitionAreaContextMenu extends ContextMenu {
#coalitionSwitch: Switch;
#coalitionArea: CoalitionArea | null = null;
#iadsDensitySlider: Slider;
#iadsDistributionSlider: Slider;
#iadsTypesDropdown: Dropdown;
#iadsErasDropdown: Dropdown;
#iadsRangesDropdown: Dropdown;
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
/* Create the coalition switch */
this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value), true);
this.#coalitionSwitch.setValue(true);
/* Create the controls of the IADS creation submenu */
this.#iadsTypesDropdown = new Dropdown("iads-units-type-options", () => { });
this.#iadsErasDropdown = new Dropdown("iads-era-options", () => {});
this.#iadsRangesDropdown = new Dropdown("iads-range-options", () => {});
this.#iadsDensitySlider = new Slider("iads-density-slider", 5, 100, "%", (value: number) => { });
this.#iadsDistributionSlider = new Slider("iads-distribution-slider", 5, 100, "%", (value: number) => { });
/* Set the default parameters of the sliders */
this.#iadsDensitySlider.setIncrement(5);
this.#iadsDensitySlider.setValue(50);
this.#iadsDensitySlider.setActive(true);
this.#iadsDistributionSlider.setIncrement(5);
this.#iadsDistributionSlider.setValue(50);
this.#iadsDistributionSlider.setActive(true);
document.addEventListener("coalitionAreaContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.#showSubMenu(e.detail.type);
else
this.#hideSubMenus();
});
document.addEventListener("coalitionAreaBringToBack", (e: any) => {
if (this.#coalitionArea)
getApp().getMap().bringCoalitionAreaToBack(this.#coalitionArea);
getApp().getMap().hideCoalitionAreaContextMenu();
});
document.addEventListener("coalitionAreaDelete", (e: any) => {
if (this.#coalitionArea)
getApp().getMap().deleteCoalitionArea(this.#coalitionArea);
getApp().getMap().hideCoalitionAreaContextMenu();
});
document.addEventListener("contextMenuCreateIads", (e: any) => {
const area = this.getCoalitionArea();
const forceCoalition = (this.getContainer()?.querySelector("#force-coalition")?.querySelector("input") as HTMLInputElement).checked;
if (area)
getApp().getUnitsManager().createIADS(area, getCheckboxOptions(this.#iadsTypesDropdown), getCheckboxOptions(this.#iadsErasDropdown), getCheckboxOptions(this.#iadsRangesDropdown), this.#iadsDensitySlider.getValue(), this.#iadsDistributionSlider.getValue(), forceCoalition);
this.hide();
});
this.hide();
}
/**
*
* @param x X screen coordinate of the top left corner of the context menu
* @param y Y screen coordinate of the top left corner of the context menu
* @param latlng Leaflet latlng object of the mouse click
*/
show(x: number, y: number, latlng: LatLng) {
super.show(x, y, latlng);
/* Create the checkboxes to select the unit roles */
this.#iadsTypesDropdown.setOptionsElements(IADSTypes.map((role: string) => {
return createCheckboxOption(role, `Add ${role}s to the IADS` );
}));
/* Create the checkboxes to select the unit periods */
this.#iadsErasDropdown.setOptionsElements(groundUnitDatabase.getEras().map((era: string) => {
return createCheckboxOption(era, `Add ${era} era units to the IADS`);
}));
/* Create the checkboxes to select the unit ranges */
this.#iadsRangesDropdown.setOptionsElements(["Short range", "Medium range", "Long range"].map((range: string) => {
return createCheckboxOption(range, `Add ${range} units to the IADS`);
}));
if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER)
this.#coalitionSwitch.hide()
}
/** Set the CoalitionArea object the user will be able to edit using this menu
*
* @param coalitionArea The CoalitionArea object to edit
*/
setCoalitionArea(coalitionArea: CoalitionArea) {
this.#coalitionArea = coalitionArea;
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition())
});
this.#coalitionSwitch.setValue(this.getCoalitionArea()?.getCoalition() === "blue");
}
/** Get the CoalitionArea object the contextmenu is editing
*
* @returns The CoalitionArea the contextmenu is editing
*/
getCoalitionArea() {
return this.#coalitionArea;
}
/** Show a submenu of the contextmenu
*
* @param type The submenu type, currently only "iads"
*/
#showSubMenu(type: string) {
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", type !== "iads");
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", type === "iads");
this.clip();
this.setVisibleSubMenu(type);
}
/** Hide all submenus
*
*/
#hideSubMenus() {
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", false);
this.clip();
this.setVisibleSubMenu(null);
}
/** Callback event called when the coalition switch is clicked to change the coalition of the CoalitionArea
*
* @param value Switch position (false: red, true: blue)
*/
#onSwitchClick(value: boolean) {
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER) {
this.getCoalitionArea()?.setCoalition(value ? "blue" : "red");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition())
});
}
}
}

View File

@ -1,107 +0,0 @@
import { LatLng } from "leaflet";
/** Base class for map contextmenus. By default it is empty and requires to be extended. */
export class ContextMenu {
#container: HTMLElement | null;
#latlng: LatLng = new LatLng(0, 0);
#x: number = 0;
#y: number = 0;
#visibleSubMenu: string | null = null;
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
this.#container = document.getElementById(ID);
this.hide();
}
/** Show the contextmenu on top of the map, usually at the location where the user has clicked on it.
*
* @param x X screen coordinate of the top left corner of the context menu. If undefined, use the old value
* @param y Y screen coordinate of the top left corner of the context menu. If undefined, use the old value
* @param latlng Leaflet latlng object of the mouse click. If undefined, use the old value
*/
show(x: number | undefined = undefined, y: number | undefined = undefined, latlng: LatLng | undefined = undefined) {
this.#latlng = latlng ?? this.#latlng;
this.#container?.classList.toggle("hide", false);
this.#x = x ?? this.#x;
this.#y = y ?? this.#y;
this.clip();
this.getContainer()?.dispatchEvent(new Event("show"));
}
/** Hide the contextmenu
*
*/
hide() {
this.#container?.classList.toggle("hide", true);
this.getContainer()?.dispatchEvent(new Event("hide"));
}
/**
*
* @returns The HTMLElement that contains the contextmenu
*/
getContainer() {
return this.#container;
}
/**
*
* @returns The Leaflet latlng object associated to the click that caused the contextmenu to be shown
*/
getLatLng() {
return this.#latlng;
}
/**
*
* @returns The x coordinate of the top left corner of the menu
*/
getX() {
return this.#x;
}
/**
*
* @returns The y coordinate of the top left corner of the menu
*/
getY() {
return this.#y;
}
/** Clips the contextmenu, meaning it moves it on the screen to make sure it does not overflow the window.
*
*/
clip() {
if (this.#container != null) {
if (this.#x + this.#container.offsetWidth < window.innerWidth)
this.#container.style.left = this.#x + "px";
else
this.#container.style.left = window.innerWidth - this.#container.offsetWidth - 10 + "px";
if (this.#y + this.#container.offsetHeight < window.innerHeight)
this.#container.style.top = this.#y + "px";
else
this.#container.style.top = window.innerHeight - this.#container.offsetHeight - 10 + "px";
}
}
/** Sets the currently visible submenu
*
* @param menu The name of the currently visibile submenu, or null if no submenu is visible
*/
setVisibleSubMenu(menu: string | null) {
this.#visibleSubMenu = menu;
}
/**
*
* @returns The name of the currently visible submenu
*/
getVisibleSubMenu() {
return this.#visibleSubMenu;
}
}

View File

@ -1,351 +0,0 @@
import { LatLng } from "leaflet";
import { getApp } from "..";
import { ContextMenu } from "./contextmenu";
import { Switch } from "../controls/switch";
import { GAME_MASTER } from "../constants/constants";
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
import { AirDefenceUnitSpawnMenu, AircraftSpawnMenu, GroundUnitSpawnMenu, HelicopterSpawnMenu, NavyUnitSpawnMenu } from "../controls/unitspawnmenu";
import { SmokeMarker } from "../map/markers/smokemarker";
import { UnitSpawnTable } from "../interfaces";
import { getCategoryBlueprintIconSVG, getUnitDatabaseByCategory } from "../other/utils";
import { SVGInjector } from "@tanem/svg-injector";
/** The MapContextMenu is the main contextmenu shown to the user whenever it rightclicks on the map. It is the primary interaction method for the user.
* It allows to spawn units, create explosions and smoke, and edit CoalitionAreas.
*/
export class MapContextMenu extends ContextMenu {
#coalitionSwitch: Switch;
#aircraftSpawnMenu: AircraftSpawnMenu;
#helicopterSpawnMenu: HelicopterSpawnMenu;
#airDefenceUnitSpawnMenu: AirDefenceUnitSpawnMenu;
#groundUnitSpawnMenu: GroundUnitSpawnMenu;
#navyUnitSpawnMenu: NavyUnitSpawnMenu;
#coalitionArea: CoalitionArea | null = null;
#selectedUnitCategory: string = "";
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
/* Create the coalition switch */
this.#coalitionSwitch = new Switch("coalition-switch", (value: boolean) => this.#onSwitchClick(value));
this.#coalitionSwitch.setValue(true);
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick());
/* Create the spawn menus for the different unit types */
this.#aircraftSpawnMenu = new AircraftSpawnMenu("aircraft-spawn-menu");
this.#helicopterSpawnMenu = new HelicopterSpawnMenu("helicopter-spawn-menu");
this.#airDefenceUnitSpawnMenu = new AirDefenceUnitSpawnMenu("air-defence-spawn-menu");
this.#groundUnitSpawnMenu = new GroundUnitSpawnMenu("groundunit-spawn-menu");
this.#navyUnitSpawnMenu = new NavyUnitSpawnMenu("navyunit-spawn-menu");
/* Event listeners */
document.addEventListener("mapContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type) {
this.#showSubMenu(e.detail.type);
this.#selectedUnitCategory = e.detail.type;
} else
this.#hideSubMenus(e.detail.type);
});
document.addEventListener("contextMenuDeploySmoke", (e: any) => {
this.hide();
getApp().getServerManager().spawnSmoke(e.detail.color, this.getLatLng());
var marker = new SmokeMarker(this.getLatLng(), e.detail.color);
marker.addTo(getApp().getMap());
});
document.addEventListener("contextMenuExplosion", (e: any) => {
this.hide();
getApp().getServerManager().spawnExplosion(e.detail.strength ?? 0, e.detail.explosionType, this.getLatLng());
});
document.addEventListener("editCoalitionArea", (e: any) => {
this.hide();
if (this.#coalitionArea) {
getApp().getMap().deselectAllCoalitionAreas();
this.#coalitionArea.setSelected(true);
}
});
// document.addEventListener("commandModeOptionsChanged", (e: any) => {
// //this.#refreshOptions();
// });
this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#airDefenceUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#groundUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#navyUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#airDefenceUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#groundUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#navyUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.getContainer()?.addEventListener("show", () => this.#aircraftSpawnMenu.showCirclesPreviews());
this.getContainer()?.addEventListener("show", () => this.#helicopterSpawnMenu.showCirclesPreviews());
this.getContainer()?.addEventListener("show", () => this.#airDefenceUnitSpawnMenu.showCirclesPreviews());
this.getContainer()?.addEventListener("show", () => this.#groundUnitSpawnMenu.showCirclesPreviews());
this.getContainer()?.addEventListener("show", () => this.#navyUnitSpawnMenu.showCirclesPreviews());
this.getContainer()?.addEventListener("hide", () => this.#aircraftSpawnMenu.clearCirclesPreviews());
this.getContainer()?.addEventListener("hide", () => this.#helicopterSpawnMenu.clearCirclesPreviews());
this.getContainer()?.addEventListener("hide", () => this.#airDefenceUnitSpawnMenu.clearCirclesPreviews());
this.getContainer()?.addEventListener("hide", () => this.#groundUnitSpawnMenu.clearCirclesPreviews());
this.getContainer()?.addEventListener("hide", () => this.#navyUnitSpawnMenu.clearCirclesPreviews());
this.#setupHistory();
}
/** Show the contextmenu on top of the map, usually at the location where the user has clicked on it.
*
* @param x X screen coordinate of the top left corner of the context menu
* @param y Y screen coordinate of the top left corner of the context menu
* @param latlng Leaflet latlng object of the mouse click
*/
show(x: number, y: number, latlng: LatLng) {
if (!getApp().getCurrentContext().getUseSpawnMenu())
return false;
super.show(x, y, latlng);
this.#aircraftSpawnMenu.setLatLng(latlng);
this.#helicopterSpawnMenu.setLatLng(latlng);
this.#airDefenceUnitSpawnMenu.setLatLng(latlng);
this.#groundUnitSpawnMenu.setLatLng(latlng);
this.#navyUnitSpawnMenu.setLatLng(latlng);
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.setCountries();
this.#airDefenceUnitSpawnMenu.setCountries();
this.#groundUnitSpawnMenu.setCountries();
this.#navyUnitSpawnMenu.setCountries();
/* Only a Game Master can choose the coalition of a new unit */
if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER)
this.#coalitionSwitch.hide()
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) });
if (getApp().getActiveCoalition() == "blue")
this.#coalitionSwitch.setValue(true);
else if (getApp().getActiveCoalition() == "red")
this.#coalitionSwitch.setValue(false);
else
this.#coalitionSwitch.setValue(undefined);
/* Hide the coalition area button. It will be visible if a coalition area is set */
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", true);
}
/** If the user rightclicked on a CoalitionArea, it will be given the ability to edit it.
*
* @param coalitionArea The CoalitionArea the user can edit
*/
setCoalitionArea(coalitionArea: CoalitionArea) {
this.#coalitionArea = coalitionArea;
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", false);
}
/** Shows the submenu depending on unit selection
*
* @param type Submenu type, either "aircraft", "helicopter", "groundunit", "navyunit", "smoke", or "explosion"
*/
#showSubMenu(type: string) {
if (type === "more")
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide");
else if (["aircraft", "helicopter", "air-defence", "groundunit"].includes(type))
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", type !== "helicopter");
this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter");
this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", type !== "groundunit");
this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", type === "groundunit");
this.getContainer()?.querySelector("#air-defence-spawn-menu")?.classList.toggle("hide", type !== "air-defence");
this.getContainer()?.querySelector("#air-defence-spawn-button")?.classList.toggle("is-open", type === "air-defence");
this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", type !== "navyunit");
this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", type === "navyunit");
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke");
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", type === "smoke");
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion");
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", type === "explosion");
(this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => { (element as HTMLButtonElement).disabled = true; })
this.#aircraftSpawnMenu.reset();
this.#aircraftSpawnMenu.setCountries();
this.#aircraftSpawnMenu.clearCirclesPreviews();
this.#helicopterSpawnMenu.reset();
this.#helicopterSpawnMenu.setCountries();
this.#helicopterSpawnMenu.clearCirclesPreviews();
this.#airDefenceUnitSpawnMenu.reset();
this.#airDefenceUnitSpawnMenu.setCountries();
this.#airDefenceUnitSpawnMenu.clearCirclesPreviews();
this.#groundUnitSpawnMenu.reset();
this.#groundUnitSpawnMenu.setCountries();
this.#groundUnitSpawnMenu.clearCirclesPreviews();
this.#navyUnitSpawnMenu.reset();
this.#navyUnitSpawnMenu.setCountries();
this.#navyUnitSpawnMenu.clearCirclesPreviews();
this.setVisibleSubMenu(type);
this.clip();
}
/** Hide all the submenus
*
* @param type The type of the currenlt open submenu.
*/
#hideSubMenus(type: string) {
/* Close the lower options bar if the currently open submenu does not required it */
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", ["aircraft", "helicopter", "groundunit"].includes(type));
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", false);
this.#aircraftSpawnMenu.reset();
this.#helicopterSpawnMenu.reset();
this.#airDefenceUnitSpawnMenu.reset();
this.#groundUnitSpawnMenu.reset();
this.#navyUnitSpawnMenu.reset();
this.#aircraftSpawnMenu.clearCirclesPreviews();
this.#helicopterSpawnMenu.clearCirclesPreviews();
this.#airDefenceUnitSpawnMenu.clearCirclesPreviews();
this.#groundUnitSpawnMenu.clearCirclesPreviews();
this.#navyUnitSpawnMenu.clearCirclesPreviews();
this.setVisibleSubMenu(null);
this.clip();
}
/** Callback called when the user left clicks on the coalition switch
*
* @param value Switch position (true: "blue", false: "red")
*/
#onSwitchClick(value: boolean) {
value ? getApp().setActiveCoalition("blue") : getApp().setActiveCoalition("red");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) });
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.setCountries();
this.#airDefenceUnitSpawnMenu.setCountries();
this.#groundUnitSpawnMenu.setCountries();
this.#navyUnitSpawnMenu.setCountries();
}
/** Callback called when the user rightclicks on the coalition switch. This will select the "neutral" coalition.
*
*/
#onSwitchRightClick() {
this.#coalitionSwitch.setValue(undefined);
getApp().setActiveCoalition("neutral");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) });
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.setCountries();
this.#airDefenceUnitSpawnMenu.setCountries();
this.#groundUnitSpawnMenu.setCountries();
this.#navyUnitSpawnMenu.setCountries();
}
/** Handles all of the logic for historal logging.
*
*/
#setupHistory() {
/* Set up the tab clicks */
const spawnModes = this.getContainer()?.querySelectorAll(".spawn-mode");
const activeCoalitionLabel = document.getElementById("active-coalition-label");
const tabs = this.getContainer()?.querySelectorAll(".spawn-mode-tab");
// Default selected tab to the "spawn now" option
if (tabs) tabs[tabs.length-1].classList.add("selected");
tabs?.forEach((btn:Element) => {
btn.addEventListener("click", (ev:MouseEventInit) => {
// Highlight tab
tabs.forEach(tab => tab.classList.remove("selected"));
btn.classList.add("selected");
// Hide/reset
spawnModes?.forEach(div => div.classList.add("hide"));
const prevSiblings = [];
let prev = btn.previousElementSibling;
/* Tabs and content windows are assumed to be in the same order */
// Count previous
while ( prev ) {
prevSiblings.push(prev);
prev = prev.previousElementSibling;
}
// Show content
if (spawnModes && spawnModes[prevSiblings.length]) {
spawnModes[prevSiblings.length].classList.remove("hide");
}
// We don't want to see the "Spawn [coalition] unit" label
if (activeCoalitionLabel) activeCoalitionLabel.classList.toggle("hide", !btn.hasAttribute("data-show-label"));
});
});
const history = <HTMLDivElement>document.getElementById("spawn-history-menu");
const maxEntries = 20;
/** Listen for unit spawned **/
document.addEventListener( "unitSpawned", (ev:CustomEventInit) => {
const buttons = history.querySelectorAll("button");
const detail:any = ev.detail;
if (buttons.length === 0) history.innerHTML = ""; // Take out any "no data" messages
const button = document.createElement("button");
button.title = "Click to spawn";
button.setAttribute("data-spawned-coalition", detail.coalition);
button.setAttribute("data-unit-type", detail.unitSpawnTable[0].unitType);
button.setAttribute("data-unit-qty", detail.unitSpawnTable.length);
const db = getUnitDatabaseByCategory(detail.category);
button.innerHTML = `<img src="${getCategoryBlueprintIconSVG(detail.category, detail.unitSpawnTable[0].unitType)}" /><span>${db?.getByName(detail.unitSpawnTable[0].unitType)?.label} (${detail.unitSpawnTable.length})</span>`;
// Remove a previous instance to save clogging up the list
const previous:any = [].slice.call(buttons).find( (button:Element) => (
detail.coalition === button.getAttribute("data-spawned-coalition") &&
detail.unitSpawnTable[0].unitType === button.getAttribute("data-unit-type") &&
detail.unitSpawnTable.length === parseInt(button.getAttribute("data-unit-qty") || "-1")));
if (previous instanceof HTMLElement) previous.remove();
/* Click to do the spawn */
button.addEventListener("click", (ev:MouseEventInit) => {
detail.unitSpawnTable.forEach((table:UnitSpawnTable, i:number) => {
table.location = this.getLatLng(); // Set to new menu location
table.location.lat += 0.00015 * i;
});
getApp().getUnitsManager().spawnUnits(detail.category, detail.unitSpawnTable, detail.coalition, detail.immediate, detail.airbase, detail.country);
this.hide();
});
/* Insert into DOM */
history.prepend(button);
SVGInjector(button.querySelectorAll("img"));
/* Trim down to max number of entries */
while (history.querySelectorAll("button").length > maxEntries) {
history.childNodes[maxEntries].remove();
}
});
}
}

View File

@ -1,36 +0,0 @@
import { ContextActionSet } from "../unit/contextactionset";
import { ContextMenu } from "./contextmenu";
/** The UnitContextMenu is shown when the user rightclicks on a unit. It dynamically presents the user with possible actions to perform on the unit. */
export class UnitContextMenu extends ContextMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
}
/** Set the options that will be presented to the user in the contextmenu
*
* @param options Dictionary element containing the text and tooltip of the options shown in the menu
* @param callback Callback that will be called when the user clicks on one of the options
*/
setContextActions(contextActionSet: ContextActionSet) {
this.getContainer()?.replaceChildren(...Object.keys(contextActionSet.getContextActions()).map((key: string, idx: number) => {
const contextAction = contextActionSet.getContextActions()[key];
var button = document.createElement("button");
var el = document.createElement("div");
el.title = contextAction.getDescription();
el.innerText = contextAction.getLabel();
el.id = key;
button.addEventListener("click", () => {
contextAction.executeCallback();
if (contextAction.getHideContextAfterExecution())
this.hide();
});
button.appendChild(el);
return (button);
}));
}
}

View File

@ -1,41 +0,0 @@
export class Control {
#container: HTMLElement | null;
expectedValue: any = null;
constructor(container: string | null, options?: any) {
if (typeof container === "string")
this.#container = document.getElementById(container);
else
this.#container = this.createElement(options);
}
show() {
if (this.#container != null)
this.#container.classList.remove("hide");
}
hide() {
if (this.#container != null)
this.#container.classList.add("hide");
}
getContainer() {
return this.#container;
}
setExpectedValue(expectedValue: any) {
this.expectedValue = expectedValue;
}
resetExpectedValue() {
this.expectedValue = null;
}
checkExpectedValue(value: any) {
return this.expectedValue === null || value === this.expectedValue;
}
createElement(options?: any): HTMLElement | null {
return null;
}
}

View File

@ -1,345 +0,0 @@
export class Dropdown {
#container: HTMLElement;
#options: HTMLElement;
#value: HTMLElement;
#callback: CallableFunction;
#defaultValue: string;
#optionsList: string[] = [];
#labelsList: string[] | undefined = [];
#index: number = 0;
#hidden: boolean = false;
#text!: HTMLElement;
constructor(ID: string | null, callback: CallableFunction, options: string[] | null = null, defaultText?: string) {
if (ID === null)
this.#container = this.#createElement(defaultText);
else
this.#container = document.getElementById(ID) as HTMLElement;
this.#options = this.#container.querySelector(".ol-select-options") as HTMLElement;
const text = this.#container.querySelector(".ol-select-value-text");
this.#value = (text instanceof HTMLElement) ? text : this.#container.querySelector(".ol-select-value") as HTMLElement;
this.#defaultValue = this.#value.innerText;
this.#callback = callback;
if (options != null) this.setOptions(options);
(this.#container.querySelector(".ol-select-value") as HTMLElement)?.addEventListener("click", (ev) => {
if (!this.#container.hasAttribute("disabled") && !this.#container.closest("disabled")) this.#toggle();
});
document.addEventListener("click", (ev) => {
if (!(this.#value.contains(ev.target as Node) || this.#options.contains(ev.target as Node) || this.#container.contains(ev.target as Node))) {
this.close();
}
});
this.#options.classList.add("ol-scrollable");
}
getContainer() {
return this.#container;
}
/** Set the dropdown options strings
*
* @param optionsList List of options. These are the keys that will always be returned on selection
* @param sort Sort method. null means no sorting. "string" performs js default sort. "number" sorts purely by numeric value.
* "string+number" sorts by string, unless two elements are lexicographically identical up to a numeric value (e.g. "SA-2" and "SA-3"), in which case it sorts by number.
* @param labelsList (Optional) List of labels to be shown instead of the keys directly. If provided, the options will be sorted by label.
*/
setOptions(optionsList: string[], sort: null | "string" | "number" | "string+number" = "string", labelsList: string[] | undefined = undefined) {
if (sort != null) {
/* If labels are provided, sort by labels, else by options */
if (labelsList && labelsList.length == optionsList.length)
this.#sortByLabels(optionsList, sort, labelsList);
else
this.#sortByOptions(optionsList, sort);
} else {
this.#optionsList = optionsList;
}
/* If no options are provided, return */
if (this.#optionsList.length == 0) {
optionsList = ["No options available"]
this.#value.innerText = "No options available";
return;
}
/* Create the buttons containing the options or the labels */
this.#options.replaceChildren(...this.#optionsList.map((option: string, idx: number) => {
var div = document.createElement("div");
var button = document.createElement("button");
/* If the labels are provided use them for the options */
if (this.#labelsList && this.#labelsList.length === optionsList.length)
button.textContent = this.#labelsList[idx];
else
button.textContent = option;
div.appendChild(button);
if (option === this.#defaultValue)
this.#index = idx;
button.addEventListener("click", (e: MouseEvent) => {
e.stopPropagation();
this.selectValue(idx);
});
return div;
}));
}
getOptionsList() {
return this.#optionsList;
}
getLabelsList() {
return this.#labelsList;
}
/** Manually set the HTMLElements of the dropdown values. Handling of the selection must be performed externally.
*
* @param optionsElements List of elements to be added to the dropdown
*/
setOptionsElements(optionsElements: HTMLElement[]) {
this.#optionsList = [];
this.#labelsList = [];
this.#options.replaceChildren(...optionsElements);
}
getOptionElements() {
return this.#options.children;
}
addOptionElement(optionElement: HTMLElement) {
this.#options.appendChild(optionElement);
}
addHorizontalDivider() {
let div = document.createElement("div");
let hr = document.createElement('hr');
div.appendChild(hr);
this.#options.appendChild(div);
}
/** Select the active value of the dropdown
*
* @param idx The index of the element to select
* @returns True if the index is valid, false otherwise
*/
selectValue(idx: number) {
if (idx < this.#optionsList.length) {
var option = this.#optionsList[idx];
var el = document.createElement("div");
el.classList.add("ol-ellipsed");
if (this.#labelsList && this.#labelsList.length == this.#optionsList.length)
el.innerText = this.#labelsList[idx];
else
el.innerText = option;
this.#value.replaceChildren();
this.#value.appendChild(el);
this.#index = idx;
this.close();
this.#callback(option);
return true;
}
else
return false;
}
reset() {
this.#options.replaceChildren();
this.#value.innerText = this.#defaultValue;
}
/** Manually set the selected value of the dropdown
*
* @param value The value to select. Must be one of the valid options
*/
setValue(value: string) {
var index = this.#optionsList.findIndex((option) => { return option === value });
if (index > -1)
this.selectValue(index);
}
getValue() {
return this.#value.innerText;
}
/** Force the selected value of the dropdown.
*
* @param value Any string. Will be shown as selected value even if not one of the options.
*/
forceValue(value: string) {
var el = document.createElement("div");
el.classList.add("ol-ellipsed");
el.innerText = value;
this.#value.replaceChildren();
this.#value.appendChild(el);
this.close();
}
getIndex() {
return this.#index;
}
clip() {
const options = this.#options;
const bounds = options.getBoundingClientRect();
this.#container.dataset.position = (bounds.bottom > window.innerHeight) ? "top" : "";
}
close() {
this.#container.classList.remove("is-open");
this.#container.dataset.position = "";
}
open() {
this.#container.classList.add("is-open");
this.#options.classList.toggle("scrollbar-visible", this.#options.scrollHeight > this.#options.clientHeight);
this.clip();
}
show() {
this.#container.classList.remove("hide");
this.#hidden = false;
}
hide() {
this.#container.classList.add("hide");
this.#hidden = true;
}
isHidden() {
return this.#hidden;
}
#toggle() {
this.#container.classList.contains("is-open") ? this.close() : this.open();
}
#createElement(defaultText: string | undefined) {
var div = document.createElement("div");
div.classList.add("ol-select");
var value = document.createElement("div");
value.classList.add("ol-select-value");
value.innerText = defaultText ? defaultText : "";
var options = document.createElement("div");
options.classList.add("ol-select-options");
div.append(value, options);
return div;
}
/** Sort the elements by their option keys
*
* @param optionsList The unsorted list of options
* @param sort The sorting method
*/
#sortByOptions(optionsList: string[], sort: string) {
if (sort === "number") {
this.#optionsList = JSON.parse(JSON.stringify(this.#numberSort(optionsList)));
} else if (sort === "string+number") {
this.#optionsList = JSON.parse(JSON.stringify(this.#stringNumberSort(optionsList)));
} else if (sort === "string") {
this.#optionsList = JSON.parse(JSON.stringify(this.#stringSort(optionsList)));
}
}
/** Sort the elements by their labels
*
* @param optionsList The unsorted list of options
* @param sort The sorting method
* @param labelsList The unsorted list of labels. The elements will be sorted according to these values
*/
#sortByLabels(optionsList: string[], sort: string, labelsList: string[]) {
/* Create a temporary deepcopied list. This is necessary because unlike options, labels can be repeated.
Once matched, labels are removed from the temporary array to avoid repeating the same key multiple times */
var tempLabelsList: (string | undefined)[] = JSON.parse(JSON.stringify(labelsList));
if (sort === "number") {
this.#labelsList = JSON.parse(JSON.stringify(this.#numberSort(labelsList)));
} else if (sort === "string+number") {
this.#labelsList = JSON.parse(JSON.stringify(this.#stringNumberSort(labelsList)));
} else if (sort === "string") {
this.#labelsList = JSON.parse(JSON.stringify(this.#stringSort(labelsList)));
}
/* Remap the options list to match their labels */
this.#optionsList = optionsList?.map((option: string, idx: number) => {
let originalIdx = tempLabelsList.indexOf(this.#labelsList? this.#labelsList[idx]: "");
/* After a match has been completed, set the label to undefined so it won't be matched again. This allows to have repeated labels */
tempLabelsList[originalIdx] = undefined;
return optionsList[originalIdx];
})
}
/** Sort elements by number. All elements must be parsable as numbers.
*
* @param elements List of strings
* @returns Sorted list
*/
#numberSort(elements: string[]) {
return elements.sort((elementA: string, elementB: string) => {
const a = parseFloat(elementA);
const b = parseFloat(elementB);
if (a > b)
return 1;
else
return (b > a) ? -1 : 0;
});
}
/** Sort elements by string, unless two elements are lexicographically identical up to a numeric value (e.g. "SA-2" and "SA-3"), in which case sort by number
*
* @param elements List of strings
* @returns Sorted list
*/
#stringNumberSort(elements: string[]) {
return elements.sort((elementA: string, elementB: string) => {
/* Check if there is a number in both strings */
var regex = /\d+/g;
var matchesA = elementA.match(regex);
var matchesB = elementB.match(regex);
/* Get the position of the number in the string */
var indexA = -1;
var indexB = -1;
if (matchesA != null && matchesA?.length > 0)
indexA = elementA.search(matchesA[0]);
if (matchesB != null && matchesB?.length > 0)
indexB = elementB.search(matchesB[0]);
/* If the two strings are the same up to the number, sort them using the number value, else sort them according to the string */
if ((matchesA != null && matchesA?.length > 0) && (matchesB != null && matchesB?.length > 0) && elementA.substring(0, indexA) === elementB.substring(0, indexB)) {
const a = parseInt(matchesA[0] ?? 0);
const b = parseInt(matchesB[0] ?? 0);
if (a > b)
return 1;
else
return (b > a) ? -1 : 0;
} else {
if (elementA > elementB)
return 1;
else
return (elementB > elementA) ? -1 : 0;
}
});
}
/** Sort by string. Just a wrapper for consistency.
*
* @param elements List of strings
* @returns Sorted list
*/
#stringSort(elements: string[]) {
return elements.sort();
}
}

View File

@ -1,154 +0,0 @@
import { zeroPad } from "../other/utils";
import { Control } from "./control";
export class Slider extends Control {
#callback: CallableFunction | null = null;
#slider: HTMLInputElement | null = null;
#valueText: HTMLElement | null = null;
#minValue: number = 0;
#maxValue: number = 0;
#increment: number = 0;
#minMaxValueDiv: HTMLElement | null = null;
#unitOfMeasure: string;
#dragged: boolean = false;
#value: number = 0;
constructor(ID: string | null, minValue: number, maxValue: number, unitOfMeasure: string, callback: CallableFunction, options?: any) {
super(ID, options);
this.#callback = callback;
this.#unitOfMeasure = unitOfMeasure;
this.#slider = this.getContainer()?.querySelector("input") as HTMLInputElement;
if (this.#slider != null) {
this.#slider.addEventListener("input", (e: any) => this.#update());
this.#slider.addEventListener("mousedown", (e: any) => this.#onStart());
this.#slider.addEventListener("mouseup", (e: any) => this.#onFinalize());
}
this.#valueText = this.getContainer()?.querySelector(".ol-slider-value") as HTMLElement;
this.#minMaxValueDiv = this.getContainer()?.querySelector(".ol-slider-min-max") as HTMLElement;
this.setIncrement(1);
this.setMinMax(minValue, maxValue);
}
setActive(newActive: boolean) {
if (!this.getDragged()) {
this.getContainer()?.classList.toggle("active", newActive);
if (!newActive && this.#valueText != null)
this.#valueText.innerText = "Mixed values";
}
}
setMinMax(newMinValue: number, newMaxValue: number) {
if (this.#minValue != newMinValue || this.#maxValue != newMaxValue) {
this.#minValue = newMinValue;
this.#maxValue = newMaxValue;
this.#updateMaxValue();
if (this.#minMaxValueDiv != null) {
this.#minMaxValueDiv.setAttribute('data-min-value', `${this.#minValue}${this.#unitOfMeasure}`);
this.#minMaxValueDiv.setAttribute('data-max-value', `${this.#maxValue}${this.#unitOfMeasure}`);
}
}
}
setIncrement(newIncrement: number) {
if (this.#increment != newIncrement) {
this.#increment = newIncrement;
this.#updateMaxValue();
}
}
setValue(newValue: number, ignoreExpectedValue: boolean = true) {
if (!this.getDragged() && (ignoreExpectedValue || this.checkExpectedValue(newValue))) {
if (this.#value !== newValue)
this.#value = newValue;
if (this.#slider != null)
this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max));
this.#update();
}
}
getValue() {
return this.#value;
}
setDragged(newDragged: boolean) {
this.#dragged = newDragged;
}
getDragged() {
return this.#dragged;
}
#updateMaxValue() {
var oldValue = this.getValue();
if (this.#slider != null)
this.#slider.max = String((this.#maxValue - this.#minValue) / this.#increment);
this.setValue(oldValue);
}
#update() {
if (this.#valueText != null && this.#slider != null)
{
/* Update the text value */
var value = this.#minValue + Math.round(parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue));
var strValue = String(value);
if (value > 1000)
strValue = String(Math.floor(value / 1000)) + "," + zeroPad(value - Math.floor(value / 1000) * 1000, 3);
this.#valueText.innerText = `${strValue} ${this.#unitOfMeasure.toUpperCase()}`;
/* Update the position of the slider */
var percentValue = parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * 90 + 5;
this.#slider.style.background = `linear-gradient(to right, var(--accent-light-blue) 5%, var(--accent-light-blue) ${percentValue}%, var(--background-dark-grey) ${percentValue}%, var(--background-dark-grey) 100%)`
}
this.setActive(true);
}
#onStart() {
this.setDragged(true);
}
#onFinalize() {
this.setDragged(false);
if (this.#slider != null) {
this.resetExpectedValue();
this.setValue(this.#minValue + parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue));
if (this.#callback) {
this.#callback(this.getValue());
this.setExpectedValue(this.getValue());
}
}
}
createElement(options?: any): HTMLElement | null {
var containerEl = document.createElement("div");
containerEl.classList.add("ol-slider-container", "flight-control-ol-slider");
var dl = document.createElement("dl");
dl.classList.add("ol-data-grid");
var dt = document.createElement("dt");
dt.innerText = (options !== undefined && options.title !== undefined)? options.title: "";
var dd = document.createElement("dd");
var sliderEl = document.createElement("div");
sliderEl.classList.add("ol-slider-value");
dd.append(sliderEl);
dl.append(dt, dd);
var input = document.createElement("input") as HTMLInputElement;
input.type = "range";
input.min = "0";
input.max = "100";
input.value = "0"
input.classList.add("ol-slider");
containerEl.append(dl, input);
return containerEl;
}
}

View File

@ -1,47 +0,0 @@
import { Control } from "./control";
export class Switch extends Control {
#value: boolean | undefined = false;
#callback: CallableFunction | null = null;
// TODO: allow for null ID so that the element is created automatically
constructor(ID: string, callback: CallableFunction, initialValue?: boolean) {
super(ID);
this.getContainer()?.addEventListener('click', (e) => this.#onToggle());
this.setValue(initialValue !== undefined? initialValue: true);
this.#callback = callback;
/* Add the toggle itself to the document */
const container = this.getContainer();
if (container != undefined){
const width = getComputedStyle(container).width;
const height = getComputedStyle(container).height;
var el = document.createElement("div");
el.classList.add("ol-switch-fill");
el.style.setProperty("--width", width? width: "0");
el.style.setProperty("--height", height? height: "0");
this.getContainer()?.appendChild(el);
}
}
setValue(newValue: boolean | undefined, ignoreExpectedValue: boolean = true) {
if (ignoreExpectedValue || this.checkExpectedValue(newValue)) {
this.#value = newValue;
this.getContainer()?.setAttribute("data-value", String(newValue));
}
}
getValue() {
return this.#value;
}
#onToggle() {
this.resetExpectedValue();
this.setValue(!this.getValue());
if (this.#callback) {
this.#callback(this.getValue());
this.setExpectedValue(this.getValue());
}
}
}

View File

@ -1,836 +0,0 @@
import { Circle, LatLng } from "leaflet";
import { Dropdown } from "./dropdown";
import { Slider } from "./slider";
import { UnitDatabase } from "../unit/databases/unitdatabase";
import { getApp } from "..";
import { GAME_MASTER, GROUND_UNIT_AIR_DEFENCE_REGEX } from "../constants/constants";
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 { UnitBlueprint, UnitSpawnOptions, UnitSpawnTable } from "../interfaces";
/** This is the common code for all the unit spawn menus. It is shown both when right clicking on the map and when spawning from airbase.
*
*/
export abstract class UnitSpawnMenu {
protected showRangeCircles: boolean = false;
protected unitTypeFilter = (unit:any) => { return true; };
/* Default options */
protected spawnOptions: UnitSpawnOptions = {
roleType: "",
name: "",
latlng: new LatLng(0, 0),
coalition: "blue",
count: 1,
country: "",
skill: "Excellent",
loadout: undefined,
airbase: undefined,
liveryID: undefined,
altitude: undefined
};
#container: HTMLElement;
#unitDatabase: UnitDatabase;
#countryCodes: any;
#orderByRole: boolean;
#showLoadout: boolean = true;
#showSkill: boolean = true;
#showAltitudeSlider: boolean = true;
/* Controls */
#unitRoleTypeDropdown: Dropdown;
#unitLabelDropdown: Dropdown;
#unitCountDropdown: Dropdown;
#unitLoadoutDropdown: Dropdown;
#unitSkillDropdown: Dropdown;
#unitCountryDropdown: Dropdown;
#unitLiveryDropdown: Dropdown;
#unitSpawnAltitudeSlider: Slider;
/* HTML Elements */
#deployUnitButtonEl: HTMLButtonElement;
#unitCountDivider: HTMLDivElement;
#unitLoadoutPreviewEl: HTMLDivElement;
#unitImageEl: HTMLImageElement;
#unitLoadoutListEl: HTMLDivElement;
#descriptionDiv: HTMLDivElement;
#abilitiesDiv: HTMLDivElement;
#advancedOptionsDiv: HTMLDivElement;
#unitInfoDiv: HTMLDivElement;
#advancedOptionsToggle: HTMLDivElement;
#advancedOptionsText: HTMLDivElement;
#unitInfoToggle: HTMLDivElement;
#unitInfoText: HTMLDivElement;
/* Range circle previews */
#engagementCircle: Circle;
#acquisitionCircle: Circle;
constructor(ID: string, unitDatabase: UnitDatabase, orderByRole: boolean) {
this.#container = document.getElementById(ID) as HTMLElement;
this.#unitDatabase = unitDatabase;
this.#orderByRole = orderByRole;
/* Create the dropdowns and the altitude slider */
this.#unitRoleTypeDropdown = new Dropdown(null, (roleType: string) => this.#setUnitRoleType(roleType), undefined, "Role");
this.#unitLabelDropdown = new Dropdown(null, (name: string) => this.#setUnitName(name), undefined, "Type");
this.#unitLoadoutDropdown = new Dropdown(null, (loadout: string) => this.#setUnitLoadout(loadout), undefined, "Loadout");
this.#unitSkillDropdown = new Dropdown(null, (skill: string) => this.#setUnitSkill(skill), undefined, "Skill");
this.#unitCountDropdown = new Dropdown(null, (count: string) => this.#setUnitCount(count), undefined, "Count");
this.#unitCountryDropdown = new Dropdown(null, () => { /* Custom button implementation */ }, undefined, "Country");
this.#unitLiveryDropdown = new Dropdown(null, (livery: string) => this.#setUnitLivery(livery), undefined, "Livery");
this.#unitSpawnAltitudeSlider = new Slider(null, 0, 1000, "ft", (value: number) => { this.spawnOptions.altitude = ftToM(value); }, { title: "Spawn altitude" });
/* The unit label and unit count are in the same "row" for clarity and compactness */
var unitLabelCountContainerEl = document.createElement("div");
unitLabelCountContainerEl.classList.add("unit-label-count-container");
this.#unitCountDivider = document.createElement("div");
this.#unitCountDivider.innerText = "x";
unitLabelCountContainerEl.append(this.#unitLabelDropdown.getContainer(), this.#unitCountDivider, this.#unitCountDropdown.getContainer());
/* Create the unit image and loadout elements */
this.#unitImageEl = document.createElement("img");
this.#unitImageEl.classList.add("unit-image", "hide");
this.#unitLoadoutPreviewEl = document.createElement("div");
this.#unitLoadoutPreviewEl.classList.add("unit-loadout-preview");
this.#unitLoadoutListEl = document.createElement("div");
this.#unitLoadoutListEl.classList.add("unit-loadout-list");
this.#unitLoadoutPreviewEl.append(this.#unitImageEl, this.#unitLoadoutListEl);
/* Create the advanced options collapsible div */
this.#advancedOptionsDiv = document.createElement("div");
this.#advancedOptionsDiv.classList.add("contextmenu-advanced-options", "hide");
this.#advancedOptionsToggle = document.createElement("div");
this.#advancedOptionsToggle.classList.add("contextmenu-advanced-options-toggle");
this.#advancedOptionsText = document.createElement("div");
this.#advancedOptionsText.innerText = "Faction / Liveries / Skill Level";
this.#advancedOptionsToggle.append(this.#advancedOptionsText);
this.#advancedOptionsToggle.addEventListener("click", () => {
this.#advancedOptionsToggle.classList.toggle("is-open");
this.#advancedOptionsDiv.classList.toggle("hide");
this.#container.dispatchEvent(new Event("resize"));
});
this.#advancedOptionsDiv.append(this.#unitCountryDropdown.getContainer(), this.#unitLiveryDropdown.getContainer(), this.#unitSkillDropdown.getContainer());
/* Create the unit info collapsible div */
this.#unitInfoDiv = document.createElement("div");
this.#unitInfoDiv.classList.add("contextmenu-metadata", "hide");
this.#unitInfoToggle = document.createElement("div");
this.#unitInfoToggle.classList.add("contextmenu-metadata-toggle");
this.#unitInfoText = document.createElement("div");
this.#unitInfoText.innerText = "Unit information";
this.#unitInfoToggle.append(this.#unitInfoText);
this.#unitInfoToggle.addEventListener("click", () => {
this.#unitInfoToggle.classList.toggle("is-open");
this.#unitInfoDiv.classList.toggle("hide");
this.#container.dispatchEvent(new Event("resize"));
});
this.#descriptionDiv = document.createElement("div");
this.#abilitiesDiv = document.createElement("div");
this.#unitInfoDiv.append(this.#descriptionDiv, this.#abilitiesDiv);
/* Create the unit deploy button */
this.#deployUnitButtonEl = document.createElement("button");
this.#deployUnitButtonEl.classList.add("deploy-unit-button");
this.#deployUnitButtonEl.disabled = true;
this.#deployUnitButtonEl.innerText = "Deploy unit";
this.#deployUnitButtonEl.setAttribute("data-coalition", "blue");
this.#deployUnitButtonEl.addEventListener("click", () => {
this.deployUnits(this.spawnOptions, parseInt(this.#unitCountDropdown.getValue()));
});
/* Assemble all components */
this.#container.append(this.#unitRoleTypeDropdown.getContainer(), unitLabelCountContainerEl, this.#unitLoadoutDropdown.getContainer(), this.#unitSpawnAltitudeSlider.getContainer() as HTMLElement,
this.#unitLoadoutPreviewEl, this.#advancedOptionsToggle, this.#advancedOptionsDiv, this.#unitInfoToggle, this.#unitInfoDiv, this.#deployUnitButtonEl);
/* Load the country codes from the public folder */
var xhr = new XMLHttpRequest();
xhr.open('GET', 'images/countries/codes.json', true);
xhr.responseType = 'json';
xhr.onload = () => {
var status = xhr.status;
if (status === 200)
this.#countryCodes = xhr.response;
else
console.error(`Error retrieving country codes`)
};
xhr.send();
/* Create the range circle previews */
this.#engagementCircle = new Circle(this.spawnOptions.latlng, { radius: 0, weight: 4, opacity: 0.8, fillOpacity: 0, dashArray: "4 8", interactive: false, bubblingMouseEvents: false });
this.#acquisitionCircle = new Circle(this.spawnOptions.latlng, { radius: 0, weight: 2, opacity: 0.8, fillOpacity: 0, dashArray: "8 12", interactive: false, bubblingMouseEvents: false });
/* Event listeners */
this.#container.addEventListener("unitRoleTypeChanged", () => {
/* Shown the unit label and the unit count dropdowns */
this.#unitLabelDropdown.show();
this.#unitCountDivider.classList.remove("hide");
this.#unitCountDropdown.show();
/* Hide all the other components */
this.#unitLoadoutDropdown.hide();
this.#unitSkillDropdown.hide();
this.#unitSpawnAltitudeSlider.hide();
this.#unitLoadoutPreviewEl.classList.add("hide");
this.#advancedOptionsDiv.classList.add("hide");
this.#unitInfoDiv.classList.add("hide");
this.#advancedOptionsText.classList.add("hide");
this.#advancedOptionsToggle.classList.add("hide");
this.#unitInfoText.classList.add("hide");
this.#unitInfoToggle.classList.add("hide");
/* Disable the spawn button */
this.#deployUnitButtonEl.disabled = true;
this.#unitLabelDropdown.reset();
this.#unitLoadoutListEl.replaceChildren();
this.#unitLoadoutDropdown.reset();
this.#unitImageEl.classList.toggle("hide", true);
this.#unitLiveryDropdown.reset();
/* Populate the labels dropdown from the database */
var blueprints: UnitBlueprint[] = [];
if (this.#orderByRole)
blueprints = this.#unitDatabase.getByRole(this.spawnOptions.roleType);
else
blueprints = this.#unitDatabase.getByType(this.spawnOptions.roleType);
/* Presort the elements by name in case any have equal labels */
blueprints = blueprints.sort((blueprintA: UnitBlueprint, blueprintB: UnitBlueprint) => {
if (blueprintA.name > blueprintA.name)
return 1;
else
return (blueprintB.name > blueprintA.name) ? -1 : 0;
});
this.#unitLabelDropdown.setOptions(blueprints.map((blueprint) => { return blueprint.name }), "string+number", blueprints.map((blueprint) => { return blueprint.label }));
/* Add the tags to the options */
var elements: HTMLElement[] = [];
for (let idx = 0; idx < this.#unitLabelDropdown.getOptionElements().length; idx++) {
let name = this.#unitLabelDropdown.getOptionsList()[idx];
let element = this.#unitLabelDropdown.getOptionElements()[idx] as HTMLElement;
let entry = this.#unitDatabase.getByName(name);
if (entry && entry.tags?.trim() !== "") {
element.querySelectorAll("button")[0]?.append(...(entry.tags?.split(",").map((tag: string) => {
tag = tag.trim();
let el = document.createElement("div");
el.classList.add("pill", `ol-tag`, `ol-tag-${tag.replace(/[\W_]+/g,"-")}`);
el.textContent = tag;
element.appendChild(el);
return el;
}) ?? []));
elements.push(element);
}
}
/* Request resizing */
this.#container.dispatchEvent(new Event("resize"));
/* Reset the spawn options */
this.spawnOptions.name = "";
this.spawnOptions.loadout = undefined;
this.spawnOptions.skill = "Excellent";
this.spawnOptions.liveryID = undefined;
this.#computeSpawnPoints();
})
this.#container.addEventListener("unitLabelChanged", () => {
/* If enabled, show the altitude slideer and loadouts section */
if (this.#showAltitudeSlider)
this.#unitSpawnAltitudeSlider.show();
if (this.#showLoadout) {
this.#unitLoadoutDropdown.show();
this.#unitLoadoutPreviewEl.classList.remove("hide");
}
if (this.#showSkill) {
this.#unitSkillDropdown.show();
}
/* Show the advanced options and unit info sections */
this.#advancedOptionsText.classList.remove("hide");
this.#advancedOptionsToggle.classList.remove("hide");
this.#advancedOptionsToggle.classList.remove("is-open");
this.#unitInfoText.classList.remove("hide");
this.#unitInfoToggle.classList.remove("hide");
this.#unitInfoToggle.classList.remove("is-open");
/* Enable the spawn button */
this.#deployUnitButtonEl.disabled = false;
/* If enabled, populate the loadout dropdown */
if (!this.#unitLoadoutDropdown.isHidden()) {
this.#unitLoadoutDropdown.setOptions(this.#unitDatabase.getLoadoutNamesByRole(this.spawnOptions.name, this.spawnOptions.roleType));
this.#unitLoadoutDropdown.selectValue(0);
}
if (!this.#unitSkillDropdown.isHidden()) {
const sortedOptions = ["Average", "Good", "High", "Excellent"];
this.#unitSkillDropdown.setOptions(sortedOptions, null);
this.#unitSkillDropdown.selectValue(4);
}
/* Get the unit data from the db */
var blueprint = this.#unitDatabase.getByName(this.spawnOptions.name);
/* Shown the unit silhouette */
this.#unitImageEl.src = `images/units/${blueprint?.filename}`;
this.#unitImageEl.classList.toggle("hide", !(blueprint?.filename !== undefined && blueprint?.filename !== ''));
/* Set the livery options */
this.#setUnitLiveryOptions();
/* Populate the description and abilities sections */
this.#descriptionDiv.replaceChildren();
this.#abilitiesDiv.replaceChildren();
if (blueprint?.description)
this.#descriptionDiv.textContent = blueprint.description;
if (blueprint?.abilities) {
var abilities = blueprint.abilities.split(",");
this.#abilitiesDiv.replaceChildren();
for (let ability of abilities) {
if (ability !== "") {
ability = ability.trimStart();
var div = document.createElement("div");
div.textContent = ability.charAt(0).toUpperCase() + ability.slice(1);
this.#abilitiesDiv.append(div);
div.classList.add("pill-light");
}
}
}
/* Show the range circles */
this.showCirclesPreviews();
/* Request resizing */
this.#container.dispatchEvent(new Event("resize"));
this.#computeSpawnPoints();
})
this.#container.addEventListener("unitLoadoutChanged", () => {
/* Update the loadout information */
var items = this.spawnOptions.loadout?.items.map((item: any) => { return `${item.quantity}x ${item.name}`; });
if (items != undefined) {
items.length == 0 ? items.push("Empty loadout") : "";
this.#unitLoadoutListEl.replaceChildren(
...items.map((item: any) => {
var div = document.createElement('div');
div.innerText = item;
return div;
})
);
}
this.#container.dispatchEvent(new Event("resize"));
})
this.#container.addEventListener("unitSkillChanged", () => {
})
this.#container.addEventListener("unitCountChanged", () => {
/* Recompute the spawn points */
this.#computeSpawnPoints();
})
this.#container.addEventListener("unitCountryChanged", () => {
/* Get the unit liveries by country */
this.#setUnitLiveryOptions();
})
this.#container.addEventListener("unitLiveryChanged", () => {
})
document.addEventListener('activeCoalitionChanged', () => {
/* If the coalition changed, update the circle previews to set the colours */
this.showCirclesPreviews();
});
}
abstract deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number): void;
getContainer() {
return this.#container;
}
getVisible() {
return !this.getContainer().classList.contains( "hide" );
}
reset() {
/* Disable the spawn button */
this.#deployUnitButtonEl.disabled = true;
/* Reset all the dropdowns */
this.#unitRoleTypeDropdown.reset();
this.#unitLabelDropdown.reset();
this.#unitLiveryDropdown.reset();
if (this.#orderByRole)
this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getRoles());
else
this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getTypes(this.unitTypeFilter));
/* Reset the contents of the div elements */
this.#unitLoadoutListEl.replaceChildren();
this.#unitLoadoutDropdown.reset();
this.#unitImageEl.classList.toggle("hide", true);
this.#descriptionDiv.replaceChildren();
this.#abilitiesDiv.replaceChildren();
/* Hide everything but the unit type dropdown */
this.#unitLabelDropdown.hide();
this.#unitCountDivider.classList.add("hide");
this.#unitCountDropdown.hide();
this.#unitLoadoutDropdown.hide();
this.#unitSkillDropdown.hide();
this.#unitSpawnAltitudeSlider.hide();
this.#unitLoadoutPreviewEl.classList.add("hide");
this.#advancedOptionsDiv.classList.add("hide");
this.#unitInfoDiv.classList.add("hide");
this.#advancedOptionsText.classList.add("hide");
this.#advancedOptionsToggle.classList.add("hide");
this.#unitInfoText.classList.add("hide");
this.#unitInfoToggle.classList.add("hide");
/* Get the countries and clear the circle previews */
this.setCountries();
this.clearCirclesPreviews();
/* Request resizing */
this.#container.dispatchEvent(new Event("resize"));
}
setCountries() {
/* Create the countries dropdown elements (with the little flags) */
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)) {
this.#unitCountryDropdown.forceValue(this.#getFormattedCountry(countries[0]));
this.#setUnitCountry(countries[0]);
}
}
showCirclesPreviews() {
this.clearCirclesPreviews();
if ( !this.showRangeCircles || this.spawnOptions.name === "" || !this.getVisible() ) {
return;
}
let acquisitionRange = this.#unitDatabase.getByName(this.spawnOptions.name)?.acquisitionRange ?? 0;
let engagementRange = this.#unitDatabase.getByName(this.spawnOptions.name)?.engagementRange ?? 0;
if ( acquisitionRange === 0 && engagementRange === 0 ) {
return;
}
this.#acquisitionCircle.setRadius(acquisitionRange);
this.#engagementCircle.setRadius(engagementRange);
this.#acquisitionCircle.setLatLng(this.spawnOptions.latlng);
this.#engagementCircle.setLatLng(this.spawnOptions.latlng);
switch (getApp().getActiveCoalition()) {
case "red":
this.#acquisitionCircle.options.color = "#D42121";
break;
case "blue":
this.#acquisitionCircle.options.color = "#017DC1";
break;
default:
this.#acquisitionCircle.options.color = "#111111"
break;
}
switch (getApp().getActiveCoalition()) {
case "red":
this.#engagementCircle.options.color = "#FF5858";
break;
case "blue":
this.#engagementCircle.options.color = "#3BB9FF";
break;
default:
this.#engagementCircle.options.color = "#CFD9E8"
break;
}
if (engagementRange > 0)
this.#engagementCircle.addTo(getApp().getMap());
if (acquisitionRange > 0)
this.#acquisitionCircle.addTo(getApp().getMap());
}
clearCirclesPreviews() {
this.#engagementCircle.removeFrom(getApp().getMap());
this.#acquisitionCircle.removeFrom(getApp().getMap());
}
setAirbase(airbase: Airbase | undefined) {
this.spawnOptions.airbase = airbase;
}
setLatLng(latlng: LatLng) {
this.spawnOptions.latlng = latlng;
this.showCirclesPreviews();
}
setMaxUnitCount(maxUnitCount: number) {
/* Create the unit count options */
this.#unitCountDropdown.setOptions( [...Array(maxUnitCount).keys()].map( n => (n+1).toString() ), "number");
this.#unitCountDropdown.selectValue(0);
}
getRoleTypeDrodown() {
return this.#unitRoleTypeDropdown;
}
getLabelDropdown() {
return this.#unitLabelDropdown;
}
getCountDropdown() {
return this.#unitCountDropdown;
}
getLoadoutDropdown() {
return this.#unitLoadoutDropdown;
}
getSkillDropdown() {
return this.#unitSkillDropdown;
}
getCountryDropdown() {
return this.#unitCountDropdown;
}
getLiveryDropdown() {
return this.#unitLiveryDropdown;
}
getLoadoutPreview() {
return this.#unitLoadoutPreviewEl;
}
getAltitudeSlider() {
return this.#unitSpawnAltitudeSlider;
}
setShowLoadout(showLoadout: boolean) {
this.#showLoadout = showLoadout;
}
setShowSkill(showSkill: boolean) {
this.#showSkill = showSkill;
}
setShowAltitudeSlider(showAltitudeSlider: boolean) {
this.#showAltitudeSlider = showAltitudeSlider;
}
#setUnitRoleType(roleType: string) {
this.spawnOptions.roleType = roleType;
this.#container.dispatchEvent(new Event("unitRoleTypeChanged"));
}
#setUnitName(name: string) {
if (name != null)
this.spawnOptions.name = name;
this.#container.dispatchEvent(new Event("unitLabelChanged"));
}
#setUnitLoadout(loadoutName: string) {
var loadout = this.#unitDatabase.getLoadoutByName(this.spawnOptions.name, loadoutName);
if (loadout)
this.spawnOptions.loadout = loadout;
this.#container.dispatchEvent(new Event("unitLoadoutChanged"));
}
#setUnitSkill(skill: string) {
this.spawnOptions.skill = skill;
this.#container.dispatchEvent(new Event("unitSkillChanged"));
}
#setUnitCount(count: string) {
this.spawnOptions.count = parseInt(count);
this.#container.dispatchEvent(new Event("unitCountChanged"));
}
#setUnitCountry(country: string) {
this.spawnOptions.country = country;
this.#container.dispatchEvent(new Event("unitCountryChanged"));
}
#setUnitLivery(liveryName: string) {
var liveries = this.#unitDatabase.getByName(this.spawnOptions.name)?.liveries;
if (liveryName === "Default Livery") {
this.spawnOptions.liveryID = "";
}
else {
if (liveries !== undefined) {
for (let liveryID in liveries)
if (liveries[liveryID].name === liveryName)
this.spawnOptions.liveryID = liveryID;
}
}
this.#container.dispatchEvent(new Event("unitLiveryChanged"));
}
#setUnitLiveryOptions() {
if (this.spawnOptions.name !== "" && this.spawnOptions.country !== "") {
var liveries = this.#unitDatabase.getLiveryNamesByName(this.spawnOptions.name);
var countryLiveries: string[] = ["Default Livery"];
liveries.forEach((livery: any) => {
var nationLiveryCodes = this.#countryCodes[this.spawnOptions.country].liveryCodes;
if (livery.countries === "All" || livery.countries.some((country: string) => { return nationLiveryCodes.includes(country) }))
countryLiveries.push(livery.name);
});
this.#unitLiveryDropdown.setOptions(countryLiveries);
this.#unitLiveryDropdown.selectValue(0);
}
}
#createCountryButtons(parent: Dropdown, countries: string[], callback: CallableFunction) {
return Object.values(countries).map((country: string) => {
var el = document.createElement("div");
var button = document.createElement("button");
button.classList.add("country-dropdown-element");
el.appendChild(button);
button.addEventListener("click", () => {
callback(country);
parent.forceValue(this.#getFormattedCountry(country));
parent.close();
});
if (this.#countryCodes[country] !== undefined) {
var code = this.#countryCodes[country].flagCode;
if (code !== undefined) {
var img = document.createElement("img");
img.src = `images/countries/${code.toLowerCase()}.svg`;
button.appendChild(img);
}
}
else {
console.log("Unknown country " + country);
}
var text = document.createElement("div");
text.innerText = this.#getFormattedCountry(country);
button.appendChild(text);
return el;
});
}
#getFormattedCountry(country: string) {
var formattedCountry = "";
if (this.#countryCodes[country] !== undefined && this.#countryCodes[country].displayName !== undefined)
formattedCountry = this.#countryCodes[country].displayName;
else
formattedCountry = country.charAt(0).toUpperCase() + country.slice(1).toLowerCase();
return formattedCountry;
}
#computeSpawnPoints() {
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 >= getApp().getMissionManager().getAvailableSpawnPoints();
}
}
}
export class AircraftSpawnMenu extends UnitSpawnMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID, aircraftDatabase, true);
this.setMaxUnitCount(4);
this.getAltitudeSlider().setMinMax(0, 50000);
this.getAltitudeSlider().setIncrement(500);
this.getAltitudeSlider().setValue(20000);
this.spawnOptions.altitude = ftToM(20000);
}
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
spawnOptions.coalition = getApp().getActiveCoalition();
if (spawnOptions) {
var unitTable: UnitSpawnTable = {
unitType: spawnOptions.name,
location: spawnOptions.latlng,
altitude: spawnOptions.altitude ? spawnOptions.altitude : 0,
loadout: spawnOptions.loadout ? spawnOptions.loadout.name : "",
liveryID: spawnOptions.liveryID ? spawnOptions.liveryID : "",
skill: spawnOptions.skill ? spawnOptions.skill : "Excellent" // Default to "Excellent" if skill is not set
};
var units = [];
for (let i = 1; i <= unitsCount; i++) {
units.push(unitTable);
}
getApp().getUnitsManager().spawnUnits("Aircraft", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
if (res.commandHash !== undefined)
getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash);
});
this.getContainer().dispatchEvent(new Event("hide"));
}
}
}
export class HelicopterSpawnMenu extends UnitSpawnMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID, helicopterDatabase, true);
this.setMaxUnitCount(4);
this.getAltitudeSlider().setMinMax(0, 10000);
this.getAltitudeSlider().setIncrement(100);
this.getAltitudeSlider().setValue(5000);
this.spawnOptions.altitude = ftToM(5000);
}
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
spawnOptions.coalition = getApp().getActiveCoalition();
if (spawnOptions) {
var unitTable: UnitSpawnTable = {
unitType: spawnOptions.name,
location: spawnOptions.latlng,
altitude: spawnOptions.altitude? spawnOptions.altitude: 0,
loadout: spawnOptions.loadout? spawnOptions.loadout.name: "",
liveryID: spawnOptions.liveryID? spawnOptions.liveryID: "",
skill: spawnOptions.skill ? spawnOptions.skill : "Excellent" // Default to "Excellent" if skill is not set
};
var units = [];
for (let i = 1; i < unitsCount + 1; i++) {
units.push(unitTable);
}
getApp().getUnitsManager().spawnUnits("Helicopter", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
if (res.commandHash !== undefined)
getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash);
});
this.getContainer().dispatchEvent(new Event("hide"));
}
}
}
export class GroundUnitSpawnMenu extends UnitSpawnMenu {
protected showRangeCircles: boolean = true;
protected unitTypeFilter = (unit:any) => {return !(GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.type))};
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID, groundUnitDatabase, false);
this.setMaxUnitCount(20);
this.setShowAltitudeSlider(false);
this.setShowLoadout(false);
this.getLoadoutPreview().classList.add("hide");
}
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
spawnOptions.coalition = getApp().getActiveCoalition();
if (spawnOptions) {
var unitTable: UnitSpawnTable = {
unitType: spawnOptions.name,
location: spawnOptions.latlng,
liveryID: spawnOptions.liveryID? spawnOptions.liveryID: "",
skill: spawnOptions.skill ? spawnOptions.skill : "High"
};
var units = [];
let initialLat = unitTable.location.lat;
let initialLng = unitTable.location.lng;
let rows = Math.floor(Math.sqrt(unitsCount))
for (let i = 0; i < unitsCount; i++) {
unitTable.location.lat = initialLat + i % rows * 0.0001;
unitTable.location.lng = initialLng + Math.floor(i / rows) * 0.0001;
units.push(JSON.parse(JSON.stringify(unitTable)));
}
getApp().getUnitsManager().spawnUnits("GroundUnit", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
if (res.commandHash !== undefined)
getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash);
});
this.getContainer().dispatchEvent(new Event("hide"));
}
}
}
export class AirDefenceUnitSpawnMenu extends GroundUnitSpawnMenu {
protected unitTypeFilter = (unit:any) => {return GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.type)};
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
this.setMaxUnitCount(4);
}
}
export class NavyUnitSpawnMenu extends UnitSpawnMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID, navyUnitDatabase, false);
this.setMaxUnitCount(4);
this.setShowAltitudeSlider(false);
this.setShowLoadout(false);
}
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
spawnOptions.coalition = getApp().getActiveCoalition();
if (spawnOptions) {
var unitTable: UnitSpawnTable = {
unitType: spawnOptions.name,
location: spawnOptions.latlng,
liveryID: spawnOptions.liveryID? spawnOptions.liveryID: "",
skill: spawnOptions.skill ? spawnOptions.skill : "High"
};
var units = [];
let initialLat = unitTable.location.lat;
let initialLng = unitTable.location.lng;
let rows = Math.floor(Math.sqrt(unitsCount))
for (let i = 0; i < unitsCount; i++) {
unitTable.location.lat = initialLat + i % rows * 0.005;
unitTable.location.lng = initialLng + Math.floor(i / rows) * 0.005;
units.push(JSON.parse(JSON.stringify(unitTable)));
}
getApp().getUnitsManager().spawnUnits("NavyUnit", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
if (res.commandHash !== undefined)
getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash);
});
this.getContainer().dispatchEvent(new Event("hide"));
}
}
}

View File

@ -1,19 +0,0 @@
import { Panel } from "../panels/panel";
export class Dialog extends Panel {
constructor( element:string ) {
super( element );
}
hide() {
super.hide();
document.getElementById( "gray-out" )?.classList.toggle("hide", true);
}
show() {
super.show();
document.getElementById( "gray-out" )?.classList.toggle("hide", false);
}
}

View File

@ -1,37 +0,0 @@
interface CustomEventMap {
"unitSelection": CustomEvent<Unit>,
"unitDeselection": CustomEvent<Unit>,
"unitsSelection": CustomEvent<Unit[]>,
"unitsDeselection": CustomEvent<Unit[]>,
"clearSelection": CustomEvent<>,
"unitCreation": CustomEvent<Unit>,
"unitDeletion": CustomEvent<Unit>,
"unitDeath": CustomEvent<Unit>,
"unitUpdated": CustomEvent<Unit>,
"unitMoveCommand": CustomEvent<Unit>,
"unitAttackCommand": CustomEvent<Unit>,
"unitLandCommand": CustomEvent<Unit>,
"unitSetAltitudeCommand": CustomEvent<Unit>,
"unitSetSpeedCommand": CustomEvent<Unit>,
"unitSetOption": CustomEvent<Unit>,
"groupCreation": CustomEvent<Unit[]>,
"groupDeletion": CustomEvent<Unit[]>,
"mapStateChanged": CustomEvent<string>,
"mapContextMenu": CustomEvent<>,
"mapOptionsChanged": CustomEvent<>,
"commandModeOptionsChanged": CustomEvent<>,
"contactsUpdated": CustomEvent<Unit>,
"activeCoalitionChanged": CustomEvent<>
}
declare global {
interface Document {
addEventListener<K extends keyof CustomEventMap>(type: K,
listener: (this: Document, ev: CustomEventMap[K]) => void): void;
dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void;
}
function getOlympusPlugin(): OlympusPlugin;
}
export { };

View File

@ -1,14 +0,0 @@
import { OlympusApp } from "./olympusapp";
var app: OlympusApp;
function setup() {
app = new OlympusApp();
app.start();
}
export function getApp() {
return app;
}
window.onload = setup;

View File

@ -1,299 +0,0 @@
import { LatLng } from "leaflet";
import { OlympusApp } from "./olympusapp";
import { Airbase } from "./mission/airbase";
export interface OlympusPlugin {
getName: () => string;
initialize: (app: OlympusApp) => boolean;
}
declare global {
function getOlympusPlugin(): OlympusPlugin;
}
export interface ContextMenuOption {
tooltip: string;
src: string;
callback: CallableFunction;
}
export interface AirbasesData {
airbases: { [key: string]: any },
sessionHash: string;
time: number;
}
export interface BullseyesData {
bullseyes: { [key: string]: { latitude: number, longitude: number, coalition: string } },
sessionHash: string;
time: number;
}
export interface MissionData {
mission: {
theatre: string,
dateAndTime: DateAndTime;
commandModeOptions: CommandModeOptions;
coalitions: { red: string[], blue: string[] };
}
time: number;
sessionHash: string;
}
export interface CommandModeOptions {
commandMode: string;
restrictSpawns: boolean;
restrictToCoalition: boolean;
setupTime: number;
spawnPoints: {
red: number,
blue: number
},
eras: string[]
}
export interface DateAndTime {
date: { Year: number, Month: number, Day: number };
time: { h: number, m: number, s: number };
elapsedTime: number;
startTime: number;
}
export interface LogData {
logs: { [key: string]: string },
sessionHash: string;
time: number;
}
export interface ServerRequestOptions {
time?: number;
commandHash?: string;
}
export interface UnitSpawnTable {
unitType: string,
location: LatLng,
altitude?: number,
loadout?: string,
skill: string,
liveryID: string
}
export interface ObjectIconOptions {
showState: boolean,
showVvi: boolean,
showHealth: boolean,
showHotgroup: boolean,
showUnitIcon: boolean,
showShortLabel: boolean,
showFuel: boolean,
showAmmo: boolean,
showSummary: boolean,
showCallsign: boolean,
rotateToHeading: boolean
}
export interface GeneralSettings {
prohibitJettison: boolean;
prohibitAA: boolean;
prohibitAG: boolean;
prohibitAfterburner: boolean;
prohibitAirWpn: boolean;
}
export interface TACAN {
isOn: boolean;
channel: number;
XY: string;
callsign: string;
}
export interface Radio {
frequency: number;
callsign: number;
callsignNumber: number;
}
export interface Ammo {
quantity: number,
name: string,
guidance: number,
category: number,
missileCategory: number
}
export interface Contact {
ID: number,
detectionMethod: number
}
export interface Offset {
x: number,
y: number,
z: number
}
export interface UnitData {
category: string,
categoryDisplayName: 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;
horizontalVelocity: number;
verticalVelocity: number;
heading: number;
track: number;
isActiveTanker: boolean;
isActiveAWACS: 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;
operateAs: string;
shotsScatter: number;
shotsIntensity: number;
health: number;
}
export interface LoadoutItemBlueprint {
name: string;
quantity: number;
effectiveAgainst?: string;
}
export interface LoadoutBlueprint {
fuel: number;
items: LoadoutItemBlueprint[];
roles: string[];
code: string;
name: string;
enabled: boolean;
}
export interface UnitBlueprint {
name: string;
enabled: boolean;
coalition: string;
era: string;
label: string;
shortLabel: string;
type?: string;
loadouts?: LoadoutBlueprint[];
filename?: string;
liveries?: { [key: string]: { name: string, countries: string[] } };
cost?: number;
barrelHeight?: number;
muzzleVelocity?: number;
aimTime?: number;
shotsToFire?: number;
shotsBaseInterval?: number;
shotsBaseScatter?: number;
description?: string;
abilities?: string;
tags?: string;
acquisitionRange?: number;
engagementRange?: number;
targetingRange?: number;
aimMethodRange?: number;
alertnessTimeConstant?: number;
canTargetPoint?: boolean;
canRearm?: boolean;
canAAA?: boolean;
indirectFire?: boolean;
markerFile?: string;
unitWhenGrouped?: string;
}
export interface UnitSpawnOptions {
roleType: string;
name: string;
latlng: LatLng;
coalition: string;
count: number;
country: string;
skill: string;
loadout: LoadoutBlueprint | undefined;
airbase: Airbase | undefined;
liveryID: string | undefined;
altitude: number | undefined;
}
export interface AirbaseOptions {
name: string,
position: L.LatLng
}
export interface AirbaseChartData {
elevation: string,
ICAO: string,
TACAN: string,
runways: AirbaseChartRunwayData[]
}
export interface AirbaseChartRunwayHeadingData {
[index: string]: {
magHeading: string,
ILS: string
}
}
export interface AirbaseChartRunwayData {
headings: AirbaseChartRunwayHeadingData[],
length: string
}
export interface Listener {
callback: CallableFunction;
name?: string
}
export interface ShortcutOptions {
altKey?: boolean;
callback: CallableFunction;
context?: string;
ctrlKey?: boolean;
name?: string;
shiftKey?: boolean;
}
export interface ShortcutKeyboardOptions extends ShortcutOptions {
code: string;
event?: "keydown" | "keyup";
}
export interface ShortcutMouseOptions extends ShortcutOptions {
button: number;
event: "mousedown" | "mouseup";
}
export interface Manager {
add: CallableFunction;
}

View File

@ -1,136 +0,0 @@
import { Map } from 'leaflet';
import { Handler } from 'leaflet';
import { Util } from 'leaflet';
import { DomUtil } from 'leaflet';
import { DomEvent } from 'leaflet';
import { LatLngBounds } from 'leaflet';
import { Bounds } from 'leaflet';
export var BoxSelect = Handler.extend({
initialize: function (map: Map) {
this._map = map;
this._container = map.getContainer();
this._pane = map.getPanes().overlayPane;
this._resetStateTimeout = 0;
map.on('unload', this._destroy, this);
},
addHooks: function () {
DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
},
removeHooks: function () {
DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
},
moved: function () {
return this._moved;
},
_destroy: function () {
DomUtil.remove(this._pane);
delete this._pane;
},
_resetState: function () {
this._resetStateTimeout = 0;
this._moved = false;
},
_clearDeferredResetState: function () {
if (this._resetStateTimeout !== 0) {
clearTimeout(this._resetStateTimeout);
this._resetStateTimeout = 0;
}
},
_onMouseDown: function (e: any) {
if ((e.which == 1 && e.button == 0 && e.shiftKey)) {
this._map.fire('selectionstart');
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState();
this._resetState();
DomUtil.disableTextSelection();
DomUtil.disableImageDrag();
this._startPoint = this._map.mouseEventToContainerPoint(e);
//@ts-ignore
DomEvent.on(document, {
contextmenu: DomEvent.stop,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp,
keydown: this._onKeyDown
}, this);
} else {
return false;
}
},
_onMouseMove: function (e: any) {
if (!this._moved) {
this._moved = true;
this._box = DomUtil.create('div', 'leaflet-zoom-box', this._container);
DomUtil.addClass(this._container, 'leaflet-crosshair');
this._map.fire('boxzoomstart');
}
this._point = this._map.mouseEventToContainerPoint(e);
var bounds = new Bounds(this._point, this._startPoint),
size = bounds.getSize();
if (bounds.min != undefined)
DomUtil.setPosition(this._box, bounds.min);
this._box.style.width = size.x + 'px';
this._box.style.height = size.y + 'px';
},
_finish: function () {
if (this._moved) {
DomUtil.remove(this._box);
DomUtil.removeClass(this._container, 'leaflet-crosshair');
}
DomUtil.enableTextSelection();
DomUtil.enableImageDrag();
//@ts-ignore
DomEvent.off(document, {
contextmenu: DomEvent.stop,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp,
keydown: this._onKeyDown
}, this);
},
_onMouseUp: function (e: any) {
if ((e.which !== 1) && (e.button !== 0)) { return; }
this._finish();
if (!this._moved) { return; }
// Postpone to next JS tick so internal click event handling
// still see it as "moved".
window.setTimeout(Util.bind(this._resetState, this), 0);
var bounds = new LatLngBounds(
this._map.containerPointToLatLng(this._startPoint),
this._map.containerPointToLatLng(this._point));
this._map.fire('selectionend', { selectionBounds: bounds });
},
_onKeyDown: function (e: any) {
if (e.keyCode === 27) {
this._finish();
this._clearDeferredResetState();
this._resetState();
}
}
});

View File

@ -1,12 +0,0 @@
import { MiniMap, MiniMapOptions } from "leaflet-control-mini-map";
export class ClickableMiniMap extends MiniMap {
constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) {
super(layer, options);
}
getMap() {
//@ts-ignore needed to access not exported member. A bit of a hack, required to access click events //TODO: fix me
return this._miniMap;
}
}

View File

@ -1,166 +0,0 @@
import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet";
import { getApp } from "../..";
import { CoalitionAreaHandle } from "./coalitionareahandle";
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants";
export class CoalitionArea extends Polygon {
#coalition: string = "blue";
#selected: boolean = true;
#editing: boolean = true;
#handles: CoalitionAreaHandle[] = [];
#middleHandles: CoalitionAreaMiddleHandle[] = [];
#activeIndex: number = 0;
constructor(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][], options?: PolylineOptions) {
if (options === undefined)
options = {};
options.bubblingMouseEvents = false;
options.interactive = false;
super(latlngs, options);
this.#setColors();
this.#registerCallbacks();
if ([BLUE_COMMANDER, RED_COMMANDER].includes(getApp().getMissionManager().getCommandModeOptions().commandMode))
this.setCoalition(getApp().getMissionManager().getCommandedCoalition());
}
setCoalition(coalition: string) {
this.#coalition = coalition;
this.#setColors();
}
getCoalition() {
return this.#coalition;
}
setSelected(selected: boolean) {
this.#selected = selected;
this.#setColors();
this.#setHandles();
this.setOpacity(selected? 1: 0.5);
if (!this.getSelected() && this.getEditing()) {
/* Remove the vertex we were working on */
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.splice(this.#activeIndex, 1);
this.setLatLngs(latlngs);
this.setEditing(false);
}
}
getSelected() {
return this.#selected;
}
setEditing(editing: boolean) {
this.#editing = editing;
this.#setHandles();
var latlngs = this.getLatLngs()[0] as LatLng[];
/* Remove areas with less than 2 vertexes */
if (latlngs.length <= 2)
getApp().getMap().deleteCoalitionArea(this);
}
getEditing() {
return this.#editing;
}
addTemporaryLatLng(latlng: LatLng) {
this.#activeIndex++;
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.splice(this.#activeIndex, 0, latlng);
this.setLatLngs(latlngs);
this.#setHandles();
}
moveActiveVertex(latlng: LatLng) {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs[this.#activeIndex] = latlng;
this.setLatLngs(latlngs);
this.#setHandles();
}
setOpacity(opacity: number) {
this.setStyle({opacity: opacity, fillOpacity: opacity * 0.25});
}
onRemove(map: Map): this {
super.onRemove(map);
this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getApp().getMap()));
return this;
}
#setColors() {
const coalitionColor = this.getCoalition() === "blue" ? "#247be2" : "#ff5858";
this.setStyle({ color: this.getSelected() ? "white" : coalitionColor, fillColor: coalitionColor });
}
#setHandles() {
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(getApp().getMap());
handle.on("drag", (e: any) => {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs[idx] = e.target.getLatLng();
this.setLatLngs(latlngs);
this.#setMiddleHandles();
});
this.#handles.push(handle);
});
}
this.#setMiddleHandles();
}
#setMiddleHandles() {
this.#middleHandles.forEach((handle: CoalitionAreaMiddleHandle) => handle.removeFrom(getApp().getMap()));
this.#middleHandles = [];
var latlngs = this.getLatLngs()[0] as LatLng[];
if (this.getSelected() && latlngs.length >= 2) {
var lastLatLng: LatLng | null = null;
latlngs.concat([latlngs[0]]).forEach((latlng: LatLng, idx: number) => {
/* Add the polygon middle point handle (for adding new vertexes) */
if (lastLatLng != null) {
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 = getApp().getMap().layerPointToLatLng(middlePoint);
const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng);
middleHandle.addTo(getApp().getMap());
middleHandle.on("click", (e: any) => {
this.#activeIndex = idx - 1;
this.addTemporaryLatLng(middleLatLng);
});
this.#middleHandles.push(middleHandle);
}
lastLatLng = latlng;
});
}
}
#registerCallbacks() {
this.on("click", (e: any) => {
getApp().getMap().deselectAllCoalitionAreas();
if (!this.getSelected()) {
this.setSelected(true);
}
});
this.on("contextmenu", (e: any) => {
if (!this.getEditing()) {
getApp().getMap().deselectAllCoalitionAreas();
this.setSelected(true);
}
else
this.setEditing(false);
});
}
}

View File

@ -1,19 +0,0 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker";
export class CoalitionAreaHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, {interactive: true, draggable: true});
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [24, 24],
iconAnchor: [12, 12],
className: "leaflet-coalitionarea-handle-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-coalitionarea-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@ -1,19 +0,0 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker";
export class CoalitionAreaMiddleHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, {interactive: true, draggable: false});
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [16, 16],
iconAnchor: [8, 8],
className: "leaflet-coalitionarea-middle-handle-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-coalitionarea-middle-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@ -1,20 +0,0 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker";
export class DrawingCursor extends CustomMarker {
constructor() {
super(new LatLng(0, 0), {interactive: false})
this.setZIndexOffset(9999);
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [24, 24],
iconAnchor: [0, 24],
className: "leaflet-draw-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-draw-icon");
this.getElement()?.appendChild(el);
}
}

View File

@ -1,49 +0,0 @@
import * as L from "leaflet"
export class DCSLayer extends L.TileLayer {
createTile(coords: L.Coords, done: L.DoneCallback) {
let newDone = (error?: Error, tile?: HTMLElement) => {
if (error === null && tile !== undefined && !tile.classList.contains('filtered')) {
// Create a canvas and set its width and height.
var canvas = document.createElement('canvas');
canvas.setAttribute('width', '256px');
canvas.setAttribute('height', '256px');
// Get the canvas drawing context, and draw the image to it.
var context = canvas.getContext('2d');
if (context) {
context.drawImage(tile as CanvasImageSource, 0, 0, canvas.width, canvas.height);
// Get the canvas image data.
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
// Create a function for preserving a specified colour.
var makeTransparent = function(imageData: ImageData, color: {r: number, g: number, b: number}) {
// Get the pixel data from the source.
var data = imageData.data;
// Iterate through all the pixels.
for (var i = 0; i < data.length; i += 4) {
// Check if the current pixel should have preserved transparency. This simply compares whether the color we passed in is equivalent to our pixel data.
var convert = data[i] > color.r - 5 && data[i] < color.r + 5
&& data[i + 1] > color.g - 5 && data[i + 1] < color.g + 5
&& data[i + 2] > color.b - 5 && data[i + 2] < color.b + 5;
// Either preserve the initial transparency or set the transparency to 0.
data[i + 3] = convert ? 100: data[i + 3];
}
return imageData;
};
// Get the new pixel data and set it to the canvas context.
var newData = makeTransparent(imageData, {r: 26, g: 109, b: 127});
context.putImageData(newData, 0, 0);
(tile as HTMLImageElement).src = canvas.toDataURL();
tile.classList.add('filtered');
}
} else {
return done(error, tile);
}
}
return super.createTile(coords, newDone);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +0,0 @@
import { DivIcon, Map, Marker } from "leaflet";
import { MarkerOptions } from "leaflet";
import { LatLngExpression } from "leaflet";
export class CustomMarker extends Marker {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
}
onAdd(map: Map): this {
this.setIcon(new DivIcon()); // Default empty icon
super.onAdd(map);
this.createIcon();
return this;
}
onRemove(map: Map): this {
super.onRemove(map);
return this;
}
createIcon() {
/* Overloaded by child classes */
}
}

View File

@ -1,19 +0,0 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker";
export class DestinationPreviewHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, {interactive: true, draggable: true});
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [18, 18],
iconAnchor: [9, 9],
className: "leaflet-destination-preview-handle-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-destination-preview-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@ -1,20 +0,0 @@
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
export class DestinationPreviewMarker extends CustomMarker {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 26],
className: "leaflet-destination-preview",
}));
var el = document.createElement("div");
el.classList.add("ol-destination-preview-icon");
this.getElement()?.appendChild(el);
}
}

View File

@ -1,31 +0,0 @@
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
import { SVGInjector } from "@tanem/svg-injector";
import { getApp } from "../..";
export class SmokeMarker extends CustomMarker {
#color: string;
constructor(latlng: LatLngExpression, color: string, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
this.#color = color;
window.setTimeout(() => { this.removeFrom(getApp().getMap()); }, 300000) /* Remove the smoke after 5 minutes */
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 52],
className: "leaflet-smoke-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-smoke-icon");
el.setAttribute("data-color", this.#color);
var img = document.createElement("img");
img.src = "/resources/theme/images/markers/smoke.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);
}
}

View File

@ -1,20 +0,0 @@
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
export class TargetMarker extends CustomMarker {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 26],
className: "leaflet-target-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-target-icon");
this.getElement()?.appendChild(el);
}
}

View File

@ -1,76 +0,0 @@
import { CustomMarker } from "./custommarker";
import { DivIcon, LatLng } from "leaflet";
import { SVGInjector } from "@tanem/svg-injector";
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../../other/utils";
import { getApp } from "../..";
export class TemporaryUnitMarker extends CustomMarker {
#name: string;
#coalition: string;
#commandHash: string|undefined = undefined;
#timer: number = 0;
constructor(latlng: LatLng, name: string, coalition: string, commandHash?: string) {
super(latlng, {interactive: false});
this.#name = name;
this.#coalition = coalition;
this.#commandHash = commandHash;
if (commandHash !== undefined)
this.setCommandHash(commandHash)
}
setCommandHash(commandHash: string) {
this.#commandHash = commandHash;
this.#timer = window.setInterval(() => {
if (this.#commandHash !== undefined) {
getApp().getServerManager().isCommandExecuted((res: any) => {
if (res.commandExecuted) {
this.removeFrom(getApp().getMap());
window.clearInterval(this.#timer);
}
}, this.#commandHash)
}
}, 1000);
}
createIcon() {
const category = getMarkerCategoryByName(this.#name);
const databaseEntry = getUnitDatabaseByCategory(category)?.getByName(this.#name);
/* Set the icon */
var icon = new DivIcon({
className: 'leaflet-unit-icon',
iconAnchor: [25, 25],
iconSize: [50, 50],
});
this.setIcon(icon);
var el = document.createElement("div");
el.classList.add("unit");
el.setAttribute("data-object", `unit-${category}`);
el.setAttribute("data-coalition", this.#coalition);
// Main icon
var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon");
var img = document.createElement("img");
img.src = `/resources/theme/images/units/${databaseEntry?.markerFile ?? category}.svg`;
img.onload = () => SVGInjector(img);
unitIcon.appendChild(img);
unitIcon.toggleAttribute("data-rotate-to-heading", false);
el.append(unitIcon);
// Short label
if (category == "aircraft" || category == "helicopter") {
var shortLabel = document.createElement("div");
shortLabel.classList.add("unit-short-label");
shortLabel.innerText = databaseEntry?.shortLabel || "";
el.append(shortLabel);
}
this.getElement()?.appendChild(el);
this.getElement()?.classList.add("ol-temporary-marker");
}
}

View File

@ -1,56 +0,0 @@
// @ts-nocheck
// This is a horrible hack. But it is needed at the moment to ovveride a default behaviour of Leaflet. TODO please fix me the proper way.
import { Circle, Point, Polyline } from 'leaflet';
/**
* This custom Circle object implements a faster render method for very big circles. When zoomed in, the default ctx.arc method
* is very slow since the circle is huge. Also, when zoomed in most of the circle points will be outside the screen and not needed. This
* simpler, faster renderer approximates the circle with line segements and only draws those currently visibile.
* A more refined version using arcs could be implemented but this works good enough.
*/
export class RangeCircle extends Circle {
_updatePath() {
if (!this._renderer._drawing || this._empty()) { return; }
var p = this._point,
ctx = this._renderer._ctx,
r = Math.max(Math.round(this._radius), 1),
s = (Math.max(Math.round(this._radiusY), 1) || r) / r;
if (s !== 1) {
ctx.save();
ctx.scale(1, s);
}
let pathBegun = false;
let dtheta = Math.PI * 2 / 120;
for (let theta = 0; theta <= Math.PI * 2; theta += dtheta) {
let p1 = new Point(p.x + r * Math.cos(theta), p.y / s + r * Math.sin(theta));
let p2 = new Point(p.x + r * Math.cos(theta + dtheta), p.y / s + r * Math.sin(theta + dtheta));
let l1 = this._map.layerPointToLatLng(p1);
let l2 = this._map.layerPointToLatLng(p2);
let line = new Polyline([l1, l2]);
if (this._map.getBounds().intersects(line.getBounds())) {
if (!pathBegun) {
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
pathBegun = true;
}
ctx.lineTo(p2.x, p2.y);
}
else {
if (pathBegun) {
this._renderer._fillStroke(ctx, this);
pathBegun = false;
}
}
}
if (pathBegun)
this._renderer._fillStroke(ctx, this);
if (s !== 1)
ctx.restore();
}
}

View File

@ -1,136 +0,0 @@
import { Map, Point } from 'leaflet';
import { Handler } from 'leaflet';
import { Util } from 'leaflet';
import { DomUtil } from 'leaflet';
import { DomEvent } from 'leaflet';
import { LatLngBounds } from 'leaflet';
import { Bounds } from 'leaflet';
export var TouchBoxSelect = Handler.extend({
initialize: function (map: Map) {
this._map = map;
this._container = map.getContainer();
this._pane = map.getPanes().overlayPane;
this._resetStateTimeout = 0;
this._doubleClicked = false;
map.on('unload', this._destroy, this);
},
addHooks: function () {
DomEvent.on(this._container, 'touchstart', this._onMouseDown, this);
},
removeHooks: function () {
DomEvent.off(this._container, 'touchstart', this._onMouseDown, this);
},
moved: function () {
return this._moved;
},
_destroy: function () {
DomUtil.remove(this._pane);
delete this._pane;
},
_resetState: function () {
this._resetStateTimeout = 0;
this._moved = false;
},
_clearDeferredResetState: function () {
if (this._resetStateTimeout !== 0) {
clearTimeout(this._resetStateTimeout);
this._resetStateTimeout = 0;
}
},
_onMouseDown: function (e: any) {
if ((e.which == 0)) {
this._map.fire('selectionstart');
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState();
this._resetState();
DomUtil.disableTextSelection();
DomUtil.disableImageDrag();
this._startPoint = this._getMousePosition(e);
//@ts-ignore
DomEvent.on(document, {
contextmenu: DomEvent.stop,
touchmove: this._onMouseMove,
touchend: this._onMouseUp
}, this);
} else {
return false;
}
},
_onMouseMove: function (e: any) {
if (!this._moved) {
this._moved = true;
this._box = DomUtil.create('div', 'leaflet-zoom-box', this._container);
DomUtil.addClass(this._container, 'leaflet-crosshair');
}
this._point = this._getMousePosition(e);
var bounds = new Bounds(this._point, this._startPoint),
size = bounds.getSize();
if (bounds.min != undefined)
DomUtil.setPosition(this._box, bounds.min);
this._box.style.width = size.x + 'px';
this._box.style.height = size.y + 'px';
},
_finish: function () {
if (this._moved) {
DomUtil.remove(this._box);
DomUtil.removeClass(this._container, 'leaflet-crosshair');
}
DomUtil.enableTextSelection();
DomUtil.enableImageDrag();
//@ts-ignore
DomEvent.off(document, {
contextmenu: DomEvent.stop,
touchmove: this._onMouseMove,
touchend: this._onMouseUp
}, this);
},
_onMouseUp: function (e: any) {
if ((e.which !== 0)) { return; }
this._finish();
if (!this._moved) { return; }
// Postpone to next JS tick so internal click event handling
// still see it as "moved".
window.setTimeout(Util.bind(this._resetState, this), 0);
var bounds = new LatLngBounds(
this._map.containerPointToLatLng(this._startPoint),
this._map.containerPointToLatLng(this._point));
this._map.fire('selectionend', { selectionBounds: bounds });
},
_getMousePosition(e: any) {
var scale = DomUtil.getScale(this._container), offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
return new Point(
// offset.left/top values are in page scale (like clientX/Y),
// whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
(e.touches[0].clientX - offset.left) / scale.x - this._container.clientLeft,
(e.touches[0].clientY - offset.top) / scale.y - this._container.clientTop
);
}
});

View File

@ -1,96 +0,0 @@
import { DivIcon } from 'leaflet';
import { CustomMarker } from '../map/markers/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { AirbaseChartData, AirbaseOptions } from '../interfaces';
export class Airbase extends CustomMarker {
#name: string = "";
#chartData: AirbaseChartData = {
elevation: "",
ICAO: "",
TACAN: "",
runways: []
};
#coalition: string = "";
#hasChartDataBeenSet: boolean = false;
#properties: string[] = [];
#parkings: string[] = [];
constructor(options: AirbaseOptions) {
super(options.position, { riseOnHover: true });
this.#name = options.name;
}
chartDataHasBeenSet() {
return this.#hasChartDataBeenSet;
}
createIcon() {
var icon = new DivIcon({
className: 'leaflet-airbase-marker',
iconSize: [40, 40],
iconAnchor: [20, 20]
}); // Set the marker, className must be set to avoid white square
this.setIcon(icon);
var el = document.createElement("div");
el.classList.add("airbase-icon");
el.setAttribute("data-object", "airbase");
var img = document.createElement("img");
img.src = "/resources/theme/images/markers/airbase.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);
el.addEventListener( "mouseover", ( ev ) => {
document.dispatchEvent( new CustomEvent( "airbaseMouseover", { detail: this }));
});
el.addEventListener( "mouseout", ( ev ) => {
document.dispatchEvent( new CustomEvent( "airbaseMouseout", { detail: this }));
});
el.dataset.coalition = this.#coalition;
}
setCoalition(coalition: string) {
this.#coalition = coalition;
(<HTMLElement>this.getElement()?.querySelector(".airbase-icon")).dataset.coalition = this.#coalition;
}
getChartData() {
return this.#chartData;
}
getCoalition() {
return this.#coalition;
}
setName(name: string) {
this.#name = name;
}
getName() {
return this.#name;
}
setChartData(chartData: AirbaseChartData) {
this.#hasChartDataBeenSet = true;
this.#chartData = chartData;
}
setProperties(properties: string[]) {
this.#properties = properties;
}
getProperties() {
return this.#properties;
}
setParkings(parkings: string[]) {
this.#parkings = parkings;
}
getParkings() {
return this.#parkings;
}
}

View File

@ -1,36 +0,0 @@
import { DivIcon } from "leaflet";
import { CustomMarker } from "../map/markers/custommarker";
import { SVGInjector } from "@tanem/svg-injector";
export class Bullseye extends CustomMarker {
#coalition: string = "";
createIcon() {
var icon = new DivIcon({
className: 'leaflet-bullseye-marker',
iconSize: [40, 40],
iconAnchor: [20, 20]
}); // Set the marker, className must be set to avoid white square
this.setIcon(icon);
var el = document.createElement("div");
el.classList.add("bullseye-icon");
el.setAttribute("data-object", "bullseye");
var img = document.createElement("img");
img.src = "/resources/theme/images/markers/bullseye.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);
}
setCoalition(coalition: string)
{
this.#coalition = coalition;
(<HTMLElement> this.getElement()?.querySelector(".bullseye-icon")).dataset.coalition = this.#coalition;
}
getCoalition()
{
return this.#coalition;
}
}

View File

@ -1,326 +0,0 @@
import { LatLng } from "leaflet";
import { getApp } from "..";
import { Airbase } from "./airbase";
import { Bullseye } from "./bullseye";
import { BLUE_COMMANDER, ERAS, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
import { Dropdown } from "../controls/dropdown";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
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";
import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionData } from "../interfaces";
/** The MissionManager */
export class MissionManager {
#bullseyes: { [name: string]: Bullseye } = {};
#airbases: { [name: string]: Airbase } = {};
#theatre: string = "";
#dateAndTime: DateAndTime = {date: {Year: 0, Month: 0, Day: 0}, time: {h: 0, m: 0, s: 0}, startTime: 0, elapsedTime: 0};
#commandModeOptions: CommandModeOptions = {commandMode: NONE, restrictSpawns: false, restrictToCoalition: false, setupTime: Infinity, spawnPoints: {red: Infinity, blue: Infinity}, eras: []};
#remainingSetupTime: number = 0;
#spentSpawnPoint: number = 0;
#commandModeDialog: HTMLElement;
#commandModeErasDropdown: Dropdown;
#coalitions: {red: string[], blue: string[]} = {red: [], blue: []};
constructor() {
document.addEventListener("applycommandModeOptions", () => this.#applycommandModeOptions());
document.addEventListener("showCommandModeDialog", () => this.showCommandModeDialog());
document.addEventListener("toggleSpawnRestrictions", (ev:CustomEventInit) => {
this.#toggleSpawnRestrictions(ev.detail._element.checked)
});
/* command-mode settings dialog */
this.#commandModeDialog = document.querySelector("#command-mode-settings-dialog") as HTMLElement;
this.#commandModeErasDropdown = new Dropdown("command-mode-era-options", () => {});
}
/** Update location of bullseyes
*
* @param object <BulleyesData>
*/
updateBullseyes(data: BullseyesData) {
const commandMode = getApp().getMissionManager().getCommandModeOptions().commandMode;
for (let idx in data.bullseyes) {
const bullseye = data.bullseyes[idx];
// Prevent Red and Blue coalitions seeing each other's bulleye(s)
if ((bullseye.coalition === "red" && commandMode === BLUE_COMMANDER)
|| (bullseye.coalition === "blue" && commandMode === RED_COMMANDER)) {
continue;
}
if (!(idx in this.#bullseyes))
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));
this.#bullseyes[idx].setCoalition(bullseye.coalition);
}
}
}
/** Update airbase information
*
* @param object <AirbasesData>
*/
updateAirbases(data: AirbasesData) {
for (let idx in data.airbases) {
var airbase = data.airbases[idx]
if (this.#airbases[airbase.callsign] === undefined && airbase.callsign != '') {
this.#airbases[airbase.callsign] = new Airbase({
position: new LatLng(airbase.latitude, airbase.longitude),
name: airbase.callsign
}).addTo(getApp().getMap());
this.#airbases[airbase.callsign].on('contextmenu', (e) => this.#onAirbaseClick(e));
this.#loadAirbaseChartData(airbase.callsign);
}
if (this.#airbases[airbase.callsign] != undefined && airbase.latitude && airbase.longitude && airbase.coalition) {
this.#airbases[airbase.callsign].setLatLng(new LatLng(airbase.latitude, airbase.longitude));
this.#airbases[airbase.callsign].setCoalition(airbase.coalition);
}
}
}
/** Update mission information
*
* @param object <MissionData>
*/
updateMission(data: MissionData) {
if (data.mission) {
/* Set the mission theatre */
if (data.mission.theatre != this.#theatre) {
this.#theatre = data.mission.theatre;
getApp().getMap().setTheatre(this.#theatre);
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Map set to " + this.#theatre);
}
/* Set the date and time data */
this.#dateAndTime = data.mission.dateAndTime;
data.mission.dateAndTime.time.s -= 1; // ED has seconds 1-60 and not 0-59?!
/* Set the coalition countries */
this.#coalitions = data.mission.coalitions;
/* Set the command mode options */
this.#setcommandModeOptions(data.mission.commandModeOptions);
this.#remainingSetupTime = this.getCommandModeOptions().setupTime - this.getDateAndTime().elapsedTime;
var commandModePhaseEl = document.querySelector("#command-mode-phase") as HTMLElement;
if (commandModePhaseEl) {
if (this.#remainingSetupTime > 0) {
var remainingTime = `-${new Date(this.#remainingSetupTime * 1000).toISOString().substring(14, 19)}`;
commandModePhaseEl.dataset.remainingTime = remainingTime;
}
commandModePhaseEl.classList.toggle("setup-phase", this.#remainingSetupTime > 0 && this.getCommandModeOptions().restrictSpawns);
//commandModePhaseEl.classList.toggle("game-commenced", this.#remainingSetupTime <= 0 || !this.getCommandModeOptions().restrictSpawns);
//commandModePhaseEl.classList.toggle("no-restrictions", !this.getCommandModeOptions().restrictSpawns);
}
}
}
/** Get the bullseyes set in this theatre
*
* @returns object
*/
getBullseyes() {
return this.#bullseyes;
}
/** Get the airbases in this theatre
*
* @returns object
*/
getAirbases() {
return this.#airbases;
}
/** Get the options/settings as set in the command mode
*
* @returns object
*/
getCommandModeOptions() {
return this.#commandModeOptions;
}
/** Get the current date and time of the mission (based on local time)
*
* @returns object
*/
getDateAndTime() {
return this.#dateAndTime;
}
/**
* Get the number of seconds left of setup time
* @returns number
*/
getRemainingSetupTime() {
return this.#remainingSetupTime;
}
/** Get an object with the coalitions in it
*
* @returns object
*/
getCoalitions() {
return this.#coalitions;
}
/** Get the current theatre (map) name
*
* @returns string
*/
getTheatre() {
return this.#theatre;
}
getAvailableSpawnPoints() {
if (this.getCommandModeOptions().commandMode === GAME_MASTER)
return Infinity;
else if (this.getCommandModeOptions().commandMode === BLUE_COMMANDER)
return this.getCommandModeOptions().spawnPoints.blue - this.#spentSpawnPoint;
else if (this.getCommandModeOptions().commandMode === RED_COMMANDER)
return this.getCommandModeOptions().spawnPoints.red - this.#spentSpawnPoint;
else
return 0;
}
getCommandedCoalition() {
if (this.getCommandModeOptions().commandMode === BLUE_COMMANDER)
return "blue";
else if (this.getCommandModeOptions().commandMode === RED_COMMANDER)
return "red";
else
return "all";
}
refreshSpawnPoints() {
var spawnPointsEl = document.querySelector("#spawn-points");
if (spawnPointsEl) {
spawnPointsEl.textContent = `${this.getAvailableSpawnPoints()}`;
}
}
setSpentSpawnPoints(spawnPoints: number) {
this.#spentSpawnPoint = spawnPoints;
this.refreshSpawnPoints();
}
showCommandModeDialog() {
const options = this.getCommandModeOptions()
const { restrictSpawns, restrictToCoalition, setupTime } = options;
this.#toggleSpawnRestrictions(restrictSpawns);
/* Create the checkboxes to select the unit eras */
this.#commandModeErasDropdown.setOptionsElements(
ERAS.sort((eraA, eraB) => {
return ( eraA.chronologicalOrder > eraB.chronologicalOrder ) ? 1 : -1;
}).map((era) => {
return createCheckboxOption(era.name, `Enable ${era} units spawns`, this.getCommandModeOptions().eras.includes(era.name));
})
);
this.#commandModeDialog.classList.remove("hide");
const restrictSpawnsCheckbox = this.#commandModeDialog.querySelector("#restrict-spawns")?.querySelector("input") as HTMLInputElement;
const restrictToCoalitionCheckbox = this.#commandModeDialog.querySelector("#restrict-to-coalition")?.querySelector("input") as HTMLInputElement;
const blueSpawnPointsInput = this.#commandModeDialog.querySelector("#blue-spawn-points")?.querySelector("input") as HTMLInputElement;
const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement;
const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement;
restrictSpawnsCheckbox.checked = restrictSpawns;
restrictToCoalitionCheckbox.checked = restrictToCoalition;
blueSpawnPointsInput.value = String(options.spawnPoints.blue);
redSpawnPointsInput.value = String(options.spawnPoints.red);
setupTimeInput.value = String(Math.floor(setupTime / 60.0));
}
#applycommandModeOptions() {
this.#commandModeDialog.classList.add("hide");
const restrictSpawnsCheckbox = this.#commandModeDialog.querySelector("#restrict-spawns")?.querySelector("input") as HTMLInputElement;
const restrictToCoalitionCheckbox = this.#commandModeDialog.querySelector("#restrict-to-coalition")?.querySelector("input") as HTMLInputElement;
const blueSpawnPointsInput = this.#commandModeDialog.querySelector("#blue-spawn-points")?.querySelector("input") as HTMLInputElement;
const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement;
const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement;
var eras: string[] = [];
const enabledEras = getCheckboxOptions(this.#commandModeErasDropdown);
Object.keys(enabledEras).forEach((key: string) => {if (enabledEras[key]) eras.push(key)});
getApp().getServerManager().setCommandModeOptions(restrictSpawnsCheckbox.checked, restrictToCoalitionCheckbox.checked, {blue: parseInt(blueSpawnPointsInput.value), red: parseInt(redSpawnPointsInput.value)}, eras, parseInt(setupTimeInput.value) * 60);
}
#setcommandModeOptions(commandModeOptions: CommandModeOptions) {
/* Refresh all the data if we have exited the NONE state */
var requestRefresh = false;
if (this.#commandModeOptions.commandMode === NONE && commandModeOptions.commandMode !== NONE)
requestRefresh = true;
/* Refresh the page if we have lost Game Master priviledges */
if (this.#commandModeOptions.commandMode === GAME_MASTER && commandModeOptions.commandMode !== GAME_MASTER)
location.reload();
/* Check if any option has changed */
var commandModeOptionsChanged = (!commandModeOptions.eras.every((value: string, idx: number) => {return value === this.getCommandModeOptions().eras[idx]}) ||
commandModeOptions.spawnPoints.red !== this.getCommandModeOptions().spawnPoints.red ||
commandModeOptions.spawnPoints.blue !== this.getCommandModeOptions().spawnPoints.blue ||
commandModeOptions.restrictSpawns !== this.getCommandModeOptions().restrictSpawns ||
commandModeOptions.restrictToCoalition !== this.getCommandModeOptions().restrictToCoalition);
this.#commandModeOptions = commandModeOptions;
this.setSpentSpawnPoints(0);
this.refreshSpawnPoints();
if (commandModeOptionsChanged) {
document.dispatchEvent(new CustomEvent("commandModeOptionsChanged", { detail: this }));
document.getElementById("command-mode-toolbar")?.classList.remove("hide");
const el = document.getElementById("command-mode");
if (el) {
el.dataset.mode = commandModeOptions.commandMode;
el.textContent = commandModeOptions.commandMode.toUpperCase();
}
}
document.querySelector("#spawn-points-container")?.classList.toggle("hide", this.getCommandModeOptions().commandMode === GAME_MASTER || !this.getCommandModeOptions().restrictSpawns);
document.querySelector("#command-mode-settings-button")?.classList.toggle("hide", this.getCommandModeOptions().commandMode !== GAME_MASTER);
if (requestRefresh)
getApp().getServerManager().refreshAll();
}
#onAirbaseClick(e: any) {
getApp().getMap().showAirbaseContextMenu(e.sourceTarget, e.originalEvent.x, e.originalEvent.y, e.latlng);
}
#loadAirbaseChartData(callsign: string) {
if ( !this.#theatre ) {
return;
}
var xhr = new XMLHttpRequest();
xhr.open('GET', `api/airbases/${this.#theatre.toLowerCase()}/${callsign}`, true);
xhr.responseType = 'json';
xhr.onload = () => {
var status = xhr.status;
if (status === 200) {
const data = xhr.response;
this.getAirbases()[callsign].setChartData(data);
} else {
console.error(`Error retrieving data for ${callsign} airbase`)
}
};
xhr.send();
}
#toggleSpawnRestrictions(restrictionsEnabled:boolean) {
this.#commandModeDialog.querySelectorAll("input, label, .ol-select").forEach( el => {
if (!el.closest("#restrict-spawns")) el.toggleAttribute("disabled", !restrictionsEnabled);
});
}
}

View File

@ -1,485 +0,0 @@
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 { Manager } from "./other/manager";
import { SVGInjector } from "@tanem/svg-injector";
import { ServerManager } from "./server/servermanager";
import { sha256 } from 'js-sha256';
import { BLUE_COMMANDER, FILL_SELECTED_RING, GAME_MASTER, HIDE_UNITS_SHORT_RANGE_RINGS, RED_COMMANDER, SHOW_UNITS_ACQUISITION_RINGS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_LABELS } from "./constants/constants";
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 { UnitListPanel } from "./panels/unitlistpanel";
import { ContextManager } from "./context/contextmanager";
import { Context } from "./context/context";
var VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
export class OlympusApp {
/* Global data */
#activeCoalition: string = "blue";
#latestVersion: string|undefined = undefined;
#config: any = {};
/* Main leaflet map, extended by custom methods */
#map: Map | null = null;
/* Managers */
#contextManager!: ContextManager;
#dialogManager!: Manager;
#missionManager: MissionManager | null = null;
#panelsManager: Manager | null = null;
#pluginsManager: PluginsManager | null = null;
#popupsManager: Manager | null = null;
#serverManager: ServerManager | null = null;
#shortcutManager!: ShortcutManager;
#toolbarsManager: Manager | null = null;
#unitsManager: UnitsManager | null = null;
#weaponsManager: WeaponsManager | null = null;
constructor() {
}
// TODO add checks on null
getDialogManager() {
return this.#dialogManager as Manager;
}
getMap() {
return this.#map as Map;
}
getCurrentContext() {
return this.getContextManager().getCurrentContext() as Context;
}
getContextManager() {
return this.#contextManager as ContextManager;
}
getServerManager() {
return this.#serverManager as ServerManager;
}
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;
document.dispatchEvent(new CustomEvent("activeCoalitionChanged"));
}
}
/**
*
* @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";
}
}
/**
*
* @returns The aircraft database
*/
getAircraftDatabase() {
return aircraftDatabase;
}
/**
*
* @returns The helicopter database
*/
getHelicopterDatabase() {
return helicopterDatabase;
}
/**
*
* @returns The ground unit database
*/
getGroundUnitDatabase() {
return groundUnitDatabase;
}
/**
*
* @returns The navy unit database
*/
getNavyUnitDatabase() {
return navyUnitDatabase;
}
/** 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.#contextManager = new ContextManager();
this.#contextManager.add( "olympus", {} );
this.#map = new Map('map-container');
this.#missionManager = new MissionManager();
this.#panelsManager = new Manager();
this.#popupsManager = new Manager();
this.#serverManager = new ServerManager();
this.#shortcutManager = new ShortcutManager();
this.#toolbarsManager = new Manager();
this.#unitsManager = new UnitsManager();
this.#weaponsManager = new WeaponsManager();
// Toolbars
this.getToolbarsManager().add("primaryToolbar", new PrimaryToolbar("primary-toolbar"))
.add("commandModeToolbar", new CommandModeToolbar("command-mode-toolbar"));
// 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"))
.add("unitList", new UnitListPanel("unit-list-panel", "unit-list-panel-content"))
// Popups
this.getPopupsManager()
.add("infoPopup", new Popup("info-popup"));
this.#pluginsManager = new PluginsManager();
/* Set the address of the server */
this.getServerManager().setAddress(window.location.href.split('?')[0]);
/* Setup all global events */
this.#setupEvents();
/* Set the splash background image to a random image */
let splashScreen = document.getElementById("splash-screen") as HTMLElement;
let i = Math.round(Math.random() * 7 + 1);
new Promise((resolve, reject) => {
const image = new Image();
image.addEventListener('load', resolve);
image.addEventListener('error', resolve);
image.src = `/resources/theme/images/splash/${i}.jpg`;
}).then(() => {
splashScreen.style.backgroundImage = `url('/resources/theme/images/splash/${i}.jpg')`;
let loadingScreen = document.getElementById("loading-screen") as HTMLElement;
loadingScreen.classList.add("fade-out");
window.setInterval(() => { loadingScreen.classList.add("hide"); }, 1000);
})
/* Check if we are running the latest version */
const request = new Request("https://raw.githubusercontent.com/Pax1601/DCSOlympus/main/version.json");
fetch(request).then((response) => {
if (response.status === 200) {
return response.json();
} else {
throw new Error("Error connecting to Github to retrieve latest version");
}
}).then((res) => {
this.#latestVersion = res["version"];
const latestVersionSpan = document.getElementById("latest-version") as HTMLElement;
if (latestVersionSpan) {
latestVersionSpan.innerHTML = this.#latestVersion ?? "Unknown";
latestVersionSpan.classList.toggle("new-version", this.#latestVersion !== VERSION);
}
})
/* Load the config file from the server */
const configRequest = new Request(location.href + "resources/config");
fetch(configRequest).then((response) => {
if (response.status === 200) {
return response.json();
} else {
throw new Error("Error retrieving config file");
}
}).then((res) => {
this.#config = res;
document.dispatchEvent(new CustomEvent("configLoaded"));
})
}
#setupEvents() {
/* Generic clicks */
document.addEventListener("click", (ev) => {
if (ev instanceof MouseEvent && ev.target instanceof HTMLElement) {
const target = ev.target;
if (target.classList.contains("olympus-dialog-close")) {
target.closest("div.olympus-dialog")?.classList.add("hide");
}
const triggerElement = target.closest("[data-on-click]");
if (triggerElement instanceof HTMLElement) {
const eventName: string = triggerElement.dataset.onClick || "";
let params = JSON.parse(triggerElement.dataset.onClickParams || "{}");
params._element = triggerElement;
if (eventName) {
document.dispatchEvent(new CustomEvent(eventName, {
detail: params
}));
}
}
}
});
const shortcutManager = this.getShortcutManager();
shortcutManager.addKeyboardShortcut("togglePause", {
"altKey": false,
"callback": () => {
this.getServerManager().setPaused(!this.getServerManager().getPaused());
},
"code": "Space",
"context": "olympus",
"ctrlKey": false
}).addKeyboardShortcut("deselectAll", {
"callback": (ev: KeyboardEvent) => {
this.getUnitsManager().deselectAllUnits();
},
"code": "Escape",
"context": "olympus"
}).addKeyboardShortcut("toggleUnitLabels", {
"altKey": false,
"callback": () => {
const chk = document.querySelector(`label[title="${SHOW_UNIT_LABELS}"] input[type="checkbox"]`);
if (chk instanceof HTMLElement) {
chk.click();
}
},
"code": "KeyL",
"context": "olympus",
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("toggleAcquisitionRings", {
"altKey": false,
"callback": () => {
const chk = document.querySelector(`label[title="${SHOW_UNITS_ACQUISITION_RINGS}"] input[type="checkbox"]`);
if (chk instanceof HTMLElement) {
chk.click();
}
},
"code": "KeyE",
"context": "olympus",
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("toggleEngagementRings", {
"altKey": false,
"callback": () => {
const chk = document.querySelector(`label[title="${SHOW_UNITS_ENGAGEMENT_RINGS}"] input[type="checkbox"]`);
if (chk instanceof HTMLElement) {
chk.click();
}
},
"code": "KeyQ",
"context": "olympus",
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("toggleHideShortEngagementRings", {
"altKey": false,
"callback": () => {
const chk = document.querySelector(`label[title="${HIDE_UNITS_SHORT_RANGE_RINGS}"] input[type="checkbox"]`);
if (chk instanceof HTMLElement) {
chk.click();
}
},
"code": "KeyR",
"context": "olympus",
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("toggleFillEngagementRings", {
"altKey": false,
"callback": () => {
const chk = document.querySelector(`label[title="${FILL_SELECTED_RING}"] input[type="checkbox"]`);
if (chk instanceof HTMLElement) {
chk.click();
}
},
"code": "KeyF",
"context": "olympus",
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("increaseCameraZoom", {
"altKey": true,
"callback": () => {
this.getMap().increaseCameraZoom();
},
"code": "Equal",
"context": "olympus",
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("decreaseCameraZoom", {
"altKey": true,
"callback": () => {
this.getMap().decreaseCameraZoom();
},
"code": "Minus",
"context": "olympus",
"ctrlKey": false,
"shiftKey": false
});
["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => {
shortcutManager.addKeyboardShortcut(`pan${code}keydown`, {
"altKey": false,
"callback": (ev: KeyboardEvent) => {
this.getMap().handleMapPanning(ev);
},
"code": code,
"context": "olympus",
"ctrlKey": false,
"event": "keydown"
});
shortcutManager.addKeyboardShortcut(`pan${code}keyup`, {
"callback": (ev: KeyboardEvent) => {
this.getMap().handleMapPanning(ev);
},
"code": code,
"context": "olympus"
});
});
const digits = ["Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9"];
digits.forEach(code => {
shortcutManager.addKeyboardShortcut(`hotgroup${code}`, {
"altKey": false,
"callback": (ev: KeyboardEvent) => {
if (ev.ctrlKey && ev.shiftKey)
this.getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5)), false); // "Select hotgroup X in addition to any units already selected"
else if (ev.ctrlKey && !ev.shiftKey)
this.getUnitsManager().setHotgroup(parseInt(ev.code.substring(5))); // "These selected units are hotgroup X (forget any previous membership)"
else if (!ev.ctrlKey && ev.shiftKey)
this.getUnitsManager().addToHotgroup(parseInt(ev.code.substring(5))); // "Add (append) these units to hotgroup X (in addition to any existing members)"
else
this.getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5))); // "Select hotgroup X, deselect any units not in it."
},
"code": code
});
// Stop hotgroup controls sending the browser to another tab
document.addEventListener("keydown", (ev: KeyboardEvent) => {
if (ev.code === code && ev.ctrlKey === true && ev.altKey === false && ev.shiftKey === false) {
ev.preventDefault();
}
});
});
// TODO: move from here in dedicated class
document.addEventListener("closeDialog", (ev: CustomEventInit) => {
ev.detail._element.closest(".ol-dialog").classList.add("hide");
document.getElementById("gray-out")?.classList.toggle("hide", true);
});
/* Try and connect with the Olympus REST server */
const loginForm = document.getElementById("authentication-form");
if (loginForm instanceof HTMLFormElement) {
loginForm.addEventListener("submit", (ev:SubmitEvent) => {
ev.preventDefault();
ev.stopPropagation();
var hash = sha256.create();
const username = (loginForm.querySelector("#username") as HTMLInputElement).value;
const password = hash.update((loginForm.querySelector("#password") as HTMLInputElement).value).hex();
// Update the user credentials
this.getServerManager().setCredentials(username, password);
// Start periodically requesting updates
this.getServerManager().startUpdate();
this.setLoginStatus("connecting");
});
} else {
console.error("Unable to find login form.");
}
/* Reload the page, used to mimic a restart of the app */
document.addEventListener("reloadPage", () => {
location.reload();
})
/* Inject the svgs with the corresponding svg code. This allows to dynamically manipulate the svg, like changing colors */
document.querySelectorAll("[inject-svg]").forEach((el: Element) => {
var img = el as HTMLImageElement;
var isLoaded = img.complete;
if (isLoaded)
SVGInjector(img);
else
img.addEventListener("load", () => { SVGInjector(img); });
})
}
getConfig() {
return this.#config;
}
}

View File

@ -1,7 +0,0 @@
import { Manager } from "./manager";
export abstract class EventsManager extends Manager {
constructor() {
super();
}
}

View File

@ -1,37 +0,0 @@
import { Context } from "../context/context";
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()}.`);
}
if (this.#items.hasOwnProperty(name)) {
throw new Error(`Item with name "${name}" already exists.`);
}
this.#items[name] = item;
return this;
}
get(name: string) {
if (this.#items.hasOwnProperty(name)) {
return this.#items[name];
} else {
return false;
}
}
getAll() {
return this.#items;
}
}

View File

@ -1,582 +0,0 @@
import { LatLng, Point, Polygon } from "leaflet";
import * as turf from "@turf/turf";
import { UnitDatabase } from "../unit/databases/unitdatabase";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { Buffer } from "buffer";
import { GROUND_UNIT_AIR_DEFENCE_REGEX, ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
import { Dropdown } from "../controls/dropdown";
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
import { DateAndTime, UnitBlueprint } from "../interfaces";
import { Slider } from "../controls/slider";
// comment
const usng = require("usng.js");
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
const φ1 = deg2rad(lat1); // φ, λ in radians
const φ2 = deg2rad(lat2);
const λ1 = deg2rad(lon1); // φ, λ in radians
const λ2 = deg2rad(lon2);
const y = Math.sin(λ2 - λ1) * Math.cos(φ2);
const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(λ2 - λ1);
const θ = Math.atan2(y, x);
const brng = (rad2deg(θ) + 360) % 360; // in degrees
return brng;
}
export function distance(lat1: number, lon1: number, lat2: number, lon2: number) {
const R = 6371e3; // metres
const φ1 = deg2rad(lat1); // φ, λ in radians
const φ2 = deg2rad(lat2);
const Δφ = deg2rad(lat2 - lat1);
const Δλ = deg2rad(lon2 - lon1);
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const d = R * c; // in metres
return d;
}
export function bearingAndDistanceToLatLng(lat: number, lon: number, brng: number, dist: number) {
const R = 6371e3; // metres
const φ1 = deg2rad(lat); // φ, λ in radians
const λ1 = deg2rad(lon);
const φ2 = Math.asin(Math.sin(φ1) * Math.cos(dist / R) + Math.cos(φ1) * Math.sin(dist / R) * Math.cos(brng));
const λ2 = λ1 + Math.atan2(Math.sin(brng) * Math.sin(dist / R) * Math.cos(φ1), Math.cos(dist / R) - Math.sin(φ1) * Math.sin(φ2));
return new LatLng(rad2deg(φ2), rad2deg(λ2));
}
export function ConvertDDToDMS(D: number, lng: boolean) {
var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N";
var deg = 0 | (D < 0 ? (D = -D) : D);
var min = 0 | (((D += 1e-9) % 1) * 60);
var sec = (0 | (((D * 60) % 1) * 6000)) / 100;
var dec = Math.round((sec - Math.floor(sec)) * 100);
var sec = Math.floor(sec);
if (lng)
return dir + zeroPad(deg, 3) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\"";
else
return dir + zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\"";
}
export function dataPointMap(container: HTMLElement, data: any) {
Object.keys(data).forEach((key) => {
const val = "" + data[key]; // Ensure a string
container.querySelectorAll(`[data-point="${key}"]`).forEach(el => {
// We could probably have options here
if (el instanceof HTMLInputElement) {
el.value = val;
} else if (el instanceof HTMLElement) {
el.innerText = val;
}
});
});
}
export function deg2rad(deg: number) {
var pi = Math.PI;
return deg * (pi / 180);
}
export function rad2deg(rad: number) {
var pi = Math.PI;
return rad / (pi / 180);
}
export function generateUUIDv4() {
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);
});
}
export function keyEventWasInInput(event: KeyboardEvent) {
const target = event.target;
return (target instanceof HTMLElement && (["INPUT", "TEXTAREA"].includes(target.nodeName)));
}
export function reciprocalHeading(heading: number): number {
return heading > 180 ? heading - 180 : heading + 180;
}
/**
* Prepend numbers to the start of a string
*
* @param num <number> subject number
* @param places <number> places to pad
* @param decimal <boolean> whether this is a decimal number or not
*
* */
export const zeroAppend = function (num: number, places: number, decimal: boolean = false) {
var string = decimal ? num.toFixed(2) : String(num);
while (string.length < places) {
string = "0" + string;
}
return string;
}
export const zeroPad = function (num: number, places: number) {
var string = String(num);
while (string.length < places) {
string += "0";
}
return string;
}
export function similarity(s1: string, s2: string) {
var longer = s1;
var shorter = s2;
if (s1.length < s2.length) {
longer = s2;
shorter = s1;
}
var longerLength = longer.length;
if (longerLength == 0) {
return 1.0;
}
return (longerLength - editDistance(longer, shorter)) / longerLength;
}
export function editDistance(s1: string, s2: string) {
s1 = s1.toLowerCase();
s2 = s2.toLowerCase();
var costs = new Array();
for (var i = 0; i <= s1.length; i++) {
var lastValue = i;
for (var j = 0; j <= s2.length; j++) {
if (i == 0)
costs[j] = j;
else {
if (j > 0) {
var newValue = costs[j - 1];
if (s1.charAt(i - 1) != s2.charAt(j - 1))
newValue = Math.min(Math.min(newValue, lastValue),
costs[j]) + 1;
costs[j - 1] = lastValue;
lastValue = newValue;
}
}
}
if (i > 0)
costs[s2.length] = lastValue;
}
return costs[s2.length];
}
export type MGRS = {
bandLetter: string,
columnLetter: string,
easting: string,
groups: string[],
northing: string,
precision: number,
rowLetter: string,
string: string,
zoneNumber: string
}
export function latLngToMGRS(lat: number, lng: number, precision: number = 4): MGRS | false {
if (precision < 0 || precision > 6) {
console.error("latLngToMGRS: precision must be a number >= 0 and <= 6. Given precision: " + precision);
return false;
}
const mgrs = new usng.Converter().LLtoMGRS(lat, lng, precision);
const match = mgrs.match(new RegExp(`^(\\d{2})([A-Z])([A-Z])([A-Z])(\\d+)$`));
const easting = match[5].substr(0, match[5].length / 2);
const northing = match[5].substr(match[5].length / 2);
let output: MGRS = {
bandLetter: match[2],
columnLetter: match[3],
groups: [match[1] + match[2], match[3] + match[4], easting, northing],
easting: easting,
northing: northing,
precision: precision,
rowLetter: match[4],
string: match[0],
zoneNumber: match[1]
}
return output;
}
export function latLngToUTM(lat: number, lng: number) {
return new usng.Converter().LLtoUTM(lat, lng);
}
export function latLngToMercator(lat: number, lng: number): { x: number, y: number } {
var rMajor = 6378137; //Equatorial Radius, WGS84
var shift = Math.PI * rMajor;
var x = lng * shift / 180;
var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
y = y * shift / 180;
return { x: x, y: y };
}
export function mercatorToLatLng(x: number, y: number) {
var rMajor = 6378137; //Equatorial Radius, WGS84
var shift = Math.PI * rMajor;
var lng = x / shift * 180.0;
var lat = y / shift * 180.0;
lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0);
return { lng: lng, lat: lat };
}
export function createDivWithClass(className: string) {
var el = document.createElement("div");
el.classList.add(className);
return el;
}
export function knotsToMs(knots: number) {
return knots / 1.94384;
}
export function msToKnots(ms: number) {
return ms * 1.94384;
}
export function ftToM(ft: number) {
return ft * 0.3048;
}
export function mToFt(m: number) {
return m / 0.3048;
}
export function mToNm(m: number) {
return m * 0.000539957;
}
export function nmToM(nm: number) {
return nm / 0.000539957;
}
export function nmToFt(nm: number) {
return nm * 6076.12;
}
export function polyContains(latlng: LatLng, polygon: Polygon) {
var poly = polygon.toGeoJSON();
return turf.inside(turf.point([latlng.lng, latlng.lat]), poly);
}
export function randomPointInPoly(polygon: Polygon): LatLng {
var bounds = polygon.getBounds();
var x_min = bounds.getEast();
var x_max = bounds.getWest();
var y_min = bounds.getSouth();
var y_max = bounds.getNorth();
var lat = y_min + (Math.random() * (y_max - y_min));
var lng = x_min + (Math.random() * (x_max - x_min));
var poly = polygon.toGeoJSON();
var inside = turf.inside(turf.point([lng, lat]), poly);
if (inside) {
return new LatLng(lat, lng);
} else {
return randomPointInPoly(polygon);
}
}
export function polygonArea(polygon: Polygon) {
var poly = polygon.toGeoJSON();
return turf.area(poly);
}
export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: { type?: string, role?: string, ranges?: string[], eras?: string[], coalition?: string }) {
/* Start from all the unit blueprints in the database */
var unitBlueprints = Object.values(unitDatabase.getBlueprints());
/* If a specific type or role is provided, use only the blueprints of that type or role */
if (options.type && options.role) {
console.error("Can't create random unit if both type and role are provided. Either create by type or by role.")
return null;
}
if (options.type) {
unitBlueprints = unitDatabase.getByType(options.type);
}
else if (options.role) {
unitBlueprints = unitDatabase.getByType(options.role);
}
/* Keep only the units that have a range included in the requested values */
if (options.ranges) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
var rangeType = "";
var range = unitBlueprint.acquisitionRange;
if (range !== undefined) {
if (range >= 0 && range < 10000)
rangeType = "Short range";
else if (range >= 10000 && range < 100000)
rangeType = "Medium range";
else if (range >= 100000 && range < 999999)
rangeType = "Long range";
}
return options.ranges?.includes(rangeType);
});
}
/* Keep only the units that have an era included in the requested values */
if (options.eras) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
return unitBlueprint.era ? options.eras?.includes(unitBlueprint.era) : true;
});
}
/* Keep only the units that have the correct coalition, if selected */
if (options.coalition) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
return (unitBlueprint.coalition && unitBlueprint.coalition !== "") ? options.coalition === unitBlueprint.coalition : true;
});
}
var index = Math.floor(Math.random() * unitBlueprints.length);
return unitBlueprints[index];
}
export function getMarkerCategoryByName(name: string) {
if (aircraftDatabase.getByName(name) != null)
return "aircraft";
else if (helicopterDatabase.getByName(name) != null)
return "helicopter";
else if (groundUnitDatabase.getByName(name) != null) {
var type = groundUnitDatabase.getByName(name)?.type ?? "";
if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type))
return "groundunit-sam";
else
return "groundunit-other";
}
else if (navyUnitDatabase.getByName(name) != null)
return "navyunit";
else
return "aircraft"; // TODO add other unit types
}
export function getUnitDatabaseByCategory(category: string) {
if (category.toLowerCase() == "aircraft")
return aircraftDatabase;
else if (category.toLowerCase() == "helicopter")
return helicopterDatabase;
else if (category.toLowerCase().includes("groundunit"))
return groundUnitDatabase;
else if (category.toLowerCase().includes("navyunit"))
return navyUnitDatabase;
else
return null;
}
export function getCategoryBlueprintIconSVG(category: string, unitName: string) {
const path = "/resources/theme/images/buttons/visibility/";
// We can just send these back okay
if (["Aircraft", "Helicopter", "NavyUnit"].includes(category)) return `${path}${category.toLowerCase()}.svg`;
// Return if not a ground units as it's therefore something we don't recognise
if (category !== "GroundUnit") return false;
/** We need to get the unit detail for ground units so we can work out if it's an air defence unit or not **/
return GROUND_UNIT_AIR_DEFENCE_REGEX.test(unitName) ? `${path}groundunit-sam.svg` : `${path}groundunit.svg`;
}
export function base64ToBytes(base64: string) {
return Buffer.from(base64, 'base64').buffer;
}
export function enumToState(state: number) {
if (state < states.length)
return states[state];
else
return states[0];
}
export function enumToROE(ROE: number) {
if (ROE < ROEs.length)
return ROEs[ROE];
else
return ROEs[0];
}
export function enumToReactionToThreat(reactionToThreat: number) {
if (reactionToThreat < reactionsToThreat.length)
return reactionsToThreat[reactionToThreat];
else
return reactionsToThreat[0];
}
export function enumToEmissioNCountermeasure(emissionCountermeasure: number) {
if (emissionCountermeasure < emissionsCountermeasures.length)
return emissionsCountermeasures[emissionCountermeasure];
else
return emissionsCountermeasures[0];
}
export function enumToCoalition(coalitionID: number) {
switch (coalitionID) {
case 0: return "neutral";
case 1: return "red";
case 2: return "blue";
}
return "";
}
export function coalitionToEnum(coalition: string) {
switch (coalition) {
case "neutral": return 0;
case "red": return 1;
case "blue": return 2;
}
return 0;
}
export function convertDateAndTimeToDate(dateAndTime: DateAndTime) {
const date = dateAndTime.date;
const time = dateAndTime.time;
if (!date) {
return new Date();
}
let year = date.Year;
let month = date.Month - 1;
if (month < 0) {
month = 11;
year--;
}
return new Date(year, month, date.Day, time.h, time.m, time.s);
}
export function createCheckboxOption(text: string, description: string, checked: boolean = true, callback: CallableFunction = (ev: any) => { }, options?: any) {
options = {
"disabled": false,
"name": "",
"readOnly": false,
"value": null,
...options
};
var div = document.createElement("div");
div.classList.add("ol-checkbox");
var label = document.createElement("label");
label.title = description;
var input = document.createElement("input");
input.type = "checkbox";
input.checked = checked;
input.name = options.name;
input.disabled = options.disabled;
input.readOnly = options.readOnly;
input.value = options.value;
var span = document.createElement("span");
span.innerText = text;
label.appendChild(input);
label.appendChild(span);
div.appendChild(label);
input.onclick = (ev: any) => callback(ev);
return div as HTMLElement;
}
export function getCheckboxOptions(dropdown: Dropdown) {
var values: { [key: string]: boolean } = {};
const element = dropdown.getOptionElements();
for (let idx = 0; idx < element.length; idx++) {
const option = element.item(idx) as HTMLElement;
const key = option.querySelector("span")?.innerText;
const value = option.querySelector("input")?.checked;
if (key !== undefined && value !== undefined)
values[key] = value;
}
return values;
}
export function createTextInputOption(text: string, description: string, initialValue: string, type: string, callback: CallableFunction = (ev: any) => { }, options?: any) {
options = {
"disabled": false,
"name": "",
"readOnly": false,
...options
};
var div = document.createElement("div");
div.classList.add("ol-text-input", "border");
var label = document.createElement("label");
label.title = description;
var input = document.createElement("input");
input.type = type;
input.name = options.name;
input.disabled = options.disabled;
input.readOnly = options.readOnly;
if (options.min)
input.min = options.min;
if (options.max)
input.max = options.max;
input.value = initialValue;
input.style.width = "80px";
var span = document.createElement("span");
span.innerText = text;
label.appendChild(span);
label.appendChild(input);
div.appendChild(label);
input.onchange = (ev: any) => {
if (type === 'number') {
if (Number(input.max) && Number(ev.srcElement.value) > Number(input.max))
input.value = input.max;
else if (Number(input.min) && Number(ev.srcElement.value) < Number(input.min))
input.value = input.min;
}
callback(ev);
}
return div as HTMLElement;
}
export function createSliderInputOption(text: string, description: string, initialValue: number, callback: CallableFunction = (ev: any) => { }, options?: any) {
var div = document.createElement("div");
var label = document.createElement("label");
label.title = description;
var input = new Slider(null, options.min ?? 0, options.max ?? 100, "", (val: number) => {
callback({currentTarget: {value: val}});
});
input.setValue(initialValue);
input.getContainer()?.querySelector(".ol-data-grid")?.classList.add("hide");
var span = document.createElement("span");
span.innerText = text;
span.style.width = "100%";
span.style.margin = "auto";
label.appendChild(span);
label.appendChild(input.getContainer() as HTMLElement);
label.style.display = "flex";
label.style.alignContent = "center";
label.style.width = "100%";
div.appendChild(label);
return div as HTMLElement;
}
export function getGroundElevation(latlng: LatLng, callback: CallableFunction) {
/* Get the ground elevation from the server endpoint */
const xhr = new XMLHttpRequest();
xhr.open('GET', `api/elevation/${latlng.lat}/${latlng.lng}`, true);
xhr.timeout = 500; // ms
xhr.responseType = 'json';
xhr.onload = () => {
var status = xhr?.status;
if (status === 200) {
callback(xhr.response)
}
};
xhr.send();
}

View File

@ -1,51 +0,0 @@
import { Panel } from "./panel";
export class ConnectionStatusPanel extends Panel {
#elapsedTimeElement:HTMLElement;
#missionTimeElement:HTMLElement;
constructor(ID: string) {
super( ID );
this.#elapsedTimeElement = <HTMLElement>this.getElement().querySelector( ".mission-elapsed-time" );
this.#missionTimeElement = <HTMLElement>this.getElement().querySelector( ".mission-time" );
this.#elapsedTimeElement.addEventListener( "click", () => {
this.#toggleTime();
});
this.#missionTimeElement.addEventListener( "click", () => {
this.#toggleTime();
});
}
setElapsedTime( time:string ) {
this.#elapsedTimeElement.innerText = `ET: ${time}`;
}
setMissionTime( time:string ) {
this.#missionTimeElement.innerText = `MT: ${time} L`;
}
showDisconnected() {
this.getElement().toggleAttribute( "data-is-connected", false );
this.getElement().toggleAttribute( "data-is-paused", false );
}
showConnected() {
this.getElement().toggleAttribute( "data-is-connected", true );
this.getElement().toggleAttribute( "data-is-paused", false );
}
showServerPaused() {
this.getElement().toggleAttribute( "data-is-connected", false );
this.getElement().toggleAttribute( "data-is-paused", true );
}
#toggleTime() {
this.getElement().toggleAttribute( "data-mission-time" );
}
}

View File

@ -1,62 +0,0 @@
import { getApp } from "..";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";
export class HotgroupPanel extends Panel {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
document.addEventListener("unitDeath", () => this.refreshHotgroups());
}
refreshHotgroups() {
for (let hotgroup = 1; hotgroup <= 9; hotgroup++){
this.removeHotgroup(hotgroup);
if (getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).length > 0)
this.addHotgroup(hotgroup);
}
}
addHotgroup(hotgroup: number) {
// Hotgroup number
var hotgroupDiv = document.createElement("div");
hotgroupDiv.classList.add("unit-hotgroup");
var idDiv = document.createElement("div");
idDiv.classList.add("unit-hotgroup-id");
idDiv.innerText = String(hotgroup);
hotgroupDiv.appendChild(idDiv);
// Hotgroup unit count
var countDiv = document.createElement("div");
countDiv.innerText = `x${getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).length}`;
var el = document.createElement("div");
el.appendChild(hotgroupDiv);
el.appendChild(countDiv);
el.classList.add("hotgroup-selector");
el.toggleAttribute(`data-hotgroup-${hotgroup}`, true)
this.getElement().appendChild(el);
el.addEventListener("click", ( ev:MouseEvent ) => {
getApp().getUnitsManager().selectUnitsByHotgroup(hotgroup, (!ev.ctrlKey));
});
el.addEventListener("mouseover", () => {
getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(true));
});
el.addEventListener("mouseout", () => {
getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(false));
});
}
removeHotgroup(hotgroup: number) {
const el = this.getElement().querySelector(`[data-hotgroup-${hotgroup}]`) as HTMLElement;
if (el) el.remove();
}
}

View File

@ -1,99 +0,0 @@
import { getApp } from "..";
import { MouseInfoPanel } from "./mouseinfopanel";
import { Panel } from "./panel";
export class LogPanel extends Panel {
#open: boolean = false;
#queuedMessages: number = 0;
#scrolledDown: boolean = true;
#logs: {[key: string]: string} = {};
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
document.addEventListener("toggleLogPanel", () => {
this.getElement().classList.toggle("open");
this.#open = !this.#open;
this.#queuedMessages = 0;
this.#updateHeader();
this.#calculateHeight();
if (this.#scrolledDown)
this.#scrollDown();
});
const scrollEl = this.getElement().querySelector(".ol-scrollable");
if (scrollEl) {
scrollEl.addEventListener("scroll", () => {
this.#scrolledDown = Math.abs(scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight) < 1
});
}
window.addEventListener("resize", () => {
this.#calculateHeight();
});
const mouseInfoPanel = getApp().getPanelsManager().get("mouseInfo") as MouseInfoPanel;
new ResizeObserver(() => this.#calculateHeight()).observe(mouseInfoPanel.getElement())
}
show() {
super.show();
this.#calculateHeight();
}
appendLogs(logs: {[key: string]: string}) {
Object.keys(logs).forEach((key: string) => {
if (!(key in this.#logs)) {
this.#logs[key] = logs[key];
this.appendLog(logs[key]);
}
});
}
appendLog(log: string) {
var el = document.createElement("div");
el.classList.add("ol-log-entry");
el.textContent = log;
this.getElement().querySelector(".ol-scrollable")?.appendChild(el);
console.log(log);
if (!this.#open)
this.#queuedMessages++;
this.#updateHeader();
if (this.#scrolledDown)
this.#scrollDown();
}
#updateHeader() {
const headerEl = this.getElement().querySelector("#log-panel-header") as HTMLElement;
if (headerEl) {
if (this.#queuedMessages)
headerEl.innerText = `Server log (${this.#queuedMessages})`;
else
headerEl.innerText = `Server log`;
}
}
#scrollDown() {
const scrollEl = this.getElement().querySelector(".ol-scrollable");
if (scrollEl) {
scrollEl.scrollTop = scrollEl.scrollHeight - scrollEl.clientHeight;
}
}
#calculateHeight() {
const mouseInfoPanel = getApp().getPanelsManager().get("mouseInfo");
if (this.#open)
this.getElement().style.height = `${mouseInfoPanel.getElement().offsetTop - this.getElement().offsetTop - 10}px`;
else
this.getElement().style.height = "fit-content";
}
}

View File

@ -1,286 +0,0 @@
import { Icon, LatLng, Marker, Polyline } from "leaflet";
import { getApp } from "..";
import { distance, bearing, zeroAppend, mToNm, nmToFt, mToFt, latLngToMGRS, MGRS, latLngToUTM, latLngToMercator } from "../other/utils";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";
import formatcoords from "formatcoords";
import { MGRS_PRECISION_100M, MGRS_PRECISION_10KM, MGRS_PRECISION_10M, MGRS_PRECISION_1KM, MGRS_PRECISION_1M } from "../constants/constants";
export class MouseInfoPanel extends Panel {
#coordinatesElement:HTMLElement;
#locationSystems = [ "LatLng", "MGRS", "UTM" ];
#measureMarker: Marker;
#measurePoint: LatLng | null = null;
#measureIcon: Icon;
#measureLine: Polyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1, interactive: false });
#measureBox: HTMLElement;
#MGRSPrecisions = [ MGRS_PRECISION_10KM, MGRS_PRECISION_1KM, MGRS_PRECISION_100M, MGRS_PRECISION_10M, MGRS_PRECISION_1M ];
#selectedMGRSPrecisionIndex = 3;
#selectedLocationSystemIndex = 0;
#elevationRequest: XMLHttpRequest | null = null;
constructor(ID: string) {
super( ID );
this.#measureIcon = new Icon({ iconUrl: 'resources/theme/images/icons/pin.svg', iconAnchor: [16, 32] });
this.#measureMarker = new Marker([0, 0], { icon: this.#measureIcon, interactive: false });
this.#measureBox = document.createElement("div");
this.#measureBox.classList.add("ol-measure-box", "hide");
document.body.appendChild(this.#measureBox);
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));
getApp().getMap()?.on('drag', (e: any) => this.#onMouseMove(e));
document.addEventListener('unitsSelection', (e: CustomEvent<Unit[]>) => this.#update());
document.addEventListener('clearSelection', () => this.#update());
this.#coordinatesElement = <HTMLElement>this.getElement().querySelector( '#coordinates-tool' );
this.#coordinatesElement.addEventListener( "click", ( ev:MouseEvent ) => {
this.#changeLocationSystem();
});
getApp().getShortcutManager().addKeyboardShortcut( "switchMapLocationSystem", {
"callback": ( ev:KeyboardEvent ) => {
this.#changeLocationSystem();
},
"code": "KeyZ"
}).addKeyboardShortcut( "decreaseMGRSPrecision", {
"callback": ( ev:KeyboardEvent ) => {
if ( this.#getLocationSystem() !== "MGRS" ) {
return;
}
if ( this.#selectedMGRSPrecisionIndex > 0 ) {
this.#selectedMGRSPrecisionIndex--;
this.#update();
}
},
"code": "Comma"
}).addKeyboardShortcut( "increaseMGRSPrecision", {
"callback": ( ev:KeyboardEvent ) => {
if ( this.#getLocationSystem() !== "MGRS" ) {
return;
}
if ( this.#selectedMGRSPrecisionIndex < this.#MGRSPrecisions.length - 1 ) {
this.#selectedMGRSPrecisionIndex++;
this.#update();
}
},
"code": "Period"
});
}
#update() {
const mousePosition = getApp().getMap().getMouseCoordinates();
var selectedUnitPosition = null;
var selectedUnits = getApp().getUnitsManager().getSelectedUnits();
if (selectedUnits && selectedUnits.length == 1)
selectedUnitPosition = new LatLng(selectedUnits[0].getPosition().lat, selectedUnits[0].getPosition().lng);
/* Draw measures from selected unit, from pin location, and from bullseyes */
this.#drawMeasure("ref-measure-position", "measure-position", this.#measurePoint, mousePosition);
this.#drawMeasure("ref-unit-position", "unit-position", selectedUnitPosition, mousePosition);
this.getElement().querySelector(`#measuring-tool`)?.classList.toggle("hide", this.#measurePoint === null);
this.getElement().querySelector(`#unit-bullseye-tool`)?.classList.toggle("hide", selectedUnitPosition === null);
var bullseyes = getApp().getMissionManager().getBullseyes();
for (let idx in bullseyes)
this.#drawMeasure(null, `bullseye-${idx}`, bullseyes[idx].getLatLng(), mousePosition);
/* Draw coordinates */
var coords = formatcoords(mousePosition.lat, mousePosition.lng);
var coordString = coords.format('XDDMMss', {decimalPlaces: 4});
if ( this.#getLocationSystem() === "MGRS" ) {
const mgrs = <MGRS>latLngToMGRS( mousePosition.lat, mousePosition.lng, this.#MGRSPrecisions[ this.#selectedMGRSPrecisionIndex ] );
this.#drawCoordinates("ref-mouse-position-mgrs", "mouse-position-mgrs", "M"+mgrs.groups.join(" ") );
} else if ( this.#getLocationSystem() === "UTM" ) {
const utm = latLngToUTM( mousePosition.lat, mousePosition.lng );
this.#drawCoordinates("ref-mouse-position-utm-northing", "mouse-position-utm-northing", "N"+utm.northing);
this.#drawCoordinates("ref-mouse-position-utm-easting", "mouse-position-utm-easting", "E"+utm.easting);
} else {
this.#drawCoordinates("ref-mouse-position-latitude", "mouse-position-latitude", coordString.split(" ")[0]);
this.#drawCoordinates("ref-mouse-position-longitude", "mouse-position-longitude", coordString.split(" ")[1]);
}
/* Get the ground elevation from the server endpoint */
if (this.#elevationRequest == null) {
this.#elevationRequest = new XMLHttpRequest();
this.#elevationRequest.open('GET', `api/elevation/${mousePosition.lat}/${mousePosition.lng}`, true);
this.#elevationRequest.timeout = 500; // ms
this.#elevationRequest.responseType = 'json';
this.#elevationRequest.onload = () => {
var status = this.#elevationRequest?.status;
if (status === 200) {
const el = this.getElement().querySelector(`#mouse-position-elevation`) as HTMLElement;
try {
el.dataset.value = `${Math.floor(mToFt(parseFloat(this.#elevationRequest?.response)))} ft`;
} catch {
el.dataset.value = `N/A`;
}
}
this.#elevationRequest = null;
};
this.#elevationRequest.ontimeout = () => {this.#elevationRequest = null;}
this.#elevationRequest.onerror = () => {this.#elevationRequest = null;}
this.#elevationRequest.onabort = () => {this.#elevationRequest = null;}
this.#elevationRequest.send();
}
}
#onMapClick(e: any) {
if (e.originalEvent.ctrlKey) {
if (!this.#measurePoint) {
this.#measureBox.classList.toggle("hide", false);
this.#measurePoint = e.latlng;
this.#measureMarker.setLatLng(e.latlng);
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 (getApp().getMap().hasLayer(this.#measureMarker))
getApp().getMap().removeLayer(this.#measureMarker);
this.#measureLine.setLatLngs([]);
if (getApp().getMap().hasLayer(this.#measureLine))
getApp().getMap().removeLayer(this.#measureLine);
}
}
this.#update();
}
#drawMeasureLine() {
var mouseLatLng = getApp().getMap().containerPointToLatLng(getApp().getMap().getMousePosition());
const mousePosition = 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 = getApp().getMap().latLngToContainerPoint(this.#measurePoint);
var dx = mousePosition.x - startXY.x;
var dy = mousePosition.y - startXY.y;
var angle = Math.atan2(dy, dx);
if (angle > Math.PI / 2)
angle = angle - Math.PI;
if (angle < -Math.PI / 2)
angle = angle + Math.PI;
let bng = zeroAppend(Math.floor(bear), 3);
if (bng === "000")
bng = "360";
var [str, unit] = this.#computeDistanceString(dist)
let data = [`${bng}°`, `${str} ${unit}`];
this.#measureBox.innerText = data.join(" / ");
this.#measureBox.style.left = (mousePosition.x + startXY.x) / 2 - this.#measureBox.offsetWidth / 2 + "px";
this.#measureBox.style.top = (mousePosition.y + startXY.y) / 2 - this.#measureBox.offsetHeight / 2 + "px";
this.#measureBox.style.rotate = angle + "rad";
}
}
#onMouseMove(e: any) {
this.#update();
this.#drawMeasureLine();
}
#onZoom(e: any) {
this.#drawMeasureLine();
}
#drawMeasure(imgID: string | null, textID: string, value: LatLng | null, mousePosition: LatLng) {
var el = this.getElement().querySelector(`#${textID}`) as HTMLElement;
var img = imgID != null ? this.getElement().querySelector(`#${imgID}`) as HTMLElement : null;
if (value) {
if (el != null) {
el.classList.remove("hide");
var bear = bearing(value.lat, value.lng, mousePosition.lat, mousePosition.lng);
var dist = distance(value.lat, value.lng, mousePosition.lat, mousePosition.lng);
let bng = zeroAppend(Math.floor(bear), 3);
if (bng === "000")
bng = "360";
var [str, unit] = this.#computeDistanceString(dist)
el.dataset.bearing = bng;
el.dataset.distance = str;
el.dataset.distanceUnits = unit;
}
if (img != null)
img.classList.remove("hide");
}
else {
if (el != null)
el.classList.add("hide");
if (img != null)
img.classList.add("hide");
}
}
#drawCoordinates(imgID: string, textID: string, value: string) {
const el = this.getElement().querySelector(`#${textID}`) as HTMLElement;
const img = this.getElement().querySelector(`#${imgID}`) as HTMLElement;
if (img && el) {
el.innerText = value.substring(1);
img.innerText = value[0];
}
}
#computeDistanceString(dist: number) {
var val = mToNm(dist);
var strVal = 0;
var decimal = false;
var unit = "NM";
if (val > 10)
strVal = Math.floor(val);
else if (val > 1 && val <= 10) {
strVal = Math.floor(val * 100) / 100;
decimal = true;
}
else {
strVal = Math.floor(nmToFt(val));
unit = "ft";
}
return [zeroAppend(strVal, 3, decimal), unit];
}
#changeLocationSystem() {
if ( this.#selectedLocationSystemIndex < this.#locationSystems.length - 1 ) {
this.#selectedLocationSystemIndex++;
} else {
this.#selectedLocationSystemIndex = 0;
}
this.#coordinatesElement.setAttribute( "data-location-system", this.#getLocationSystem() );
this.#update();
}
#getLocationSystem() {
const x = this.#locationSystems;
const y = this.#selectedLocationSystemIndex;
const z = this.#locationSystems[ this.#selectedLocationSystemIndex ];
return this.#locationSystems[this.#selectedLocationSystemIndex];
}
}

View File

@ -1,43 +0,0 @@
import { PanelEventsManager } from "./paneleventsmanager";
export abstract class Panel {
#element: HTMLElement
#eventsManager!: PanelEventsManager;
constructor(ID: string) {
this.#element = <HTMLElement>document.getElementById(ID);
this.#eventsManager = new PanelEventsManager();
}
show() {
this.#element.classList.toggle("hide", false);
this.getEventsManager()?.trigger("show", {});
}
hide() {
this.#element.classList.toggle("hide", true);
this.getEventsManager()?.trigger("hide", {});
}
toggle() {
// Simple way to track if currently visible
if (this.getVisible())
this.hide();
else
this.show();
}
getElement() {
return this.#element;
}
getVisible() {
return (!this.getElement().classList.contains("hide"));
}
getEventsManager() {
return this.#eventsManager;
}
}

View File

@ -1,30 +0,0 @@
import { Listener } from "../interfaces";
import { EventsManager } from "../other/eventsmanager";
export class PanelEventsManager extends EventsManager {
constructor() {
super();
this.add("hide", []);
this.add("show", []);
}
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({
"callback": listener.callback
});
}
trigger(eventName: string, contextData: object) {
const listeners = this.get(eventName);
if (listeners) {
listeners.forEach((listener: Listener) => {
listener.callback(contextData);
});
}
}
}

View File

@ -1,26 +0,0 @@
import { Panel } from "./panel";
export class ServerStatusPanel extends Panel {
constructor(ID: string) {
super( ID );
}
update(frameRate: number, load: number) {
const frameRateEl = this.getElement().querySelector("#server-frame-rate");
if (frameRateEl) {
frameRateEl.textContent = `${frameRate}`;
frameRateEl.classList.toggle("fps-high", frameRate >= 60)
frameRateEl.classList.toggle("fps-medium", frameRate >= 30 && frameRate < 60)
frameRateEl.classList.toggle("fps-low", frameRate <= 30)
}
const loadEl = this.getElement().querySelector("#server-load");
if (loadEl) {
loadEl.textContent = `${load}`;
loadEl.classList.toggle("load-high", load >= 1000)
loadEl.classList.toggle("load-medium", load >= 100 && load < 1000)
loadEl.classList.toggle("load-low", load <= 100)
}
}
}

View File

@ -1,504 +0,0 @@
import { SVGInjector } from "@tanem/svg-injector";
import { getApp } from "..";
import { Dropdown } from "../controls/dropdown";
import { Slider } from "../controls/slider";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";
import { Switch } from "../controls/switch";
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, shotsIntensityDescriptions, shotsScatterDescriptions, speedIncrements } from "../constants/constants";
import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils";
import { GeneralSettings, Radio, TACAN } from "../interfaces";
import { ContextActionSet } from "../unit/contextactionset";
import { Popup } from "../popups/popup";
export class UnitControlPanel extends Panel {
#altitudeSlider: Slider;
#tankerSwitch: Switch;
#AWACSSwitch: Switch;
#altitudeTypeSwitch: Switch;
#speedSlider: Slider;
#speedTypeSwitch: Switch;
#onOffSwitch: Switch;
#followRoadsSwitch: Switch;
#operateAsSwitch: Switch;
#TACANXYDropdown: Dropdown;
#radioDecimalsDropdown: Dropdown;
#radioCallsignDropdown: Dropdown;
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
#advancedSettingsDialog: HTMLElement;
#units: Unit[] = [];
#selectedUnitsTypes: string[] = [];
#deleteDropdown: Dropdown;
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string) {
super(ID);
/* Unit control sliders */
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getApp().getUnitsManager().setAltitude(ftToM(value)); });
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getApp().getUnitsManager().setAltitudeType(value ? "ASL" : "AGL"); });
this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getApp().getUnitsManager().setSpeed(knotsToMs(value)); });
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getApp().getUnitsManager().setSpeedType(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], () => { getApp().getUnitsManager().setROE(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], () => { getApp().getUnitsManager().setReactionToThreat(option); });
});
this.#optionButtons["emissionsCountermeasures"] = emissionsCountermeasures.map((option: string, index: number) => {
return this.#createOptionButton(option, `emissions/${option.toLowerCase()}.svg`, emissionsCountermeasuresDescriptions[index], () => { getApp().getUnitsManager().setEmissionsCountermeasures(option); });
});
this.#optionButtons["shotsScatter"] = [1, 2, 3].map((option: number, index: number) => {
return this.#createOptionButton(option.toString(), `scatter/${option.toString().toLowerCase()}.svg`, shotsScatterDescriptions[index], () => { getApp().getUnitsManager().setShotsScatter(option); });
});
this.#optionButtons["shotsIntensity"] = [1, 2, 3].map((option: number, index: number) => {
return this.#createOptionButton(option.toString(), `intensity/${option.toString().toLowerCase()}.svg`, shotsIntensityDescriptions[index], () => { getApp().getUnitsManager().setShotsIntensity(option); });
});
this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]);
this.getElement().querySelector("#reaction-to-threat-buttons-container")?.append(...this.#optionButtons["reactionToThreat"]);
this.getElement().querySelector("#emissions-countermeasures-buttons-container")?.append(...this.#optionButtons["emissionsCountermeasures"]);
this.getElement().querySelector("#shots-scatter-buttons-container")?.append(...this.#optionButtons["shotsScatter"]);
this.getElement().querySelector("#shots-intensity-buttons-container")?.append(...this.#optionButtons["shotsIntensity"]);
/* Tanker */
this.#tankerSwitch = new Switch("tanker-on-switch", (value: boolean) => {
// TODO: split setAdvancedOptions into setIsTanker, setIsAWACS, setAdvancedOptions
var selectedUnits = getApp().getUnitsManager().getSelectedUnits();
selectedUnits.forEach((unit: Unit) => {
unit.setAdvancedOptions(value, unit.getIsActiveAWACS(), unit.getTACAN(), unit.getRadio(), unit.getGeneralSettings());
});
});
/* AWACS */
this.#AWACSSwitch = new Switch("AWACS-on-switch", (value: boolean) => {
// TODO: split setAdvancedOptions into setIsTanker, setIsAWACS, setAdvancedOptions
var selectedUnits = getApp().getUnitsManager().getSelectedUnits();
selectedUnits.forEach((unit: Unit) => {
unit.setAdvancedOptions(unit.getIsActiveTanker(), value, unit.getTACAN(), unit.getRadio(), unit.getGeneralSettings());
});
});
/* On off switch */
this.#onOffSwitch = new Switch("on-off-switch", (value: boolean) => {
getApp().getUnitsManager().setOnOff(value);
});
/* Follow roads switch */
this.#followRoadsSwitch = new Switch("follow-roads-switch", (value: boolean) => {
getApp().getUnitsManager().setFollowRoads(value);
if (value)
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Warning: follow roads movements can cause lag");
});
/* Operate as */
this.#operateAsSwitch = new Switch("operate-as-switch", (value: boolean) => {
getApp().getUnitsManager().setOperateAs(value);
});
/* Mouseover of (?) highlights activation buttons */
const operateAsQuestionMark = <HTMLElement>this.getElement().querySelector("#operate-as h4 img");
operateAsQuestionMark.addEventListener("mouseover", () => {
document.querySelectorAll(`#rapid-controls button.scenic-action`).forEach((btn: Element) => {
btn.classList.add(`pulse`);
});
});
operateAsQuestionMark.addEventListener("mouseout", () => {
document.querySelectorAll(`#rapid-controls button.scenic-action.pulse`).forEach((btn: Element) => {
btn.classList.remove(`pulse`);
});
});
/* Advanced settings dialog */
this.#advancedSettingsDialog = <HTMLElement>document.querySelector("#advanced-settings-dialog");
/* Advanced settings dropdowns */
this.#TACANXYDropdown = new Dropdown("TACAN-XY", () => { });
this.#TACANXYDropdown.setOptions(["X", "Y"]);
this.#radioDecimalsDropdown = new Dropdown("radio-decimals", () => { });
this.#radioDecimalsDropdown.setOptions([".000", ".250", ".500", ".750"]);
this.#radioCallsignDropdown = new Dropdown("radio-callsign", () => { });
this.#deleteDropdown = new Dropdown("delete-options", () => { });
/* Events and timer */
window.setInterval(() => { this.update(); }, 25);
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => {
this.show();
this.addButtons();
this.#updateRapidControls();
});
document.addEventListener("clearSelection", () => {
this.hide();
this.#updateRapidControls();
});
document.addEventListener("applyAdvancedSettings", () => { this.#applyAdvancedSettings(); })
document.addEventListener("showAdvancedSettings", () => {
this.#updateAdvancedSettingsDialog(getApp().getUnitsManager().getSelectedUnits());
this.#advancedSettingsDialog.classList.remove("hide");
});
/* This is for when a ctrl-click happens on the map for deselection and we need to remove the selected unit from the panel */
document.addEventListener("unitsDeselection", (ev: CustomEventInit) => {
if (ev.detail.length > 0) {
this.show();
this.addButtons();
this.#updateRapidControls();
} else {
this.hide();
this.#updateRapidControls();
}
});
window.addEventListener("resize", (e: any) => this.#calculateMaxHeight());
const element = document.getElementById("toolbar-container");
if (element)
new ResizeObserver(() => this.#calculateTop()).observe(element);
this.#calculateMaxHeight()
this.hide();
}
show() {
const context = getApp().getCurrentContext();
if (!context.getUseUnitControlPanel())
return;
this.#updateNumberOfSelectedUnits();
super.show();
this.#speedTypeSwitch.resetExpectedValue();
this.#altitudeTypeSwitch.resetExpectedValue();
this.#tankerSwitch.resetExpectedValue();
this.#AWACSSwitch.resetExpectedValue();
this.#onOffSwitch.resetExpectedValue();
this.#followRoadsSwitch.resetExpectedValue();
this.#operateAsSwitch.resetExpectedValue();
this.#altitudeSlider.resetExpectedValue();
this.#speedSlider.resetExpectedValue();
this.#calculateMaxHeight();
}
addButtons() {
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) => {
var button = document.createElement("button");
var callsign = unit.getUnitName() || "";
var label = unit.getDatabase()?.getByName(unit.getName())?.label || unit.getName();
button.setAttribute("data-unit-id", "" + unit.ID);
button.setAttribute("data-label", label);
button.setAttribute("data-callsign", callsign);
button.setAttribute("data-coalition", unit.getCoalition());
button.classList.add("pill", "highlight-coalition")
button.addEventListener("click", (ev: MouseEventInit) => {
// Ctrl-click deselection
if (ev.ctrlKey === true && ev.shiftKey === false && ev.altKey === false) {
getApp().getUnitsManager().deselectUnit(unit.ID);
button.remove();
// Deselect all
} else {
getApp().getUnitsManager().deselectAllUnits();
getApp().getUnitsManager().selectUnit(unit.ID, true);
}
});
return (button);
}));
} else {
var el = document.createElement("div");
el.innerText = "Too many units selected";
this.getElement().querySelector("#selected-units-container")?.replaceChildren(el);
}
}
update() {
if (this.getVisible()) {
const element = this.getElement();
if (element != null && this.#units.length > 0) {
/* Toggle visibility of control elements */
var isTanker = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.isTanker(); });
var isAWACS = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.isAWACS(); });
var isActiveTanker = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getIsActiveTanker() });
var isActiveAWACAS = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getIsActiveAWACS() });
element.toggleAttribute("data-show-categories-tooltip", this.#selectedUnitsTypes.length > 1);
element.toggleAttribute("data-show-speed-slider", this.#selectedUnitsTypes.length == 1);
element.toggleAttribute("data-show-altitude-slider", this.#selectedUnitsTypes.length == 1 && (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")));
element.toggleAttribute("data-show-roe", !isTanker && !isAWACS);
element.toggleAttribute("data-show-threat", (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")) && !(this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-emissions-countermeasures", (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")) && !(this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-shots-scatter", this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit")); //TODO: more refined
element.toggleAttribute("data-show-shots-intensity", this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit")); //TODO: more refined
element.toggleAttribute("data-show-tanker-button", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.isTanker(); }) === true);
element.toggleAttribute("data-show-AWACS-button", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.isAWACS(); }) === true);
element.toggleAttribute("data-show-on-off", (this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")) && !(this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")));
element.toggleAttribute("data-show-follow-roads", (this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit")));
element.toggleAttribute("data-show-operate-as", this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit") && getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getCoalition() }) === "neutral");
if (this.#units.length == 1) {
if (isAWACS)
element.toggleAttribute("data-show-advanced-settings-button", isActiveAWACAS);
else if (isTanker)
element.toggleAttribute("data-show-advanced-settings-button", isActiveTanker);
else
element.toggleAttribute("data-show-advanced-settings-button", true);
}
if (this.#selectedUnitsTypes.length == 1) {
/* Flight controls */
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 isActiveTanker = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getIsActiveTanker() });
var isActiveAWACAS = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getIsActiveAWACS() });
var onOff = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getOnOff() });
var followRoads = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getFollowRoads() });
var operateAs = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getOperateAs() });
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined ? desiredAltitudeType == "ASL" : undefined, false);
this.#speedTypeSwitch.setValue(desiredSpeedType != undefined ? desiredSpeedType == "CAS" : undefined, false);
this.#speedSlider.setMinMax(minSpeedValues[this.#selectedUnitsTypes[0]], maxSpeedValues[this.#selectedUnitsTypes[0]]);
this.#altitudeSlider.setMinMax(minAltitudeValues[this.#selectedUnitsTypes[0]], maxAltitudeValues[this.#selectedUnitsTypes[0]]);
this.#speedSlider.setIncrement(speedIncrements[this.#selectedUnitsTypes[0]]);
this.#altitudeSlider.setIncrement(altitudeIncrements[this.#selectedUnitsTypes[0]]);
this.#speedSlider.setActive(desiredSpeed != undefined);
if (desiredSpeed != undefined)
this.#speedSlider.setValue(msToKnots(desiredSpeed), false);
this.#altitudeSlider.setActive(desiredAltitude != undefined);
if (desiredAltitude != undefined)
this.#altitudeSlider.setValue(mToFt(desiredAltitude), false);
}
else {
this.#speedSlider.setActive(false);
this.#altitudeSlider.setActive(false);
}
/* Option buttons */
this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getROE() === button.value))
});
this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getReactionToThreat() === button.value))
});
this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getEmissionsCountermeasures() === button.value))
});
this.#optionButtons["shotsScatter"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getShotsScatter().toString() === button.value))
});
this.#optionButtons["shotsIntensity"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getShotsIntensity().toString() === button.value))
});
this.#tankerSwitch.setValue(isActiveTanker, false);
this.#AWACSSwitch.setValue(isActiveAWACAS, false);
this.#onOffSwitch.setValue(onOff, false);
this.#followRoadsSwitch.setValue(followRoads, false);
this.#operateAsSwitch.setValue(operateAs ? operateAs === "blue" : undefined, false);
}
}
}
#updateRapidControls() {
var contextActionSet = new ContextActionSet();
var units = getApp().getUnitsManager().getSelectedUnits();
var showAltitudeChange = units.some((unit: Unit) => { return ["Aircraft", "Helicopter"].includes(unit.getCategory()); });
this.getElement().querySelector("#climb")?.classList.toggle("hide", !showAltitudeChange);
this.getElement().querySelector("#descend")?.classList.toggle("hide", !showAltitudeChange);
units.forEach((unit: Unit) => {
unit.appendContextActions(contextActionSet, null, null);
})
const rapidControlsContainer = this.getElement().querySelector("#rapid-controls") as HTMLElement;
const unitActionButtons = rapidControlsContainer.querySelectorAll(".unit-action-button");
for (let button of unitActionButtons) {
rapidControlsContainer.removeChild(button);
}
for (let key in contextActionSet.getContextActions()) {
const contextAction = contextActionSet.getContextActions()[key];
let button = document.createElement("button");
button.title = contextAction.getDescription();
button.classList.add("ol-button", "unit-action-button");
if (contextAction.getOptions().isScenic)
button.classList.add("scenic-action");
button.id = key;
rapidControlsContainer.appendChild(button);
button.onclick = () => {
contextAction.executeCallback();
}
}
}
#updateAdvancedSettingsDialog(units: Unit[]) {
if (units.length == 1) {
/* HTML Elements */
const unitNameEl = this.#advancedSettingsDialog.querySelector("#unit-name") as HTMLElement;
const prohibitJettisonCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-jettison-checkbox")?.querySelector("input") as HTMLInputElement;
const prohibitAfterburnerCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-afterburner-checkbox")?.querySelector("input") as HTMLInputElement;
const prohibitAACheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-AA-checkbox")?.querySelector("input") as HTMLInputElement;
const prohibitAGCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-AG-checkbox")?.querySelector("input") as HTMLInputElement;
const prohibitAirWpnCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-air-wpn-checkbox")?.querySelector("input") as HTMLInputElement;
const TACANCheckbox = this.#advancedSettingsDialog.querySelector("#TACAN-checkbox")?.querySelector("input") as HTMLInputElement;
const TACANChannelInput = this.#advancedSettingsDialog.querySelector("#TACAN-channel")?.querySelector("input") as HTMLInputElement;
const TACANCallsignInput = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input") as HTMLInputElement;
const radioMhzInput = this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input") as HTMLInputElement;
const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement;
const unit = units[0];
const isTanker = unit.isTanker();
const isAWACS = unit.isAWACS();
const radioMHz = Math.floor(unit.getRadio().frequency / 1000000);
const radioDecimals = (unit.getRadio().frequency / 1000000 - radioMHz) * 1000;
/* Activate the correct options depending on unit type */
this.#advancedSettingsDialog.toggleAttribute("data-show-settings", !isTanker && !isAWACS);
this.#advancedSettingsDialog.toggleAttribute("data-show-air-unit-checkboxes", ["Aircraft", "Helicopter"].includes(units[0].getCategory()));
this.#advancedSettingsDialog.toggleAttribute("data-show-tasking", isTanker || isAWACS);
this.#advancedSettingsDialog.toggleAttribute("data-show-tanker", isTanker);
this.#advancedSettingsDialog.toggleAttribute("data-show-AWACS", isAWACS);
this.#advancedSettingsDialog.toggleAttribute("data-show-TACAN", isTanker || ["Aircraft Carrier", "Super Aircraft Carrier"].includes(units[0].getType()));
this.#advancedSettingsDialog.toggleAttribute("data-show-radio", isTanker || isAWACS);
/* Set common properties */
// Name
unitNameEl.innerText = unit.getUnitName();
// General settings
prohibitJettisonCheckbox.checked = unit.getGeneralSettings().prohibitJettison;
prohibitAfterburnerCheckbox.checked = unit.getGeneralSettings().prohibitAfterburner;
prohibitAACheckbox.checked = unit.getGeneralSettings().prohibitAA;
prohibitAGCheckbox.checked = unit.getGeneralSettings().prohibitAG;
prohibitAirWpnCheckbox.checked = unit.getGeneralSettings().prohibitAirWpn;
// TACAN
TACANCheckbox.checked = unit.getTACAN().isOn;
TACANChannelInput.value = String(unit.getTACAN().channel);
TACANCallsignInput.value = String(unit.getTACAN().callsign);
this.#TACANXYDropdown.setValue(unit.getTACAN().XY);
// Radio
radioMhzInput.value = String(radioMHz);
radioCallsignNumberInput.value = String(unit.getRadio().callsignNumber);
this.#radioDecimalsDropdown.setValue("." + radioDecimals);
if (isTanker) /* Set tanker specific options */
this.#radioCallsignDropdown.setOptions(["Texaco", "Arco", "Shell"], null);
else if (isAWACS) /* Set AWACS specific options */
this.#radioCallsignDropdown.setOptions(["Overlord", "Magic", "Wizard", "Focus", "Darkstar"], null);
else
this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"], null);
// This must be done after setting the options
if (!this.#radioCallsignDropdown.selectValue(unit.getRadio().callsign - 1)) // Ensure the selected value is in the acceptable range
this.#radioCallsignDropdown.selectValue(0);
}
}
#updateNumberOfSelectedUnits() {
const num = getApp().getUnitsManager().getSelectedUnits().length;
this.getElement().querySelectorAll(".num-selected-units").forEach(el => {
if (el instanceof HTMLElement) el.innerText = num + "";
});
}
#applyAdvancedSettings() {
/* HTML Elements */
const prohibitJettisonCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-jettison-checkbox")?.querySelector("input") as HTMLInputElement;
const prohibitAfterburnerCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-afterburner-checkbox")?.querySelector("input") as HTMLInputElement;
const prohibitAACheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-AA-checkbox")?.querySelector("input") as HTMLInputElement;
const prohibitAGCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-AG-checkbox")?.querySelector("input") as HTMLInputElement;
const prohibitAirWpnCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-air-wpn-checkbox")?.querySelector("input") as HTMLInputElement;
const TACANCheckbox = this.#advancedSettingsDialog.querySelector("#TACAN-checkbox")?.querySelector("input") as HTMLInputElement;
const TACANChannelInput = this.#advancedSettingsDialog.querySelector("#TACAN-channel")?.querySelector("input") as HTMLInputElement;
const TACANCallsignInput = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input") as HTMLInputElement;
const radioMhzInput = this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input") as HTMLInputElement;
const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement;
/* TACAN */
const TACAN: TACAN = {
isOn: TACANCheckbox.checked ? true : false,
channel: Number(TACANChannelInput.value),
XY: this.#TACANXYDropdown.getValue(),
callsign: TACANCallsignInput.value as string
}
/* Radio */
const radioMHz = Number(radioMhzInput.value);
const radioDecimals = this.#radioDecimalsDropdown.getValue();
const radio: Radio = {
frequency: (radioMHz * 1000 + Number(radioDecimals.substring(1))) * 1000,
callsign: this.#radioCallsignDropdown.getIndex() + 1,
callsignNumber: Number(radioCallsignNumberInput.value)
}
/* General settings */
const generalSettings: GeneralSettings = {
prohibitJettison: prohibitJettisonCheckbox.checked ? true : false,
prohibitAfterburner: prohibitAfterburnerCheckbox.checked ? true : false,
prohibitAA: prohibitAACheckbox.checked ? true : false,
prohibitAG: prohibitAGCheckbox.checked ? true : false,
prohibitAirWpn: prohibitAirWpnCheckbox.checked ? true : false
}
/* Send command and close */
var units = getApp().getUnitsManager().getSelectedUnits();
// TODO: split setAdvancedOptions into setIsTanker, setIsAWACS, setAdvancedOptions
if (units.length > 0)
units[0].setAdvancedOptions(units[0].getIsActiveTanker(), units[0].getIsActiveAWACS(), TACAN, radio, generalSettings);
this.#advancedSettingsDialog.classList.add("hide");
}
#createOptionButton(value: string, url: string, title: string, callback: EventListenerOrEventListenerObject) {
var button = document.createElement("button");
button.title = title;
button.value = value;
var img = document.createElement("img");
img.src = `/resources/theme/images/buttons/${url}`;
img.onload = () => SVGInjector(img);
button.appendChild(img);
button.addEventListener("click", callback);
return button;
}
#calculateTop() {
const element = document.getElementById("toolbar-container");
if (element)
this.getElement().style.top = `${element.offsetTop + element.offsetHeight + 10}px`;
}
#calculateMaxHeight() {
const element = document.getElementById("unit-control-panel-content");
this.#calculateTop();
if (element)
element.style.maxHeight = `${window.innerHeight - this.getElement().offsetTop - 10}px`;
}
}

View File

@ -1,109 +0,0 @@
import { getApp } from "..";
import { Ammo } from "../interfaces";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";
export class UnitInfoPanel extends Panel {
#currentTask: HTMLElement;
#fuelBar: HTMLElement;
#fuelPercentage: HTMLElement;
#loadoutContainer: HTMLElement;
#silhouette: HTMLImageElement;
#unitControl: HTMLElement;
#unitLabel: HTMLElement;
#unitGroup: HTMLElement;
#unitName: HTMLElement;
constructor(ID: string) {
super( ID );
this.#currentTask = (this.getElement().querySelector("#current-task")) as HTMLElement;
this.#fuelBar = (this.getElement().querySelector("#fuel-bar")) as HTMLElement;
this.#fuelPercentage = (this.getElement().querySelector("#fuel-percentage")) as HTMLElement;
this.#loadoutContainer = (this.getElement().querySelector("#loadout-container")) as HTMLElement;
this.#silhouette = (this.getElement().querySelector("#loadout-silhouette")) as HTMLImageElement;
this.#unitControl = (this.getElement().querySelector("#unit-control")) as HTMLElement;
this.#unitLabel = (this.getElement().querySelector("#unit-label")) as HTMLElement;
this.#unitGroup = (this.getElement().querySelector("#unit-group")) as HTMLElement;
this.#unitName = (this.getElement().querySelector("#unit-name")) as HTMLElement;
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => this.#onUnitsSelection(e.detail));
document.addEventListener("unitsDeselection", (e: CustomEvent<Unit[]>) => this.#onUnitsDeselection(e.detail));
document.addEventListener("clearSelection", (e: CustomEvent<Unit[]>) => this.#onUnitsDeselection([]));
document.addEventListener("unitUpdated", (e: CustomEvent<Unit>) => this.#onUnitUpdate(e.detail));
this.hide();
}
#onUnitUpdate(unit: Unit) {
if (this.getElement() != null && this.getVisible() && unit.getSelected()) {
/* Set the unit info */
this.#unitLabel.innerText = unit.getDatabaseEntry()?.label || unit.getName();
this.#unitGroup.dataset.groupName = unit.getGroup()?.getName() ?? "No group";
this.#unitName.innerText = unit.getUnitName();
if (unit.getHuman())
this.#unitControl.innerText = "Human";
else if (unit.getControlled())
this.#unitControl.innerText = "Olympus controlled";
else
this.#unitControl.innerText = "DCS Controlled";
this.#fuelBar.style.width = String(unit.getFuel() + "%");
this.#fuelPercentage.dataset.percentage = "" + unit.getFuel();
this.#currentTask.dataset.currentTask = unit.getTask() !== "" ? unit.getTask() : "No task";
this.#currentTask.dataset.coalition = unit.getCoalition();
const filename = unit.getDatabase()?.getByName(unit.getName())?.filename;
if (filename)
this.#silhouette.src = `/images/units/${filename}`;
this.#silhouette.classList.toggle("hide", filename == undefined || filename == '');
/* Add the loadout elements */
const items = this.#loadoutContainer.querySelector("#loadout-items") as HTMLElement;
if (items) {
const ammo = Object.values(unit.getAmmo());
if (ammo.length > 0) {
items.replaceChildren(...Object.values(unit.getAmmo()).map(
(ammo: Ammo) => {
var el = document.createElement("div");
el.dataset.qty = `${ammo.quantity}`;
el.dataset.item = ammo.name;
return el;
}
));
} else {
items.innerText = "No loadout";
}
}
}
}
#onUnitsSelection(units: Unit[]) {
if (units.length == 1) {
this.show();
this.#onUnitUpdate(units[0]);
}
else
this.hide();
}
#onUnitsDeselection(units: Unit[]) {
if (units.length == 1) {
this.show();
this.#onUnitUpdate(units[0]);
}
else
this.hide();
}
show() {
const context = getApp().getCurrentContext();
if ( !context.getUseUnitInfoPanel() )
return;
super.show();
}
}

View File

@ -1,191 +0,0 @@
import { getApp } from "..";
import { ShortcutKeyboard } from "../shortcut/shortcut";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";
export class UnitListPanel extends Panel {
#contentElement: HTMLElement;
#currentSortAlgorithm: string = "unitName";
#currentSortDirection: string = "ASC";
#units: Unit[] = [];
#unitNameCache: {[key:string]: string} = {};
#updatesInterval!: ReturnType<typeof setInterval>;
constructor(panelElement: string, contentElement: string) {
super(panelElement);
const getElement = document.getElementById(contentElement);
if (getElement instanceof HTMLElement)
this.#contentElement = getElement;
else
throw new Error(`UnitList: unable to find element "${contentElement}".`);
// Add the header click listener and sorting
[].slice.call(this.getElement().querySelectorAll(".headers > *")).forEach((header: HTMLElement) => {
header.addEventListener("click", (ev: MouseEvent) => {
const el = ev.target;
if (el instanceof HTMLElement) {
const newSort = el.getAttribute("data-sort-field") || "unitName";
if (this.#currentSortAlgorithm === newSort)
this.#currentSortDirection = (this.#currentSortDirection === "ASC") ? "DESC" : "ASC";
else
this.#currentSortDirection = "ASC";
this.#currentSortAlgorithm = newSort;
this.doUpdate();
}
});
});
// Dynamically listen for clicks in order to do stuff with units
this.getElement().addEventListener("click", (ev: MouseEvent) => {
const t = ev.target;
if (t instanceof HTMLElement) {
const el = t.closest( "[data-unit-id]");
if (el instanceof HTMLElement) {
let id: number = Number(el.getAttribute("data-unit-id") || 0);
getApp().getUnitsManager().selectUnit(id);
}
}
});
new ShortcutKeyboard({
"callback": () => { this.toggle() },
"code": "KeyU"
});
this.startUpdates();
}
doUpdate() {
if (!this.getVisible())
return;
this.#contentElement.innerHTML = "";
this.#units = Object.values(getApp().getUnitsManager().getUnits());
if (this.#currentSortAlgorithm === "coalition")
this.#sortUnitsByCoalition();
if (this.#currentSortAlgorithm === "name")
this.#sortUnitsByName();
if (this.#currentSortAlgorithm === "unitName")
this.#sortUnitsByUnitName();
Object.values(this.#units).forEach((unit: Unit) => {
// Exclude dead units
if (!unit.getAlive()) {
return;
}
const name = unit.getName();
if ( this.#unitNameCache.hasOwnProperty( name ) === false ) {
this.#unitNameCache[name] = unit.getDatabase()?.getByName(unit.getName())?.label || unit.getName();
}
this.#contentElement.innerHTML += `
<div class="unit-list-unit" data-unit-id="${unit.ID}" data-value-unitName="${unit.getUnitName()}" data-value-name="${unit.getName()}" data-value-coalition="${unit.getCoalition()}" data-value-human="${unit.getHuman() ? "Human" : "AI"}">
<div><span>${unit.getUnitName()}</span></div>
<div>${this.#unitNameCache[name]}</div>
<div>${unit.getCategory()}</div>
<div>${unit.getCoalition()}</div>
<div>${unit.getHuman() ? "Human" : "AI"}</div>
</div>`;
});
}
getContentElement() {
return this.#contentElement;
}
#sortStringsCompare(str1: string, str2: string) {
if (str1 > str2) {
return (this.#currentSortDirection === "ASC") ? 1 : -1;
} else if (str1 < str2) {
return (this.#currentSortDirection === "ASC") ? -1 : 1;
}
return 0;
}
#sortUnitsByUnitName() {
this.#units.sort((unit1: Unit, unit2: Unit) => {
const str1 = unit1.getUnitName().toLowerCase();
const str2 = unit2.getUnitName().toLowerCase();
return this.#sortStringsCompare(str1, str2);
});
}
#sortUnitsByCategory() {
this.#units.sort((unit1: Unit, unit2: Unit) => {
let str1 = unit1.getCategory();
let str2 = unit2.getCategory();
let cmp = this.#sortStringsCompare(str1, str2);
if (cmp !== 0)
return cmp;
str1 = unit1.getUnitName().toLowerCase();
str2 = unit2.getUnitName().toLowerCase();
return this.#sortStringsCompare(str1, str2);
});
}
#sortUnitsByCoalition() {
this.#units.sort((unit1: Unit, unit2: Unit) => {
let str1 = unit1.getCoalition();
let str2 = unit2.getCoalition();
let cmp = this.#sortStringsCompare(str1, str2);
if (cmp !== 0)
return cmp;
str1 = unit1.getUnitName().toLowerCase();
str2 = unit2.getUnitName().toLowerCase();
return this.#sortStringsCompare(str1, str2);
});
}
#sortUnitsByName() {
this.#units.sort((unit1: Unit, unit2: Unit) => {
const str1 = unit1.getName().toLowerCase();
const str2 = unit2.getName().toLowerCase();
return this.#sortStringsCompare(str1, str2);
});
}
startUpdates() {
this.doUpdate();
this.#updatesInterval = setInterval(() => {
this.doUpdate();
}, 4000);
}
stopUpdates() {
clearInterval(this.#updatesInterval);
}
toggle() {
if (this.getVisible())
this.stopUpdates();
else
this.startUpdates();
super.toggle();
}
}

View File

@ -1,74 +0,0 @@
import path from "path";
import { Manager } from "../other/manager";
import { getApp } from "..";
import { OlympusPlugin } from "../interfaces";
/** The plugins manager is responsible for loading and initializing all the plugins. Plugins are located in the public/plugins folder.
* Each plugin must be comprised of a single folder containing a index.js file. Each plugin must set the globalThis.getOlympusPlugin variable to
* return a valid class implementing the OlympusPlugin interface.
*/
export class PluginsManager extends Manager {
constructor() {
super();
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 */
var plugin: OlympusPlugin | null = null;
try {
eval(xhr.response);
plugin = globalThis.getOlympusPlugin() as OlympusPlugin;
console.log(plugin.getName() + " loaded correctly");
} catch (error: any) {
console.log("An error occured while loading a plugin from " + pluginName);
console.log(error);
}
/* If the plugin was loaded, try to initialize it */
if (plugin != null) {
try {
if (plugin.initialize(getApp())) {
console.log(plugin.getName() + " initialized correctly");
this.add(pluginName, plugin);
}
} catch (error: any) {
console.log("An error occured while initializing a plugin from " + pluginName);
console.log(error);
}
}
} else {
console.error(`Error retrieving plugin from ${pluginName}`)
}
};
xhr.send();
})
}
}

View File

@ -1,58 +0,0 @@
import { Panel } from "../panels/panel";
export class PopupMessage {
#element: HTMLDivElement;
#fadeTime: number = 2000; // Milliseconds
constructor(text: string, fateTime: number) {
this.#element = document.createElement("div");
this.#fadeTime = fateTime;
this.#element.innerText = text;
this.#element.classList.remove("invisible");
this.#element.classList.add("visible");
window.setTimeout(() => {
this.#element.classList.remove("visible");
this.#element.classList.add("invisible");
window.setTimeout(() => {
this.#element.dispatchEvent(new Event("removed"));
this.#element.remove();
}, 2000);
}, this.#fadeTime);
}
getElement() {
return this.#element;
}
}
export class Popup extends Panel {
#fadeTime: number = 2000; // Milliseconds
#messages: PopupMessage[] = [];
#stackAfter: number;
constructor(ID: string, stackAfter: number = 3) {
super(ID);
this.show();
this.#stackAfter = stackAfter;
}
setFadeTime(fadeTime: number) {
this.#fadeTime = fadeTime;
}
setText(text: string) {
var message = new PopupMessage(text, this.#fadeTime);
message.getElement().addEventListener("removed", () => {
var index = this.#messages.indexOf(message);
if (index !== -1)
this.#messages.splice(index, 1);
})
this.getElement().appendChild(message.getElement());
this.#messages.push(message);
if (this.#messages.length > this.#stackAfter) {
this.#messages[this.#messages.length - this.#stackAfter - 1].getElement().classList.add("ol-popup-stack");
}
}
}

View File

@ -1,67 +0,0 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"airfields": {
"type": "object",
"minProperties": 1,
"patternProperties": {
".*": {
"type": "object",
"properties": {
"elevation": {
"type": "string",
"pattern": "^(0|([1-9][0-9]*))?$"
},
"ICAO": {
"type": "string"
},
"runways": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"headings": {
"type": "array",
"items": {
"type": "object",
"patternProperties": {
".*": {
"type": "object",
"properties": {
"ILS": {
"type": "string",
"pattern": "^(1[0-9]{1,2}\\.[0-9][05])?$"
},
"magHeading": {
"type": "string",
"pattern": "^([0-2][0-9]{2})|(3(([0-5][0-9])|(60)))?$"
}
},
"required": ["magHeading"]
}
}
},
"minItems": 1
},
"length": {
"type": "string",
"pattern": "^[1-9][0-9]{3,4}$"
}
},
"required": [ "headings", "length" ]
}
},
"TACAN": {
"type": "string",
"pattern": "^([1-9][0-9]{1,2}X)?$"
}
},
"required": [ "elevation", "runways" ]
}
}
}
},
"required": ["airfields"]
}

View File

@ -1,428 +0,0 @@
{
"$defs": {
"coalitionName": {
"enum": [
"blue",
"neutral",
"red"
],
"type": "string"
},
"lat": {
"maximum": 90,
"minimum": -90,
"type": "number"
},
"lng": {
"maximum": 180,
"minimum": -180,
"type": "number"
},
"vec2": {
"additionalProperties": false,
"properties": {
"lat": {
"$ref": "#/$defs/lat"
},
"lng": {
"$ref": "#/$defs/lng"
}
},
"required": [
"lat",
"lng"
],
"type": "object"
},
"vec3": {
"additionalProperties": false,
"properties": {
"alt": {
"type": "number"
},
"lat": {
"$ref": "#/$defs/lat"
},
"lng": {
"$ref": "#/$defs/lng"
}
},
"required": [
"alt",
"lat",
"lng"
],
"type": "object"
}
},
"patternProperties": {
".*": {
"items": {
"additionalProperties": false,
"properties": {
"activePath": {
"items": {
"$ref": "#/$defs/vec3"
},
"type": "array"
},
"alive": {
"type": "boolean"
},
"ammo": {
"items": {
"additionalProperties": false,
"properties": {
"category": {
"minimum": 0,
"type": "number"
},
"guidance": {
"minimum": 0,
"type": "number"
},
"missileCategory": {
"minimum": 0,
"type": "number"
},
"name": {
"minLength": 3,
"type": "string"
},
"quantity": {
"minimum": 0,
"type": "number"
}
},
"required": [
"quantity",
"name",
"guidance",
"category",
"missileCategory"
],
"type": "object"
},
"type": "array"
},
"category": {
"type": "string"
},
"categoryDisplayName": {
"type": "string"
},
"coalition": {
"$ref": "#/$defs/coalitionName"
},
"contacts": {
"type": "array"
},
"controlled": {
"type": "boolean"
},
"country": {
"type": "number"
},
"desiredAltitude": {
"minimum": 0,
"type": "number"
},
"desiredAltitudeType": {
"enum": [
"AGL",
"ASL"
],
"type": "string"
},
"desiredSpeed": {
"minimum": 0,
"type": "number"
},
"desiredSpeedType": {
"enum": [
"CAS",
"GS"
],
"type": "string"
},
"emissionsCountermeasures": {
"enum": [
"attack",
"defend",
"free",
"silent"
],
"type": "string"
},
"followRoads": {
"type": "boolean"
},
"formationOffset": {
"additionalProperties": false,
"properties": {
"x": {
"minimum": 0,
"type": "number"
},
"y": {
"minimum": 0,
"type": "number"
},
"z": {
"minimum": 0,
"type": "number"
}
},
"required": [
"x",
"y",
"z"
],
"type": "object"
},
"fuel": {
"maximum": 100,
"minimum": 0,
"type": "number"
},
"generalSettings": {
"additionalProperties": false,
"properties": {
"prohibitAA": {
"type": "boolean"
},
"prohibitAfterburner": {
"type": "boolean"
},
"prohibitAG": {
"type": "boolean"
},
"prohibitAirWpn": {
"type": "boolean"
},
"prohibitJettison": {
"type": "boolean"
}
},
"required": [
"prohibitAA",
"prohibitAfterburner",
"prohibitAG",
"prohibitAirWpn",
"prohibitJettison"
],
"type": "object"
},
"groupName": {
"type": "string"
},
"hasTask": {
"type": "boolean"
},
"heading": {
"type": "number"
},
"health": {
"maximum": 100,
"minimum": 0,
"type": "number"
},
"horizontalVelocity": {
"minimum": 0,
"type": "number"
},
"human": {
"type": "boolean"
},
"ID": {
"type": "number"
},
"isActiveAWACS": {
"type": "boolean"
},
"isActiveTanker": {
"type": "boolean"
},
"isLeader": {
"type": "boolean"
},
"leaderID": {
"minimum": 0,
"type": "number"
},
"name": {
"type": "string"
},
"onOff": {
"type": "boolean"
},
"operateAs": {
"$ref": "#/$defs/coalitionName"
},
"position": {
"$ref": "#/$defs/vec3"
},
"radio": {
"additionalProperties": false,
"properties": {
"callsign": {
"type": "number"
},
"callsignNumber": {
"type": "number"
},
"frequency": {
"type": "number"
}
},
"required": [
"frequency",
"callsign",
"callsignNumber"
],
"type": "object"
},
"reactionToThreat": {
"enum": [
"evade",
"maneouvre",
"none",
"passive"
],
"type": "string"
},
"ROE": {
"enum": [
"designated",
"free",
"hold",
"return"
],
"type": "string"
},
"shotsIntensity": {
"maximum": 3,
"minimum": 1,
"type": "number"
},
"shotsScatter": {
"maximum": 3,
"minimum": 1,
"type": "number"
},
"speed": {
"minimum": 0,
"type": "number"
},
"state": {
"type": "string"
},
"TACAN": {
"properties": {
"callsign": {
"type": "string"
},
"channel": {
"minimum": 0,
"type": "number"
},
"isOn": {
"type": "boolean"
},
"XY": {
"enum": [
"X",
"Y"
],
"type": "string"
}
},
"required": [
"callsign",
"channel",
"isOn",
"XY"
],
"type": "object"
},
"targetID": {
"minimum": 0,
"type": "number"
},
"targetPosition": {
"anyOf": [
{"$ref": "#/$defs/vec2"},
{"$ref": "#/$defs/vec3"}
]
},
"task": {
"type": "string"
},
"track": {
"type": "number"
},
"unitName": {
"type": "string"
},
"verticalVelocity": {
"minimum": 0,
"type": "number"
}
},
"type": "object",
"required": [
"activePath",
"alive",
"ammo",
"category",
"categoryDisplayName",
"coalition",
"contacts",
"controlled",
"country",
"desiredAltitude",
"desiredAltitudeType",
"desiredSpeed",
"desiredSpeedType",
"emissionsCountermeasures",
"followRoads",
"formationOffset",
"fuel",
"generalSettings",
"groupName",
"hasTask",
"heading",
"health",
"horizontalVelocity",
"human",
"ID",
"isActiveAWACS",
"isActiveTanker",
"isLeader",
"leaderID",
"name",
"onOff",
"operateAs",
"position",
"radio",
"reactionToThreat",
"ROE",
"shotsIntensity",
"shotsScatter",
"speed",
"state",
"TACAN",
"targetID",
"targetPosition",
"task",
"track",
"unitName",
"verticalVelocity"
]
},
"minItems": 1,
"type": "array"
}
},
"type": "object"
}

View File

@ -1,50 +0,0 @@
import Ajv from "ajv";
import { AnySchemaObject } from "ajv/dist/core";
// For future extension
abstract class JSONSchemaValidator {
#ajv: Ajv;
#compiledValidator: any;
#schema!: AnySchemaObject;
constructor(schema: AnySchemaObject) {
this.#schema = schema;
this.#ajv = new Ajv({
"allErrors": true
});
this.#compiledValidator = this.getAjv().compile(this.getSchema());
}
getAjv() {
return this.#ajv;
}
getCompiledValidator() {
return this.#compiledValidator;
}
getErrors() {
return this.getCompiledValidator().errors;
}
getSchema() {
return this.#schema;
}
validate(data: any) {
return (this.getCompiledValidator())(data);
}
}
export class ImportFileJSONSchemaValidator extends JSONSchemaValidator {
constructor() {
const schema = require("../schemas/importdata.schema.json");
super(schema);
}
}

View File

@ -1,164 +0,0 @@
import { LatLng } from "leaflet";
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../interfaces";
export class DataExtractor {
#seekPosition = 0;
#dataview: DataView;
#decoder: TextDecoder;
#buffer: ArrayBuffer;
constructor(buffer: ArrayBuffer) {
this.#buffer = buffer;
this.#dataview = new DataView(this.#buffer);
this.#decoder = new TextDecoder("utf-8");
}
setSeekPosition(seekPosition: number) {
this.#seekPosition = seekPosition;
}
getSeekPosition() {
return this.#seekPosition;
}
extractBool() {
const value = this.#dataview.getUint8(this.#seekPosition);
this.#seekPosition += 1;
return value > 0;
}
extractUInt8() {
const value = this.#dataview.getUint8(this.#seekPosition);
this.#seekPosition += 1;
return value;
}
extractUInt16() {
const value = this.#dataview.getUint16(this.#seekPosition, true);
this.#seekPosition += 2;
return value;
}
extractUInt32() {
const value = this.#dataview.getUint32(this.#seekPosition, true);
this.#seekPosition += 4;
return value;
}
extractUInt64() {
const value = this.#dataview.getBigUint64(this.#seekPosition, true);
this.#seekPosition += 8;
return value;
}
extractFloat64() {
const value = this.#dataview.getFloat64(this.#seekPosition, true);
this.#seekPosition += 8;
return value;
}
extractLatLng() {
return new LatLng(this.extractFloat64(), this.extractFloat64(), this.extractFloat64())
}
extractFromBitmask(bitmask: number, position: number) {
return ((bitmask >> position) & 1) > 0;
}
extractString(length?: number) {
if (length === undefined)
length = this.extractUInt16()
var stringBuffer = this.#buffer.slice(this.#seekPosition, this.#seekPosition + length);
var view = new Int8Array(stringBuffer);
var stringLength = length;
view.every((value: number, idx: number) => {
if (value === 0) {
stringLength = idx;
return false;
} else
return true;
});
const value = this.#decoder.decode(stringBuffer);
this.#seekPosition += length;
return value.substring(0, stringLength).trim();
}
extractChar() {
return this.extractString(1);
}
extractTACAN() {
const value: TACAN = {
isOn: this.extractBool(),
channel: this.extractUInt8(),
XY: this.extractChar(),
callsign: this.extractString(4)
}
return value;
}
extractRadio() {
const value: Radio = {
frequency: this.extractUInt32(),
callsign: this.extractUInt8(),
callsignNumber: this.extractUInt8()
}
return value;
}
extractGeneralSettings() {
const value: GeneralSettings = {
prohibitJettison: this.extractBool(),
prohibitAA: this.extractBool(),
prohibitAG: this.extractBool(),
prohibitAfterburner: this.extractBool(),
prohibitAirWpn: this.extractBool(),
}
return value;
}
extractAmmo() {
const value: Ammo[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push({
quantity: this.extractUInt16(),
name: this.extractString(33),
guidance: this.extractUInt8(),
category: this.extractUInt8(),
missileCategory: this.extractUInt8()
});
}
return value;
}
extractContacts(){
const value: Contact[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push({
ID: this.extractUInt32(),
detectionMethod: this.extractUInt8()
});
}
return value;
}
extractActivePath() {
const value: LatLng[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push(this.extractLatLng());
}
return value;
}
extractOffset() {
const value: Offset = {
x: this.extractFloat64(),
y: this.extractFloat64(),
z: this.extractFloat64(),
}
return value;
}
}

Some files were not shown because too many files have changed in this diff Show More