mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge branch 'main' into 329-add-basic-automatic-firefight
This commit is contained in:
commit
0ca31afc91
3
.gitignore
vendored
3
.gitignore
vendored
@ -9,4 +9,5 @@ Output
|
||||
node_modules
|
||||
/client/TODO.txt
|
||||
/client/public/javascripts/bundle.js
|
||||
!client/bin
|
||||
!client/bin
|
||||
/client/public/plugins
|
||||
|
||||
@ -29,17 +29,8 @@ declare global {
|
||||
listener: (this: Document, ev: CustomEventMap[K]) => void): void;
|
||||
dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void;
|
||||
}
|
||||
|
||||
function getOlympusPlugin(): OlympusPlugin;
|
||||
}
|
||||
|
||||
export interface ConfigParameters {
|
||||
port: number;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface ContextMenuOption {
|
||||
tooltip: string;
|
||||
src: string;
|
||||
callback: CallableFunction;
|
||||
}
|
||||
|
||||
export { };
|
||||
export { };
|
||||
267
client/@types/olympus.d.ts
vendored
Normal file
267
client/@types/olympus.d.ts
vendored
Normal file
@ -0,0 +1,267 @@
|
||||
interface OlympusPlugin {
|
||||
getName: () => string;
|
||||
initialize: (any) => boolean;
|
||||
}
|
||||
|
||||
declare global {
|
||||
function getOlympusPlugin(): OlympusPlugin;
|
||||
}
|
||||
|
||||
interface ConfigurationOptions {
|
||||
port: number;
|
||||
address: string;
|
||||
}
|
||||
|
||||
interface ContextMenuOption {
|
||||
tooltip: string;
|
||||
src: string;
|
||||
callback: CallableFunction;
|
||||
}
|
||||
|
||||
interface AirbasesData {
|
||||
airbases: { [key: string]: any },
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface BullseyesData {
|
||||
bullseyes: { [key: string]: { latitude: number, longitude: number, coalition: string } },
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface MissionData {
|
||||
mission: {
|
||||
theatre: string,
|
||||
dateAndTime: DateAndTime;
|
||||
commandModeOptions: CommandModeOptions;
|
||||
coalitions: { red: string[], blue: string[] } = { };
|
||||
}
|
||||
time: number;
|
||||
sessionHash: string;
|
||||
}
|
||||
|
||||
interface CommandModeOptions {
|
||||
commandMode: string;
|
||||
restrictSpawns: boolean;
|
||||
restrictToCoalition: boolean;
|
||||
setupTime: number;
|
||||
spawnPoints: {
|
||||
red: number,
|
||||
blue: number
|
||||
},
|
||||
eras: string[]
|
||||
}
|
||||
|
||||
interface DateAndTime {
|
||||
date: { Year: number, Month: number, Day: number };
|
||||
time: { h: number, m: number, s: number };
|
||||
elapsedTime: number;
|
||||
startTime: number;
|
||||
}
|
||||
|
||||
interface LogData {
|
||||
logs: { [key: string]: string },
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface ServerRequestOptions {
|
||||
time?: number;
|
||||
commandHash?: string;
|
||||
}
|
||||
|
||||
interface UnitSpawnTable {
|
||||
unitType: string,
|
||||
location: latlng,
|
||||
altitude?: number,
|
||||
loadout?: string,
|
||||
liveryID: string
|
||||
}
|
||||
|
||||
interface ObjectIconOptions {
|
||||
showState: boolean,
|
||||
showVvi: boolean,
|
||||
showHotgroup: boolean,
|
||||
showUnitIcon: boolean,
|
||||
showShortLabel: boolean,
|
||||
showFuel: boolean,
|
||||
showAmmo: boolean,
|
||||
showSummary: boolean,
|
||||
showCallsign: boolean,
|
||||
rotateToHeading: boolean
|
||||
}
|
||||
|
||||
interface GeneralSettings {
|
||||
prohibitJettison: boolean;
|
||||
prohibitAA: boolean;
|
||||
prohibitAG: boolean;
|
||||
prohibitAfterburner: boolean;
|
||||
prohibitAirWpn: boolean;
|
||||
}
|
||||
|
||||
interface TACAN {
|
||||
isOn: boolean;
|
||||
channel: number;
|
||||
XY: string;
|
||||
callsign: string;
|
||||
}
|
||||
|
||||
interface Radio {
|
||||
frequency: number;
|
||||
callsign: number;
|
||||
callsignNumber: number;
|
||||
}
|
||||
|
||||
interface Ammo {
|
||||
quantity: number,
|
||||
name: string,
|
||||
guidance: number,
|
||||
category: number,
|
||||
missileCategory: number
|
||||
}
|
||||
|
||||
interface Contact {
|
||||
ID: number,
|
||||
detectionMethod: number
|
||||
}
|
||||
|
||||
interface Offset {
|
||||
x: number,
|
||||
y: number,
|
||||
z: number
|
||||
}
|
||||
|
||||
interface UnitData {
|
||||
category: string,
|
||||
ID: number;
|
||||
alive: boolean;
|
||||
human: boolean;
|
||||
controlled: boolean;
|
||||
coalition: string;
|
||||
country: number;
|
||||
name: string;
|
||||
unitName: string;
|
||||
groupName: string;
|
||||
state: string;
|
||||
task: string;
|
||||
hasTask: boolean;
|
||||
position: LatLng;
|
||||
speed: number;
|
||||
heading: number;
|
||||
isTanker: boolean;
|
||||
isAWACS: boolean;
|
||||
onOff: boolean;
|
||||
followRoads: boolean;
|
||||
fuel: number;
|
||||
desiredSpeed: number;
|
||||
desiredSpeedType: string;
|
||||
desiredAltitude: number;
|
||||
desiredAltitudeType: string;
|
||||
leaderID: number;
|
||||
formationOffset: Offset;
|
||||
targetID: number;
|
||||
targetPosition: LatLng;
|
||||
ROE: string;
|
||||
reactionToThreat: string;
|
||||
emissionsCountermeasures: string;
|
||||
TACAN: TACAN;
|
||||
radio: Radio;
|
||||
generalSettings: GeneralSettings;
|
||||
ammo: Ammo[];
|
||||
contacts: Contact[];
|
||||
activePath: LatLng[];
|
||||
isLeader: boolean;
|
||||
}
|
||||
|
||||
interface LoadoutItemBlueprint {
|
||||
name: string;
|
||||
quantity: number;
|
||||
effectiveAgainst?: string;
|
||||
}
|
||||
|
||||
interface LoadoutBlueprint {
|
||||
fuel: number;
|
||||
items: LoadoutItemBlueprint[];
|
||||
roles: string[];
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UnitBlueprint {
|
||||
name: string;
|
||||
coalition: string;
|
||||
era: string;
|
||||
label: string;
|
||||
shortLabel: string;
|
||||
type?: string;
|
||||
range?: string;
|
||||
loadouts?: LoadoutBlueprint[];
|
||||
filename?: string;
|
||||
liveries?: { [key: string]: { name: string, countries: string[] } };
|
||||
cost?: number;
|
||||
}
|
||||
|
||||
interface UnitSpawnOptions {
|
||||
roleType: string;
|
||||
name: string;
|
||||
latlng: LatLng;
|
||||
coalition: string;
|
||||
count: number;
|
||||
country: string;
|
||||
loadout: LoadoutBlueprint | undefined;
|
||||
airbase: Airbase | undefined;
|
||||
liveryID: string | undefined;
|
||||
altitude: number | undefined;
|
||||
}
|
||||
|
||||
interface AirbaseOptions {
|
||||
name: string,
|
||||
position: L.LatLng
|
||||
}
|
||||
|
||||
interface AirbaseChartData {
|
||||
elevation: string,
|
||||
ICAO: string,
|
||||
TACAN: string,
|
||||
runways: AirbaseChartRunwayData[]
|
||||
}
|
||||
|
||||
interface AirbaseChartRunwayData {
|
||||
headings: AirbaseChartRunwayHeadingData[],
|
||||
length: string
|
||||
}
|
||||
|
||||
interface AirbaseChartRunwayHeadingData {
|
||||
[index: string]: {
|
||||
magHeading: string,
|
||||
ILS: string
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
callback: CallableFunction;
|
||||
name?: string
|
||||
}
|
||||
|
||||
interface ShortcutOptions {
|
||||
altKey?: boolean;
|
||||
callback: CallableFunction;
|
||||
ctrlKey?: boolean;
|
||||
name?: string;
|
||||
shiftKey?: boolean;
|
||||
}
|
||||
|
||||
interface KeyboardShortcutOptions extends ShortcutOptions {
|
||||
code: string;
|
||||
event?: "keydown" | "keyup";
|
||||
}
|
||||
|
||||
interface MouseShortcutOptions extends ShortcutOptions {
|
||||
button: number;
|
||||
event: "mousedown" | "mouseup";
|
||||
}
|
||||
|
||||
interface Manager {
|
||||
add: CallableFunction;
|
||||
}
|
||||
@ -10,6 +10,7 @@ var indexRouter = require('./routes/index');
|
||||
var uikitRouter = require('./routes/uikit');
|
||||
var usersRouter = require('./routes/users');
|
||||
var resourcesRouter = require('./routes/resources');
|
||||
var pluginsRouter = require('./routes/plugins');
|
||||
|
||||
var app = express();
|
||||
|
||||
@ -22,6 +23,7 @@ app.use(express.static(path.join(__dirname, 'public')));
|
||||
app.use('/', indexRouter);
|
||||
app.use('/api/atc', atcRouter);
|
||||
app.use('/api/airbases', airbasesRouter);
|
||||
app.use('/plugins', pluginsRouter)
|
||||
app.use('/users', usersRouter);
|
||||
app.use('/uikit', uikitRouter);
|
||||
app.use('/resources', resourcesRouter);
|
||||
|
||||
@ -444,7 +444,7 @@ class DemoDataGenerator {
|
||||
|
||||
var auth = req.get("Authorization");
|
||||
if (auth) {
|
||||
var username = atob(auth.replace("Basic ", "")).split(":")[0];
|
||||
var username = Buffer.from(auth.replace("Basic ", ""), 'base64').toString('binary').split(":")[0];
|
||||
switch (username) {
|
||||
case "admin":
|
||||
ret.mission.commandModeOptions.commandMode = "Game master";
|
||||
|
||||
2558
client/package-lock.json
generated
2558
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -41,8 +41,10 @@
|
||||
"leaflet-path-drag": "*",
|
||||
"leaflet.nauticscale": "^1.1.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"requirejs": "^2.3.6",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tsify": "^5.0.4",
|
||||
"tslib": "latest",
|
||||
"typedoc": "^0.24.8",
|
||||
"typedoc-umlclass": "^0.7.1",
|
||||
"typescript": "^4.9.4",
|
||||
|
||||
5
client/plugins/controltips/copy.bat
Normal file
5
client/plugins/controltips/copy.bat
Normal file
@ -0,0 +1,5 @@
|
||||
mkdir .\\..\\..\\public\\plugins\\controltipsplugin
|
||||
|
||||
copy .\\index.js .\\..\\..\\public\\plugins\\controltipsplugin\\index.js
|
||||
copy .\\plugin.json .\\..\\..\\public\\plugins\\controltipsplugin\\plugin.json
|
||||
copy .\\style.css .\\..\\..\\public\\plugins\\controltipsplugin\\style.css
|
||||
256
client/plugins/controltips/index.js
Normal file
256
client/plugins/controltips/index.js
Normal file
@ -0,0 +1,256 @@
|
||||
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
||||
if (kind === "m") throw new TypeError("Private method is not writable");
|
||||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
||||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
||||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
||||
};
|
||||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
||||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
||||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
||||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
||||
};
|
||||
var _ControlTipsPlugin_instances, _ControlTipsPlugin_element, _ControlTipsPlugin_app, _ControlTipsPlugin_shortcutManager, _ControlTipsPlugin_cursorIsHoveringOverUnit, _ControlTipsPlugin_cursorIsHoveringOverAirbase, _ControlTipsPlugin_updateTips;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ControlTipsPlugin = void 0;
|
||||
const SHOW_CONTROL_TIPS = "Show control tips";
|
||||
class ControlTipsPlugin {
|
||||
constructor() {
|
||||
_ControlTipsPlugin_instances.add(this);
|
||||
_ControlTipsPlugin_element.set(this, void 0);
|
||||
_ControlTipsPlugin_app.set(this, void 0);
|
||||
_ControlTipsPlugin_shortcutManager.set(this, void 0);
|
||||
_ControlTipsPlugin_cursorIsHoveringOverUnit.set(this, false);
|
||||
_ControlTipsPlugin_cursorIsHoveringOverAirbase.set(this, false);
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_element, document.createElement("div"), "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_element, "f").id = "control-tips-panel";
|
||||
document.body.appendChild(__classPrivateFieldGet(this, _ControlTipsPlugin_element, "f"));
|
||||
}
|
||||
getName() {
|
||||
return "Control Tips Plugin";
|
||||
}
|
||||
initialize(app) {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_app, app, "f");
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_shortcutManager, __classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getShortcutManager(), "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_shortcutManager, "f").onKeyDown(() => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_shortcutManager, "f").onKeyUp(() => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("airbaseMouseover", (ev) => {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverAirbase, true, "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("airbaseMouseout", (ev) => {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverAirbase, false, "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("unitDeselection", (ev) => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("unitMouseover", (ev) => {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverUnit, true, "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("unitMouseout", (ev) => {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverUnit, false, "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("unitSelection", (ev) => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("mapVisibilityOptionsChanged", () => {
|
||||
this.toggle(!__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getMap().getVisibilityOptions()[SHOW_CONTROL_TIPS]);
|
||||
});
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getMap().addVisibilityOption(SHOW_CONTROL_TIPS, true);
|
||||
return true;
|
||||
}
|
||||
getElement() {
|
||||
return __classPrivateFieldGet(this, _ControlTipsPlugin_element, "f");
|
||||
}
|
||||
toggle(bool) {
|
||||
this.getElement().classList.toggle("hide", bool);
|
||||
}
|
||||
}
|
||||
exports.ControlTipsPlugin = ControlTipsPlugin;
|
||||
_ControlTipsPlugin_element = new WeakMap(), _ControlTipsPlugin_app = new WeakMap(), _ControlTipsPlugin_shortcutManager = new WeakMap(), _ControlTipsPlugin_cursorIsHoveringOverUnit = new WeakMap(), _ControlTipsPlugin_cursorIsHoveringOverAirbase = new WeakMap(), _ControlTipsPlugin_instances = new WeakSet(), _ControlTipsPlugin_updateTips = function _ControlTipsPlugin_updateTips() {
|
||||
const combos = [
|
||||
{
|
||||
"keys": [],
|
||||
"tips": [
|
||||
{
|
||||
"key": `SHIFT`,
|
||||
"action": `Box select`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"showIfUnitSelected": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": `Deselect`,
|
||||
"showIfUnitSelected": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse1+drag`,
|
||||
"action": `Move map`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"showIfUnitSelected": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Spawn menu`,
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Quick options`,
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Airbase menu`,
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Set first waypoint`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL+Mouse2",
|
||||
"action": "Add waypoint",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2 (hold)`,
|
||||
"action": `Point operations`,
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL",
|
||||
"action": " Pin tool",
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL+Mouse2",
|
||||
"action": " Airbase menu",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Delete`,
|
||||
"action": `Delete unit`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfUnitSelected": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ControlLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle pin",
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle selection",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Add waypoint`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Airbase menu`,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ShiftLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `mouse1+drag`,
|
||||
"action": "Box select"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
const currentCombo = combos.find((combo) => __classPrivateFieldGet(this, _ControlTipsPlugin_shortcutManager, "f").keyComboMatches(combo.keys)) || combos[0];
|
||||
const element = this.getElement();
|
||||
element.innerHTML = "";
|
||||
let numSelectedUnits = 0;
|
||||
let unitSelectionContainsControlled = false;
|
||||
if (__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getUnitsManager()) {
|
||||
let selectedUnits = Object.values(__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getUnitsManager().getSelectedUnits());
|
||||
numSelectedUnits = selectedUnits.length;
|
||||
unitSelectionContainsControlled = selectedUnits.some((unit) => unit.getControlled());
|
||||
}
|
||||
currentCombo.tips.forEach((tip) => {
|
||||
if (numSelectedUnits > 0) {
|
||||
if (tip.showIfUnitSelected === false) {
|
||||
return;
|
||||
}
|
||||
if (tip.unitsMustBeControlled === true && unitSelectionContainsControlled === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (numSelectedUnits === 0 && tip.showIfUnitSelected === true) {
|
||||
return;
|
||||
}
|
||||
if (typeof tip.showIfHoveringOverAirbase === "boolean") {
|
||||
if (tip.showIfHoveringOverAirbase !== __classPrivateFieldGet(this, _ControlTipsPlugin_cursorIsHoveringOverAirbase, "f")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (typeof tip.showIfHoveringOverUnit === "boolean") {
|
||||
if (tip.showIfHoveringOverUnit !== __classPrivateFieldGet(this, _ControlTipsPlugin_cursorIsHoveringOverUnit, "f")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
element.innerHTML += `<div><span class="key">${tip.key}</span><span class="action">${tip.action}</span></div>`;
|
||||
});
|
||||
};
|
||||
|
||||
},{}],2:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const controltips_1 = require("./controltips");
|
||||
globalThis.getOlympusPlugin = () => {
|
||||
return new controltips_1.ControlTipsPlugin();
|
||||
};
|
||||
|
||||
},{"./controltips":1}]},{},[2]);
|
||||
162
client/plugins/controltips/package-lock.json
generated
Normal file
162
client/plugins/controltips/package-lock.json
generated
Normal file
@ -0,0 +1,162 @@
|
||||
{
|
||||
"name": "ControlTipsPlugin",
|
||||
"version": "v0.0.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"any-promise": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="
|
||||
},
|
||||
"convert-source-map": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
||||
"requires": {
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
|
||||
},
|
||||
"is-utf8": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
|
||||
"integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q=="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
|
||||
},
|
||||
"parse-json": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
|
||||
"integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==",
|
||||
"requires": {
|
||||
"error-ex": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"strip-bom": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
|
||||
"integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==",
|
||||
"requires": {
|
||||
"is-utf8": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="
|
||||
},
|
||||
"through2": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
|
||||
"integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
|
||||
"requires": {
|
||||
"readable-stream": "~2.3.6",
|
||||
"xtend": "~4.0.1"
|
||||
}
|
||||
},
|
||||
"tsconfig": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-5.0.3.tgz",
|
||||
"integrity": "sha512-Cq65A3kVp6BbsUgg9DRHafaGmbMb9EhAc7fjWvudNWKjkbWrt43FnrtZt6awshH1R0ocfF2Z0uxock3lVqEgOg==",
|
||||
"requires": {
|
||||
"any-promise": "^1.3.0",
|
||||
"parse-json": "^2.2.0",
|
||||
"strip-bom": "^2.0.0",
|
||||
"strip-json-comments": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"tsify": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/tsify/-/tsify-5.0.4.tgz",
|
||||
"integrity": "sha512-XAZtQ5OMPsJFclkZ9xMZWkSNyMhMxEPsz3D2zu79yoKorH9j/DT4xCloJeXk5+cDhosEibu4bseMVjyPOAyLJA==",
|
||||
"requires": {
|
||||
"convert-source-map": "^1.1.0",
|
||||
"fs.realpath": "^1.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"semver": "^6.1.0",
|
||||
"through2": "^2.0.0",
|
||||
"tsconfig": "^5.0.3"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
10
client/plugins/controltips/package.json
Normal file
10
client/plugins/controltips/package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "ControlTipsPlugin",
|
||||
"version": "v0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {}
|
||||
}
|
||||
6
client/plugins/controltips/plugin.json
Normal file
6
client/plugins/controltips/plugin.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Control Tip Plugin",
|
||||
"version": "0.0.1",
|
||||
"description": "This plugin shows useful control tips on the right side of the screen. The tips change dynamically depending on what the user does",
|
||||
"author": "Peekaboo"
|
||||
}
|
||||
259
client/plugins/controltips/src/controltipsplugin.ts
Normal file
259
client/plugins/controltips/src/controltipsplugin.ts
Normal file
@ -0,0 +1,259 @@
|
||||
const SHOW_CONTROL_TIPS = "Show control tips"
|
||||
|
||||
export class ControlTipsPlugin implements OlympusPlugin {
|
||||
#element: HTMLElement;
|
||||
#app: any;
|
||||
#shortcutManager: any;
|
||||
#cursorIsHoveringOverUnit: boolean = false;
|
||||
#cursorIsHoveringOverAirbase: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.#element = document.createElement("div");
|
||||
this.#element.id = "control-tips-panel";
|
||||
document.body.appendChild(this.#element);
|
||||
}
|
||||
|
||||
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: CustomEvent) => {
|
||||
this.#updateTips();
|
||||
});
|
||||
|
||||
document.addEventListener("unitMouseover", (ev: CustomEventInit) => {
|
||||
this.#cursorIsHoveringOverUnit = true;
|
||||
this.#updateTips();
|
||||
});
|
||||
|
||||
document.addEventListener("unitMouseout", (ev: CustomEventInit) => {
|
||||
this.#cursorIsHoveringOverUnit = false;
|
||||
this.#updateTips();
|
||||
});
|
||||
|
||||
document.addEventListener("unitSelection", (ev: CustomEvent) => {
|
||||
this.#updateTips()
|
||||
});
|
||||
|
||||
document.addEventListener("mapVisibilityOptionsChanged", () => {
|
||||
this.toggle( !this.#app.getMap().getVisibilityOptions()[SHOW_CONTROL_TIPS] );
|
||||
});
|
||||
|
||||
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": `Quick options`,
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Airbase menu`,
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Set first waypoint`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL+Mouse2",
|
||||
"action": "Add waypoint",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2 (hold)`,
|
||||
"action": `Point operations`,
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL",
|
||||
"action": " Pin tool",
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL+Mouse2",
|
||||
"action": " Airbase menu",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Delete`,
|
||||
"action": `Delete unit`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfUnitSelected": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ControlLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle pin",
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle selection",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Add waypoint`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Airbase menu`,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ShiftLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `mouse1+drag`,
|
||||
"action": "Box select"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const currentCombo: any = combos.find((combo: any) => this.#shortcutManager.keyComboMatches(combo.keys)) || combos[0];
|
||||
|
||||
const element = this.getElement();
|
||||
|
||||
element.innerHTML = "";
|
||||
|
||||
let numSelectedUnits = 0;
|
||||
let unitSelectionContainsControlled = false;
|
||||
|
||||
if (this.#app.getUnitsManager()) {
|
||||
let selectedUnits = Object.values(this.#app.getUnitsManager().getSelectedUnits());
|
||||
numSelectedUnits = selectedUnits.length;
|
||||
unitSelectionContainsControlled = selectedUnits.some((unit: any) => unit.getControlled());
|
||||
}
|
||||
|
||||
currentCombo.tips.forEach((tip: any) => {
|
||||
if (numSelectedUnits > 0) {
|
||||
if (tip.showIfUnitSelected === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tip.unitsMustBeControlled === true && unitSelectionContainsControlled === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (numSelectedUnits === 0 && tip.showIfUnitSelected === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof tip.showIfHoveringOverAirbase === "boolean") {
|
||||
if (tip.showIfHoveringOverAirbase !== this.#cursorIsHoveringOverAirbase) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof tip.showIfHoveringOverUnit === "boolean") {
|
||||
if (tip.showIfHoveringOverUnit !== this.#cursorIsHoveringOverUnit) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
element.innerHTML += `<div><span class="key">${tip.key}</span><span class="action">${tip.action}</span></div>`
|
||||
});
|
||||
}
|
||||
}
|
||||
5
client/plugins/controltips/src/index.ts
Normal file
5
client/plugins/controltips/src/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { ControlTipsPlugin } from "./controltipsplugin";
|
||||
|
||||
globalThis.getOlympusPlugin = () => {
|
||||
return new ControlTipsPlugin();
|
||||
}
|
||||
33
client/plugins/controltips/style.css
Normal file
33
client/plugins/controltips/style.css
Normal file
@ -0,0 +1,33 @@
|
||||
#control-tips-panel {
|
||||
align-self: center;
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
font-size: 13px;
|
||||
justify-self: flex-end;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
row-gap: 20px;
|
||||
text-align: right;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
#control-tips-panel>* {
|
||||
align-items: center;
|
||||
align-self: end;
|
||||
background-color: var(--background-steel);
|
||||
border-radius: var(--border-radius-md);
|
||||
color: white;
|
||||
column-gap: 8px;
|
||||
display: flex;
|
||||
justify-items: right;
|
||||
opacity: .9;
|
||||
padding: 5px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
#control-tips-panel>*>.key {
|
||||
background-color: var(--background-grey);
|
||||
border-radius: var(--border-radius-sm);
|
||||
color: white;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
104
client/plugins/controltips/tsconfig.json
Normal file
104
client/plugins/controltips/tsconfig.json
Normal file
@ -0,0 +1,104 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
/* Language and Environment */
|
||||
"target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"rootDirs": ["./src", "../../@types"], /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"../../@types"
|
||||
], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"types": [
|
||||
"olympus"
|
||||
], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
/* JavaScript Support */
|
||||
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": [
|
||||
"src/*.ts",
|
||||
"../../@types/*.d.ts"
|
||||
]
|
||||
}
|
||||
@ -530,7 +530,7 @@ nav.ol-panel> :last-child {
|
||||
}
|
||||
|
||||
.active .ol-slider::-webkit-slider-thumb {
|
||||
background: radial-gradient(circle at center, var(--accent-light-blue), var(--accent-light-blue) 40%, color-mix(in srgb, var(--accent-light-blue), transparent 66%) 50%);
|
||||
background: radial-gradient(circle at center, var(--accent-light-blue), var(--accent-light-blue) 40%, var(--transparent-accent-light-blue) 50%);
|
||||
}
|
||||
|
||||
.ol-slider::-moz-range-thumb {
|
||||
@ -545,7 +545,7 @@ nav.ol-panel> :last-child {
|
||||
|
||||
.active .ol-slider::-moz-range-thumb {
|
||||
-moz-appearance: none;
|
||||
background: radial-gradient(circle at center, var(--accent-light-blue), var(--accent-light-blue) 40%, color-mix(in srgb, var(--accent-light-blue), transparent 66%) 50%);
|
||||
background: radial-gradient(circle at center, var(--accent-light-blue), var(--accent-light-blue) 40%, var(--transparent-accent-light-blue) 50%);
|
||||
}
|
||||
|
||||
.ol-slider-min-max {
|
||||
@ -653,17 +653,6 @@ nav.ol-panel> :last-child {
|
||||
stroke: var(--background-steel) !important;
|
||||
}
|
||||
|
||||
#atc-navbar-control {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#atc-navbar-control button svg {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
#roe-buttons-container button,
|
||||
#reaction-to-threat-buttons-container button,
|
||||
#emissions-countermeasures-buttons-container button {
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
/*** UI Colours **/
|
||||
--accent-green: #8bff63;
|
||||
--accent-light-blue: #5ca7ff;
|
||||
--transparent-accent-light-blue: rgba(92, 167, 255, .33);
|
||||
--accent-light-red: #F5B6B6;
|
||||
|
||||
--background-grey: #3d4651;
|
||||
|
||||
@ -1,82 +1,65 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
const bodyParser = require('body-parser');
|
||||
app.use(bodyParser.urlencoded({ extended: false}));
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Flight:
|
||||
"name"
|
||||
"take-off time"
|
||||
"priority"
|
||||
"status"
|
||||
|
||||
//*/
|
||||
|
||||
function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function Flight( name, boardId, unitId ) {
|
||||
function Flight(name, boardId, unitId) {
|
||||
this.assignedAltitude = 0;
|
||||
this.assignedSpeed = 0;
|
||||
this.id = uuidv4();
|
||||
this.boardId = boardId;
|
||||
this.name = name;
|
||||
this.status = "unknown";
|
||||
this.takeoffTime = -1;
|
||||
this.unitId = parseInt( unitId );
|
||||
this.assignedSpeed = 0;
|
||||
this.id = uuidv4();
|
||||
this.boardId = boardId;
|
||||
this.name = name;
|
||||
this.status = "unknown";
|
||||
this.takeoffTime = -1;
|
||||
this.unitId = parseInt(unitId);
|
||||
}
|
||||
|
||||
Flight.prototype.getData = function() {
|
||||
Flight.prototype.getData = function () {
|
||||
return {
|
||||
"assignedAltitude" : this.assignedAltitude,
|
||||
"assignedSpeed" : this.assignedSpeed,
|
||||
"id" : this.id,
|
||||
"boardId" : this.boardId,
|
||||
"name" : this.name,
|
||||
"status" : this.status,
|
||||
"takeoffTime" : this.takeoffTime,
|
||||
"unitId" : this.unitId
|
||||
"assignedAltitude": this.assignedAltitude,
|
||||
"assignedSpeed": this.assignedSpeed,
|
||||
"id": this.id,
|
||||
"boardId": this.boardId,
|
||||
"name": this.name,
|
||||
"status": this.status,
|
||||
"takeoffTime": this.takeoffTime,
|
||||
"unitId": this.unitId
|
||||
};
|
||||
}
|
||||
|
||||
Flight.prototype.setAssignedAltitude = function (assignedAltitude) {
|
||||
|
||||
Flight.prototype.setAssignedAltitude = function( assignedAltitude ) {
|
||||
|
||||
if ( isNaN( assignedAltitude ) ) {
|
||||
if (isNaN(assignedAltitude)) {
|
||||
return "Altitude must be a number"
|
||||
}
|
||||
|
||||
this.assignedAltitude = parseInt( assignedAltitude );
|
||||
this.assignedAltitude = parseInt(assignedAltitude);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
Flight.prototype.setAssignedSpeed = function (assignedSpeed) {
|
||||
|
||||
Flight.prototype.setAssignedSpeed = function( assignedSpeed ) {
|
||||
|
||||
if ( isNaN( assignedSpeed ) ) {
|
||||
if (isNaN(assignedSpeed)) {
|
||||
return "Speed must be a number"
|
||||
}
|
||||
|
||||
this.assignedSpeed = parseInt( assignedSpeed );
|
||||
this.assignedSpeed = parseInt(assignedSpeed);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
Flight.prototype.setOrder = function( order ) {
|
||||
Flight.prototype.setOrder = function (order) {
|
||||
|
||||
this.order = order;
|
||||
|
||||
@ -84,10 +67,9 @@ Flight.prototype.setOrder = function( order ) {
|
||||
|
||||
}
|
||||
|
||||
Flight.prototype.setStatus = function (status) {
|
||||
|
||||
Flight.prototype.setStatus = function( status ) {
|
||||
|
||||
if ( [ "unknown", "checkedin", "readytotaxi", "clearedtotaxi", "halted", "terminated" ].indexOf( status ) < 0 ) {
|
||||
if (["unknown", "checkedin", "readytotaxi", "clearedtotaxi", "halted", "terminated"].indexOf(status) < 0) {
|
||||
return "Invalid status";
|
||||
}
|
||||
|
||||
@ -97,197 +79,186 @@ Flight.prototype.setStatus = function( status ) {
|
||||
|
||||
}
|
||||
|
||||
Flight.prototype.setTakeoffTime = function (takeoffTime) {
|
||||
|
||||
Flight.prototype.setTakeoffTime = function( takeoffTime ) {
|
||||
|
||||
if ( takeoffTime === "" || takeoffTime === -1 ) {
|
||||
if (takeoffTime === "" || takeoffTime === -1) {
|
||||
this.takeoffTime = -1;
|
||||
}
|
||||
|
||||
if ( isNaN( takeoffTime ) ) {
|
||||
if (isNaN(takeoffTime)) {
|
||||
return "Invalid takeoff time"
|
||||
}
|
||||
|
||||
this.takeoffTime = parseInt( takeoffTime );
|
||||
this.takeoffTime = parseInt(takeoffTime);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ATCDataHandler( data ) {
|
||||
function ATCDataHandler(data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
ATCDataHandler.prototype.addFlight = function( flight ) {
|
||||
ATCDataHandler.prototype.addFlight = function (flight) {
|
||||
|
||||
if ( flight instanceof Flight === false ) {
|
||||
throw new Error( "Given flight is not an instance of Flight" );
|
||||
if (flight instanceof Flight === false) {
|
||||
throw new Error("Given flight is not an instance of Flight");
|
||||
}
|
||||
|
||||
this.data.flights[ flight.id ] = flight;
|
||||
this.data.flights[flight.id] = flight;
|
||||
|
||||
}
|
||||
|
||||
|
||||
ATCDataHandler.prototype.deleteFlight = function( flightId ) {
|
||||
delete this.data.flights[ flightId ];
|
||||
ATCDataHandler.prototype.deleteFlight = function (flightId) {
|
||||
delete this.data.flights[flightId];
|
||||
}
|
||||
|
||||
|
||||
ATCDataHandler.prototype.getFlight = function( flightId ) {
|
||||
return this.data.flights[ flightId ] || false;
|
||||
ATCDataHandler.prototype.getFlight = function (flightId) {
|
||||
return this.data.flights[flightId] || false;
|
||||
}
|
||||
|
||||
|
||||
ATCDataHandler.prototype.getFlights = function() {
|
||||
ATCDataHandler.prototype.getFlights = function () {
|
||||
return this.data.flights;
|
||||
}
|
||||
|
||||
|
||||
const dataHandler = new ATCDataHandler( {
|
||||
const dataHandler = new ATCDataHandler({
|
||||
"flights": {}
|
||||
} );
|
||||
|
||||
|
||||
});
|
||||
|
||||
/**************************************************************************************************************/
|
||||
// Endpoints
|
||||
/**************************************************************************************************************/
|
||||
app.get("/flight", (req, res) => {
|
||||
|
||||
let flights = Object.values(dataHandler.getFlights());
|
||||
|
||||
app.get( "/flight", ( req, res ) => {
|
||||
if (flights && req.query.boardId) {
|
||||
|
||||
let flights = Object.values( dataHandler.getFlights() );
|
||||
|
||||
if ( flights && req.query.boardId ) {
|
||||
|
||||
flights = flights.reduce( ( acc, flight ) => {
|
||||
if ( flight.boardId === req.query.boardId ) {
|
||||
acc[ flight.id ] = flight;
|
||||
flights = flights.reduce((acc, flight) => {
|
||||
if (flight.boardId === req.query.boardId) {
|
||||
acc[flight.id] = flight;
|
||||
}
|
||||
return acc;
|
||||
}, {} );
|
||||
}, {});
|
||||
|
||||
}
|
||||
|
||||
res.json( flights );
|
||||
res.json(flights);
|
||||
|
||||
});
|
||||
|
||||
|
||||
app.patch( "/flight/:flightId", ( req, res ) => {
|
||||
app.patch("/flight/:flightId", (req, res) => {
|
||||
|
||||
const flightId = req.params.flightId;
|
||||
const flight = dataHandler.getFlight( flightId );
|
||||
const flight = dataHandler.getFlight(flightId);
|
||||
|
||||
if ( !flight ) {
|
||||
res.status( 400 ).send( `Unrecognised flight ID (given: "${req.params.flightId}")` );
|
||||
if (!flight) {
|
||||
res.status(400).send(`Unrecognised flight ID (given: "${req.params.flightId}")`);
|
||||
}
|
||||
|
||||
if ( req.body.hasOwnProperty( "assignedAltitude" ) ) {
|
||||
if (req.body.hasOwnProperty("assignedAltitude")) {
|
||||
|
||||
const altitudeChangeSuccess = flight.setAssignedAltitude( req.body.assignedAltitude );
|
||||
const altitudeChangeSuccess = flight.setAssignedAltitude(req.body.assignedAltitude);
|
||||
|
||||
if ( altitudeChangeSuccess !== true ) {
|
||||
res.status( 400 ).send( altitudeChangeSuccess );
|
||||
if (altitudeChangeSuccess !== true) {
|
||||
res.status(400).send(altitudeChangeSuccess);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( req.body.hasOwnProperty( "assignedSpeed" ) ) {
|
||||
if (req.body.hasOwnProperty("assignedSpeed")) {
|
||||
|
||||
const speedChangeSuccess = flight.setAssignedSpeed( req.body.assignedSpeed );
|
||||
const speedChangeSuccess = flight.setAssignedSpeed(req.body.assignedSpeed);
|
||||
|
||||
if ( speedChangeSuccess !== true ) {
|
||||
res.status( 400 ).send( speedChangeSuccess );
|
||||
if (speedChangeSuccess !== true) {
|
||||
res.status(400).send(speedChangeSuccess);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( req.body.status ) {
|
||||
if (req.body.status) {
|
||||
|
||||
const statusChangeSuccess = flight.setStatus( req.body.status );
|
||||
const statusChangeSuccess = flight.setStatus(req.body.status);
|
||||
|
||||
if ( statusChangeSuccess !== true ) {
|
||||
res.status( 400 ).send( statusChangeSuccess );
|
||||
if (statusChangeSuccess !== true) {
|
||||
res.status(400).send(statusChangeSuccess);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( req.body.hasOwnProperty( "takeoffTime" ) ) {
|
||||
if (req.body.hasOwnProperty("takeoffTime")) {
|
||||
|
||||
const takeoffChangeSuccess = flight.setTakeoffTime( req.body.takeoffTime );
|
||||
const takeoffChangeSuccess = flight.setTakeoffTime(req.body.takeoffTime);
|
||||
|
||||
if ( takeoffChangeSuccess !== true ) {
|
||||
res.status( 400 ).send( takeoffChangeSuccess );
|
||||
if (takeoffChangeSuccess !== true) {
|
||||
res.status(400).send(takeoffChangeSuccess);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
res.json( flight.getData() );
|
||||
res.json(flight.getData());
|
||||
|
||||
});
|
||||
|
||||
|
||||
app.post( "/flight/order", ( req, res ) => {
|
||||
app.post("/flight/order", (req, res) => {
|
||||
|
||||
if ( !req.body.boardId ) {
|
||||
res.status( 400 ).send( "Invalid/missing boardId" );
|
||||
if (!req.body.boardId) {
|
||||
res.status(400).send("Invalid/missing boardId");
|
||||
}
|
||||
|
||||
if ( !req.body.order || !Array.isArray( req.body.order ) ) {
|
||||
res.status( 400 ).send( "Invalid/missing boardId" );
|
||||
if (!req.body.order || !Array.isArray(req.body.order)) {
|
||||
res.status(400).send("Invalid/missing boardId");
|
||||
}
|
||||
|
||||
req.body.order.forEach( ( flightId, i ) => {
|
||||
req.body.order.forEach((flightId, i) => {
|
||||
|
||||
dataHandler.getFlight( flightId ).setOrder( i );
|
||||
dataHandler.getFlight(flightId).setOrder(i);
|
||||
|
||||
});
|
||||
|
||||
res.send( "" );
|
||||
res.send("");
|
||||
|
||||
});
|
||||
|
||||
|
||||
app.post( "/flight", ( req, res ) => {
|
||||
app.post("/flight", (req, res) => {
|
||||
|
||||
if ( !req.body.boardId ) {
|
||||
res.status( 400 ).send( "Invalid/missing boardId" );
|
||||
if (!req.body.boardId) {
|
||||
res.status(400).send("Invalid/missing boardId");
|
||||
}
|
||||
|
||||
if ( !req.body.name ) {
|
||||
res.status( 400 ).send( "Invalid/missing flight name" );
|
||||
if (!req.body.name) {
|
||||
res.status(400).send("Invalid/missing flight name");
|
||||
}
|
||||
|
||||
if ( !req.body.unitId || isNaN( req.body.unitId ) ) {
|
||||
res.status( 400 ).send( "Invalid/missing unitId" );
|
||||
if (!req.body.unitId || isNaN(req.body.unitId)) {
|
||||
res.status(400).send("Invalid/missing unitId");
|
||||
}
|
||||
|
||||
const flight = new Flight( req.body.name, req.body.boardId, req.body.unitId );
|
||||
const flight = new Flight(req.body.name, req.body.boardId, req.body.unitId);
|
||||
|
||||
dataHandler.addFlight( flight );
|
||||
dataHandler.addFlight(flight);
|
||||
|
||||
res.status( 201 );
|
||||
res.status(201);
|
||||
|
||||
res.json( flight.getData() );
|
||||
res.json(flight.getData());
|
||||
|
||||
});
|
||||
|
||||
|
||||
app.delete( "/flight/:flightId", ( req, res ) => {
|
||||
app.delete("/flight/:flightId", (req, res) => {
|
||||
|
||||
const flight = dataHandler.getFlight( req.params.flightId );
|
||||
const flight = dataHandler.getFlight(req.params.flightId);
|
||||
|
||||
if ( !flight ) {
|
||||
res.status( 400 ).send( `Unrecognised flight ID (given: "${req.params.flightId}")` );
|
||||
if (!flight) {
|
||||
res.status(400).send(`Unrecognised flight ID (given: "${req.params.flightId}")`);
|
||||
}
|
||||
|
||||
dataHandler.deleteFlight( req.params.flightId );
|
||||
dataHandler.deleteFlight(req.params.flightId);
|
||||
|
||||
res.status( 204 ).send( "" );
|
||||
res.status(204).send("");
|
||||
|
||||
});
|
||||
|
||||
|
||||
23
client/routes/plugins.js
Normal file
23
client/routes/plugins.js
Normal file
@ -0,0 +1,23 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const pluginsDirectory = "./public/plugins"
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function listDirectories(source) {
|
||||
const directories = fs.readdirSync(source, { withFileTypes: true })
|
||||
.filter(dirent => dirent.isDirectory())
|
||||
.map(dirent => dirent.name);
|
||||
|
||||
return directories;
|
||||
}
|
||||
|
||||
router.get('/list', function (req, res) {
|
||||
var directories = listDirectories(pluginsDirectory);
|
||||
console.log(directories)
|
||||
res.send(directories.filter(directory => fs.existsSync(path.join(pluginsDirectory, directory))));
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@ -1,6 +1,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// TODO should be user selectable or at least configurable from configuration file
|
||||
var theme = "olympus";
|
||||
|
||||
router.get('/theme/*', function (req, res, next) {
|
||||
|
||||
52
client/src/@types/server.d.ts
vendored
52
client/src/@types/server.d.ts
vendored
@ -1,52 +0,0 @@
|
||||
interface AirbasesData {
|
||||
airbases: {[key: string]: any},
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface BullseyesData {
|
||||
bullseyes: {[key: string]: {latitude: number, longitude: number, coalition: string}},
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface MissionData {
|
||||
mission: {
|
||||
theatre: string,
|
||||
dateAndTime: DateAndTime;
|
||||
commandModeOptions: CommandModeOptions;
|
||||
coalitions: {red: string[], blue: string[]} = {};
|
||||
}
|
||||
time: number;
|
||||
sessionHash: string;
|
||||
}
|
||||
|
||||
interface CommandModeOptions {
|
||||
commandMode: string;
|
||||
restrictSpawns: boolean;
|
||||
restrictToCoalition: boolean;
|
||||
setupTime: number;
|
||||
spawnPoints: {
|
||||
red: number,
|
||||
blue: number
|
||||
},
|
||||
eras: string[]
|
||||
}
|
||||
|
||||
interface DateAndTime {
|
||||
date: {Year: number, Month: number, Day: number};
|
||||
time: {h: number, m: number, s: number};
|
||||
elapsedTime: number;
|
||||
startTime: number;
|
||||
}
|
||||
|
||||
interface LogData {
|
||||
logs: {[key: string]: string},
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface ServerRequestOptions {
|
||||
time?: number;
|
||||
commandHash?: string;
|
||||
}
|
||||
104
client/src/@types/unit.d.ts
vendored
104
client/src/@types/unit.d.ts
vendored
@ -1,104 +0,0 @@
|
||||
import { LatLng } from "leaflet"
|
||||
|
||||
interface UnitSpawnTable {
|
||||
unitType: string,
|
||||
location: latlng,
|
||||
altitude?: number,
|
||||
loadout?: string,
|
||||
liveryID: string
|
||||
}
|
||||
|
||||
interface ObjectIconOptions {
|
||||
showState: boolean,
|
||||
showVvi: boolean,
|
||||
showHotgroup: boolean,
|
||||
showUnitIcon: boolean,
|
||||
showShortLabel: boolean,
|
||||
showFuel: boolean,
|
||||
showAmmo: boolean,
|
||||
showSummary: boolean,
|
||||
showCallsign: boolean,
|
||||
rotateToHeading: boolean
|
||||
}
|
||||
|
||||
interface GeneralSettings {
|
||||
prohibitJettison: boolean;
|
||||
prohibitAA: boolean;
|
||||
prohibitAG: boolean;
|
||||
prohibitAfterburner: boolean;
|
||||
prohibitAirWpn: boolean;
|
||||
}
|
||||
|
||||
interface TACAN {
|
||||
isOn: boolean;
|
||||
channel: number;
|
||||
XY: string;
|
||||
callsign: string;
|
||||
}
|
||||
|
||||
interface Radio {
|
||||
frequency: number;
|
||||
callsign: number;
|
||||
callsignNumber: number;
|
||||
}
|
||||
|
||||
interface Ammo {
|
||||
quantity: number,
|
||||
name: string,
|
||||
guidance: number,
|
||||
category: number,
|
||||
missileCategory: number
|
||||
}
|
||||
|
||||
interface Contact {
|
||||
ID: number,
|
||||
detectionMethod: number
|
||||
}
|
||||
|
||||
interface Offset {
|
||||
x: number,
|
||||
y: number,
|
||||
z: number
|
||||
}
|
||||
|
||||
interface UnitData {
|
||||
category: string,
|
||||
ID: number;
|
||||
alive: boolean;
|
||||
human: boolean;
|
||||
controlled: boolean;
|
||||
coalition: string;
|
||||
country: number;
|
||||
name: string;
|
||||
unitName: string;
|
||||
groupName: string;
|
||||
state: string;
|
||||
task: string;
|
||||
hasTask: boolean;
|
||||
position: LatLng;
|
||||
speed: number;
|
||||
heading: number;
|
||||
isTanker: boolean;
|
||||
isAWACS: boolean;
|
||||
onOff: boolean;
|
||||
followRoads: boolean;
|
||||
fuel: number;
|
||||
desiredSpeed: number;
|
||||
desiredSpeedType: string;
|
||||
desiredAltitude: number;
|
||||
desiredAltitudeType: string;
|
||||
leaderID: number;
|
||||
formationOffset: Offset;
|
||||
targetID: number;
|
||||
targetPosition: LatLng;
|
||||
ROE: string;
|
||||
reactionToThreat: string;
|
||||
emissionsCountermeasures: string;
|
||||
TACAN: TACAN;
|
||||
radio: Radio;
|
||||
generalSettings: GeneralSettings;
|
||||
ammo: Ammo[];
|
||||
contacts: Contact[];
|
||||
activePath: LatLng[];
|
||||
isLeader: boolean;
|
||||
}
|
||||
43
client/src/@types/unitdatabase.d.ts
vendored
43
client/src/@types/unitdatabase.d.ts
vendored
@ -1,43 +0,0 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
|
||||
interface LoadoutItemBlueprint {
|
||||
name: string;
|
||||
quantity: number;
|
||||
effectiveAgainst?: string;
|
||||
}
|
||||
|
||||
interface LoadoutBlueprint {
|
||||
fuel: number;
|
||||
items: LoadoutItemBlueprint[];
|
||||
roles: string[];
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UnitBlueprint {
|
||||
name: string;
|
||||
coalition: string;
|
||||
era: string;
|
||||
label: string;
|
||||
shortLabel: string;
|
||||
type?: string;
|
||||
range?: string;
|
||||
loadouts?: LoadoutBlueprint[];
|
||||
filename?: string;
|
||||
liveries?: {[key: string]: {name: string, countries: string[]}};
|
||||
cost?: number;
|
||||
}
|
||||
|
||||
interface UnitSpawnOptions {
|
||||
roleType: string;
|
||||
name: string;
|
||||
latlng: LatLng;
|
||||
coalition: string;
|
||||
count: number;
|
||||
country: string;
|
||||
loadout: LoadoutBlueprint | undefined;
|
||||
airbase: Airbase | undefined;
|
||||
liveryID: string | undefined;
|
||||
altitude: number | undefined;
|
||||
}
|
||||
290
client/src/app.ts
Normal file
290
client/src/app.ts
Normal file
@ -0,0 +1,290 @@
|
||||
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 { ShortcutKeyboard } from "./shortcut/shortcut";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { ServerManager } from "./server/servermanager";
|
||||
|
||||
import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants";
|
||||
|
||||
export class OlympusApp {
|
||||
/* Global data */
|
||||
#activeCoalition: string = "blue";
|
||||
|
||||
/* Main leaflet map, extended by custom methods */
|
||||
#map: Map | null = null;
|
||||
|
||||
/* Managers */
|
||||
#serverManager: ServerManager | null = null;
|
||||
#unitsManager: UnitsManager | null = null;
|
||||
#weaponsManager: WeaponsManager | null = null;
|
||||
#missionManager: MissionManager | null = null;
|
||||
#pluginsManager: PluginsManager | null = null;
|
||||
#panelsManager: Manager | null = null;
|
||||
#popupsManager: Manager | null = null;
|
||||
#toolbarsManager: Manager | null = null;
|
||||
#shortcutManager: ShortcutManager | null = null;
|
||||
|
||||
/* UI Toolbars */
|
||||
#primaryToolbar: PrimaryToolbar | null = null;
|
||||
#commandModeToolbar: CommandModeToolbar | null = null;
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
// TODO add checks on null
|
||||
getMap() {
|
||||
return this.#map as Map;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The active coalition
|
||||
*/
|
||||
getActiveCoalition() {
|
||||
if (this.getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER)
|
||||
return this.#activeCoalition;
|
||||
else {
|
||||
if (this.getMissionManager().getCommandModeOptions().commandMode == BLUE_COMMANDER)
|
||||
return "blue";
|
||||
else if (this.getMissionManager().getCommandModeOptions().commandMode == RED_COMMANDER)
|
||||
return "red";
|
||||
else
|
||||
return "neutral";
|
||||
}
|
||||
}
|
||||
|
||||
/** Set a message in the login splash screen
|
||||
*
|
||||
* @param status The message to show in the login splash screen
|
||||
*/
|
||||
setLoginStatus(status: string) {
|
||||
const el = document.querySelector("#login-status") as HTMLElement;
|
||||
if (el)
|
||||
el.dataset["status"] = status;
|
||||
}
|
||||
|
||||
start() {
|
||||
/* Initialize base functionalitites */
|
||||
this.#map = new Map('map-container');
|
||||
|
||||
this.#serverManager = new ServerManager();
|
||||
this.#unitsManager = new UnitsManager();
|
||||
this.#weaponsManager = new WeaponsManager();
|
||||
this.#missionManager = new MissionManager();
|
||||
|
||||
this.#shortcutManager = new ShortcutManager();
|
||||
|
||||
this.#panelsManager = new Manager();
|
||||
this.#popupsManager = new Manager();
|
||||
this.#toolbarsManager = new Manager();
|
||||
|
||||
// Panels
|
||||
this.getPanelsManager()
|
||||
.add("connectionStatus", new ConnectionStatusPanel("connection-status-panel"))
|
||||
.add("hotgroup", new HotgroupPanel("hotgroup-panel"))
|
||||
.add("mouseInfo", new MouseInfoPanel("mouse-info-panel"))
|
||||
.add("log", new LogPanel("log-panel"))
|
||||
.add("serverStatus", new ServerStatusPanel("server-status-panel"))
|
||||
.add("unitControl", new UnitControlPanel("unit-control-panel"))
|
||||
.add("unitInfo", new UnitInfoPanel("unit-info-panel"))
|
||||
|
||||
// Popups
|
||||
this.getPopupsManager().add("infoPopup", new Popup("info-popup"));
|
||||
|
||||
// Toolbars
|
||||
this.getToolbarsManager().add("primaryToolbar", new PrimaryToolbar("primary-toolbar"))
|
||||
.add("commandModeToolbar", new PrimaryToolbar("command-mode-toolbar"));
|
||||
|
||||
this.#pluginsManager = new PluginsManager();
|
||||
|
||||
/* Load the config file from the app server*/
|
||||
this.getServerManager().getConfig((config: ConfigurationOptions) => {
|
||||
if (config && config.address != undefined && config.port != undefined) {
|
||||
const address = config.address;
|
||||
const port = config.port;
|
||||
if (typeof address === 'string' && typeof port == 'number') {
|
||||
this.getServerManager().setAddress(address == "*" ? window.location.hostname : address, port);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error('Could not read configuration file');
|
||||
}
|
||||
});
|
||||
|
||||
/* Setup all global events */
|
||||
this.#setupEvents();
|
||||
}
|
||||
|
||||
#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.add("toggleDemo", new ShortcutKeyboard({
|
||||
"callback": () => {
|
||||
this.getServerManager().toggleDemoEnabled();
|
||||
},
|
||||
"code": "KeyT"
|
||||
})).add("togglePause", new ShortcutKeyboard({
|
||||
"altKey": false,
|
||||
"callback": () => {
|
||||
this.getServerManager().setPaused(!this.getServerManager().getPaused());
|
||||
},
|
||||
"code": "Space",
|
||||
"ctrlKey": false
|
||||
}));
|
||||
|
||||
["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => {
|
||||
shortcutManager.add(`pan${code}keydown`, new ShortcutKeyboard({
|
||||
"altKey": false,
|
||||
"callback": (ev: KeyboardEvent) => {
|
||||
this.getMap().handleMapPanning(ev);
|
||||
},
|
||||
"code": code,
|
||||
"ctrlKey": false,
|
||||
"event": "keydown"
|
||||
}));
|
||||
});
|
||||
|
||||
["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => {
|
||||
shortcutManager.add(`pan${code}keyup`, new ShortcutKeyboard({
|
||||
"callback": (ev: KeyboardEvent) => {
|
||||
this.getMap().handleMapPanning(ev);
|
||||
},
|
||||
"code": code
|
||||
}));
|
||||
});
|
||||
|
||||
["Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9"].forEach(code => {
|
||||
shortcutManager.add(`hotgroup${code}`, new ShortcutKeyboard({
|
||||
"callback": (ev: KeyboardEvent) => {
|
||||
if (ev.ctrlKey && ev.shiftKey)
|
||||
this.getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5)));
|
||||
else if (ev.ctrlKey && !ev.shiftKey)
|
||||
this.getUnitsManager().selectedUnitsSetHotgroup(parseInt(ev.code.substring(5)));
|
||||
else
|
||||
this.getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5)));
|
||||
},
|
||||
"code": code
|
||||
}));
|
||||
});
|
||||
|
||||
// TODO: move from here in dedicated class
|
||||
document.addEventListener("closeDialog", (ev: CustomEventInit) => {
|
||||
ev.detail._element.closest(".ol-dialog").classList.add("hide");
|
||||
});
|
||||
|
||||
/* Try and connect with the Olympus REST server */
|
||||
document.addEventListener("tryConnection", () => {
|
||||
const form = document.querySelector("#splash-content")?.querySelector("#authentication-form");
|
||||
const username = (form?.querySelector("#username") as HTMLInputElement).value;
|
||||
const password = (form?.querySelector("#password") as HTMLInputElement).value;
|
||||
|
||||
/* Update the user credentials */
|
||||
this.getServerManager().setCredentials(username, password);
|
||||
|
||||
/* Start periodically requesting updates */
|
||||
this.getServerManager().startUpdate();
|
||||
|
||||
this.setLoginStatus("connecting");
|
||||
})
|
||||
|
||||
/* 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); });
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -106,7 +106,7 @@ export const layers = {
|
||||
urlTemplate: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
||||
maxZoom: 20,
|
||||
minZoom: 1,
|
||||
attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
|
||||
attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, GetApp().getMap()ping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
|
||||
},
|
||||
"USGS Topo": {
|
||||
urlTemplate: 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from "..";
|
||||
import { getApp } from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { dataPointMap } from "../other/utils";
|
||||
@ -23,7 +23,7 @@ export class AirbaseContextMenu extends ContextMenu {
|
||||
|
||||
document.addEventListener("contextMenuLandAirbase", (e: any) => {
|
||||
if (this.#airbase)
|
||||
getUnitsManager().selectedUnitsLandAt(this.#airbase.getLatLng());
|
||||
getApp().getUnitsManager().selectedUnitsLandAt(this.#airbase.getLatLng());
|
||||
this.hide();
|
||||
})
|
||||
}
|
||||
@ -39,8 +39,8 @@ export class AirbaseContextMenu extends ContextMenu {
|
||||
this.#setProperties(this.#airbase.getProperties());
|
||||
this.#setParkings(this.#airbase.getParkings());
|
||||
this.#setCoalition(this.#airbase.getCoalition());
|
||||
this.#showLandButton(getUnitsManager().getSelectedUnitsCategories().length == 1 && ["Aircraft", "Helicopter"].includes(getUnitsManager().getSelectedUnitsCategories()[0]) && (getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral"))
|
||||
this.#showSpawnButton(getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getMissionHandler().getCommandedCoalition());
|
||||
this.#showLandButton(getApp().getUnitsManager().getSelectedUnitsCategories().length == 1 && ["Aircraft", "Helicopter"].includes(getApp().getUnitsManager().getSelectedUnitsCategories()[0]) && (getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral"))
|
||||
this.#showSpawnButton(getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getApp().getMissionManager().getCommandedCoalition());
|
||||
this.#setAirbaseData();
|
||||
|
||||
this.clip();
|
||||
@ -109,8 +109,8 @@ export class AirbaseContextMenu extends ContextMenu {
|
||||
*/
|
||||
#showSpawnMenu() {
|
||||
if (this.#airbase != null) {
|
||||
setActiveCoalition(this.#airbase.getCoalition());
|
||||
getMap().showAirbaseSpawnMenu(this.getX(), this.getY(), this.getLatLng(), this.#airbase);
|
||||
getApp().setActiveCoalition(this.#airbase.getCoalition());
|
||||
getApp().getMap().showAirbaseSpawnMenu(this.getX(), this.getY(), this.getLatLng(), this.#airbase);
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,11 +135,11 @@ export class AirbaseContextMenu extends ContextMenu {
|
||||
if ( runways.length === 0 ) {
|
||||
runwaysContainer.innerText = "No data";
|
||||
} else {
|
||||
runways.forEach( runway => {
|
||||
runways.forEach( (runway: AirbaseChartRunwayData) => {
|
||||
let runwayDiv = document.createElement( "div" );
|
||||
runwayDiv.classList.add( "runway" );
|
||||
|
||||
runway.headings.forEach( headings => {
|
||||
runway.headings.forEach( (headings: AirbaseChartRunwayHeadingData) => {
|
||||
for ( const [ heading, data ] of Object.entries( headings ) ) {
|
||||
|
||||
let headingDiv = document.createElement( "div" );
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getActiveCoalition } from "..";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
import { AircraftSpawnMenu, HelicopterSpawnMenu } from "../controls/unitspawnmenu";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { getApp } from "..";
|
||||
|
||||
/** This context menu is shown when the user wants to spawn a new aircraft or helicopter from the ground at an airbase.
|
||||
* It is shown by clicking on the "spawn" button of a AirbaseContextMenu. */
|
||||
@ -54,7 +54,7 @@ export class AirbaseSpawnContextMenu extends ContextMenu {
|
||||
this.#aircraftSpawnMenu.setCountries();
|
||||
this.#helicopterSpawnMenu.setCountries();
|
||||
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) });
|
||||
}
|
||||
|
||||
/** Sets the airbase at which the new unit will be spawned
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getMap, getMissionHandler, getUnitsManager } from "..";
|
||||
import { getApp } from "..";
|
||||
import { GAME_MASTER, IADSTypes } from "../constants/constants";
|
||||
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
@ -54,20 +54,20 @@ export class CoalitionAreaContextMenu extends ContextMenu {
|
||||
|
||||
document.addEventListener("coalitionAreaBringToBack", (e: any) => {
|
||||
if (this.#coalitionArea)
|
||||
getMap().bringCoalitionAreaToBack(this.#coalitionArea);
|
||||
getMap().hideCoalitionAreaContextMenu();
|
||||
getApp().getMap().bringCoalitionAreaToBack(this.#coalitionArea);
|
||||
getApp().getMap().hideCoalitionAreaContextMenu();
|
||||
});
|
||||
|
||||
document.addEventListener("coalitionAreaDelete", (e: any) => {
|
||||
if (this.#coalitionArea)
|
||||
getMap().deleteCoalitionArea(this.#coalitionArea);
|
||||
getMap().hideCoalitionAreaContextMenu();
|
||||
getApp().getMap().deleteCoalitionArea(this.#coalitionArea);
|
||||
getApp().getMap().hideCoalitionAreaContextMenu();
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuCreateIads", (e: any) => {
|
||||
const area = this.getCoalitionArea();
|
||||
if (area)
|
||||
getUnitsManager().createIADS(area, getCheckboxOptions(this.#iadsTypesDropdown), getCheckboxOptions(this.#iadsErasDropdown), getCheckboxOptions(this.#iadsRangesDropdown), this.#iadsDensitySlider.getValue(), this.#iadsDistributionSlider.getValue());
|
||||
getApp().getUnitsManager().createIADS(area, getCheckboxOptions(this.#iadsTypesDropdown), getCheckboxOptions(this.#iadsErasDropdown), getCheckboxOptions(this.#iadsRangesDropdown), this.#iadsDensitySlider.getValue(), this.#iadsDistributionSlider.getValue());
|
||||
})
|
||||
this.hide();
|
||||
}
|
||||
@ -97,7 +97,7 @@ export class CoalitionAreaContextMenu extends ContextMenu {
|
||||
return createCheckboxOption(range, `Add ${range} units to the IADS`);
|
||||
}));
|
||||
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER)
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER)
|
||||
this.#coalitionSwitch.hide()
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ export class CoalitionAreaContextMenu extends ContextMenu {
|
||||
* @param value Switch position (false: blue, true: red)
|
||||
*/
|
||||
#onSwitchClick(value: boolean) {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER) {
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER) {
|
||||
this.getCoalitionArea()?.setCoalition(value ? "red" : "blue");
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
|
||||
element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition())
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getActiveCoalition, getMap, getMissionHandler, setActiveCoalition } from "..";
|
||||
import { spawnExplosion, spawnSmoke } from "../server/server";
|
||||
import { getApp } from "..";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
import { Switch } from "../controls/switch";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
@ -48,20 +47,20 @@ export class MapContextMenu extends ContextMenu {
|
||||
|
||||
document.addEventListener("contextMenuDeploySmoke", (e: any) => {
|
||||
this.hide();
|
||||
spawnSmoke(e.detail.color, this.getLatLng());
|
||||
getApp().getServerManager().spawnSmoke(e.detail.color, this.getLatLng());
|
||||
var marker = new SmokeMarker(this.getLatLng(), e.detail.color);
|
||||
marker.addTo(getMap());
|
||||
marker.addTo(getApp().getMap());
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuExplosion", (e: any) => {
|
||||
this.hide();
|
||||
spawnExplosion(e.detail.strength, this.getLatLng());
|
||||
getApp().getServerManager().spawnExplosion(e.detail.strength, this.getLatLng());
|
||||
});
|
||||
|
||||
document.addEventListener("editCoalitionArea", (e: any) => {
|
||||
this.hide();
|
||||
if (this.#coalitionArea) {
|
||||
getMap().deselectAllCoalitionAreas();
|
||||
getApp().getMap().deselectAllCoalitionAreas();
|
||||
this.#coalitionArea.setSelected(true);
|
||||
}
|
||||
});
|
||||
@ -103,13 +102,13 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.#navyUnitSpawnMenu.setCountries();
|
||||
|
||||
/* Only a Game Master can choose the coalition of a new unit */
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER)
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER)
|
||||
this.#coalitionSwitch.hide()
|
||||
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
|
||||
if (getActiveCoalition() == "blue")
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) });
|
||||
if (getApp().getActiveCoalition() == "blue")
|
||||
this.#coalitionSwitch.setValue(false);
|
||||
else if (getActiveCoalition() == "red")
|
||||
else if (getApp().getActiveCoalition() == "red")
|
||||
this.#coalitionSwitch.setValue(true);
|
||||
else
|
||||
this.#coalitionSwitch.setValue(undefined);
|
||||
@ -199,8 +198,8 @@ export class MapContextMenu extends ContextMenu {
|
||||
* @param value Switch position (false: "blue", true: "red")
|
||||
*/
|
||||
#onSwitchClick(value: boolean) {
|
||||
value ? setActiveCoalition("red") : setActiveCoalition("blue");
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
|
||||
value ? getApp().setActiveCoalition("red") : getApp().setActiveCoalition("blue");
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) });
|
||||
this.#aircraftSpawnMenu.setCountries();
|
||||
this.#helicopterSpawnMenu.setCountries();
|
||||
this.#groundUnitSpawnMenu.setCountries();
|
||||
@ -212,8 +211,8 @@ export class MapContextMenu extends ContextMenu {
|
||||
*/
|
||||
#onSwitchRightClick() {
|
||||
this.#coalitionSwitch.setValue(undefined);
|
||||
setActiveCoalition("neutral");
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
|
||||
getApp().setActiveCoalition("neutral");
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) });
|
||||
this.#aircraftSpawnMenu.setCountries();
|
||||
this.#helicopterSpawnMenu.setCountries();
|
||||
this.#groundUnitSpawnMenu.setCountries();
|
||||
|
||||
@ -68,6 +68,10 @@ export class Dropdown {
|
||||
return this.#options.children;
|
||||
}
|
||||
|
||||
addOptionElement(optionElement: HTMLElement) {
|
||||
this.#options.appendChild(optionElement);
|
||||
}
|
||||
|
||||
selectText(text: string) {
|
||||
const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text);
|
||||
if (index > -1) {
|
||||
|
||||
@ -2,16 +2,14 @@ import { LatLng } from "leaflet";
|
||||
import { Dropdown } from "./dropdown";
|
||||
import { Slider } from "./slider";
|
||||
import { UnitDatabase } from "../unit/databases/unitdatabase";
|
||||
import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager } from "..";
|
||||
import { getApp } from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { UnitSpawnOptions } from "../@types/unitdatabase";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { ftToM } from "../other/utils";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
|
||||
import { UnitSpawnTable } from "../@types/unit";
|
||||
|
||||
export class UnitSpawnMenu {
|
||||
#container: HTMLElement;
|
||||
@ -209,8 +207,8 @@ export class UnitSpawnMenu {
|
||||
}
|
||||
|
||||
setCountries() {
|
||||
var coalitions = getMissionHandler().getCoalitions();
|
||||
var countries = Object.values(coalitions[getActiveCoalition() as keyof typeof coalitions]);
|
||||
var coalitions = getApp().getMissionManager().getCoalitions();
|
||||
var countries = Object.values(coalitions[getApp().getActiveCoalition() as keyof typeof coalitions]);
|
||||
this.#unitCountryDropdown.setOptionsElements(this.#createCountryButtons(this.#unitCountryDropdown, countries, (country: string) => { this.#setUnitCountry(country) }));
|
||||
|
||||
if (countries.length > 0 && !countries.includes(this.#spawnOptions.country)) {
|
||||
@ -377,11 +375,11 @@ export class UnitSpawnMenu {
|
||||
}
|
||||
|
||||
#computeSpawnPoints() {
|
||||
if (getMissionHandler() && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
|
||||
if (getApp().getMissionManager() && getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER) {
|
||||
var unitCount = parseInt(this.#unitCountDropdown.getValue());
|
||||
var unitSpawnPoints = unitCount * this.#unitDatabase.getSpawnPointsByLabel(this.#unitLabelDropdown.getValue());
|
||||
this.#deployUnitButtonEl.dataset.points = `${unitSpawnPoints}`;
|
||||
this.#deployUnitButtonEl.disabled = unitSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
|
||||
this.#deployUnitButtonEl.disabled = unitSpawnPoints >= getApp().getMissionManager().getAvailableSpawnPoints();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -400,7 +398,7 @@ export class AircraftSpawnMenu extends UnitSpawnMenu {
|
||||
}
|
||||
|
||||
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
|
||||
spawnOptions.coalition = getActiveCoalition();
|
||||
spawnOptions.coalition = getApp().getActiveCoalition();
|
||||
if (spawnOptions) {
|
||||
var unitTable: UnitSpawnTable = {
|
||||
unitType: spawnOptions.name,
|
||||
@ -414,9 +412,9 @@ export class AircraftSpawnMenu extends UnitSpawnMenu {
|
||||
units.push(unitTable);
|
||||
}
|
||||
|
||||
getUnitsManager().spawnUnits("Aircraft", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
|
||||
getApp().getUnitsManager().spawnUnits("Aircraft", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
|
||||
if (res.commandHash !== undefined)
|
||||
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
|
||||
getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash);
|
||||
});
|
||||
|
||||
this.getContainer().dispatchEvent(new Event("hide"));
|
||||
@ -438,7 +436,7 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu {
|
||||
}
|
||||
|
||||
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
|
||||
spawnOptions.coalition = getActiveCoalition();
|
||||
spawnOptions.coalition = getApp().getActiveCoalition();
|
||||
if (spawnOptions) {
|
||||
var unitTable: UnitSpawnTable = {
|
||||
unitType: spawnOptions.name,
|
||||
@ -452,9 +450,9 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu {
|
||||
units.push(unitTable);
|
||||
}
|
||||
|
||||
getUnitsManager().spawnUnits("Helicopter", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
|
||||
getApp().getUnitsManager().spawnUnits("Helicopter", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
|
||||
if (res.commandHash !== undefined)
|
||||
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
|
||||
getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash);
|
||||
});
|
||||
|
||||
this.getContainer().dispatchEvent(new Event("hide"));
|
||||
@ -476,7 +474,7 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu {
|
||||
}
|
||||
|
||||
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
|
||||
spawnOptions.coalition = getActiveCoalition();
|
||||
spawnOptions.coalition = getApp().getActiveCoalition();
|
||||
if (spawnOptions) {
|
||||
var unitTable: UnitSpawnTable = {
|
||||
unitType: spawnOptions.name,
|
||||
@ -490,9 +488,9 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu {
|
||||
unitTable.location.lat += i > 0? 0.0001: 0;
|
||||
}
|
||||
|
||||
getUnitsManager().spawnUnits("GroundUnit", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
|
||||
getApp().getUnitsManager().spawnUnits("GroundUnit", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
|
||||
if (res.commandHash !== undefined)
|
||||
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
|
||||
getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash);
|
||||
});
|
||||
|
||||
this.getContainer().dispatchEvent(new Event("hide"));
|
||||
@ -514,7 +512,7 @@ export class NavyUnitSpawnMenu extends UnitSpawnMenu {
|
||||
}
|
||||
|
||||
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
|
||||
spawnOptions.coalition = getActiveCoalition();
|
||||
spawnOptions.coalition = getApp().getActiveCoalition();
|
||||
if (spawnOptions) {
|
||||
var unitTable: UnitSpawnTable = {
|
||||
unitType: spawnOptions.name,
|
||||
@ -528,9 +526,9 @@ export class NavyUnitSpawnMenu extends UnitSpawnMenu {
|
||||
unitTable.location.lat += i > 0? 0.0001: 0;
|
||||
}
|
||||
|
||||
getUnitsManager().spawnUnits("NavyUnit", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
|
||||
getApp().getUnitsManager().spawnUnits("NavyUnit", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
|
||||
if (res.commandHash !== undefined)
|
||||
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
|
||||
getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash);
|
||||
});
|
||||
|
||||
this.getContainer().dispatchEvent(new Event("hide"));
|
||||
|
||||
@ -1,153 +0,0 @@
|
||||
export interface FeatureSwitchInterface {
|
||||
"defaultEnabled": boolean, // default on/off state (if allowed by forceState)
|
||||
"forceState": number, // -1 don't force; 0 force off; 1 force on
|
||||
"label": string,
|
||||
"name": string,
|
||||
"onEnabled"?: CallableFunction,
|
||||
"options"?: object,
|
||||
"removeArtifactsIfDisabled"?: boolean
|
||||
}
|
||||
|
||||
|
||||
class FeatureSwitch {
|
||||
|
||||
// From config param
|
||||
defaultEnabled;
|
||||
forceState = -1;
|
||||
label;
|
||||
name;
|
||||
onEnabled;
|
||||
removeArtifactsIfDisabled = true;
|
||||
|
||||
// Self-set
|
||||
userPreference;
|
||||
|
||||
|
||||
constructor(config: FeatureSwitchInterface) {
|
||||
|
||||
this.defaultEnabled = config.defaultEnabled;
|
||||
this.forceState = config.forceState;
|
||||
this.label = config.label;
|
||||
this.name = config.name;
|
||||
this.onEnabled = config.onEnabled;
|
||||
|
||||
this.userPreference = this.getUserPreference();
|
||||
|
||||
}
|
||||
|
||||
|
||||
getUserPreference() {
|
||||
|
||||
let preferences = JSON.parse(localStorage.getItem("featureSwitches") || "{}");
|
||||
|
||||
return (preferences.hasOwnProperty(this.name)) ? preferences[this.name] : this.defaultEnabled;
|
||||
|
||||
}
|
||||
|
||||
|
||||
isEnabled() {
|
||||
|
||||
if ( this.forceState === 0 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( this.forceState === 1 ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.userPreference;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FeatureSwitches {
|
||||
|
||||
#featureSwitches: FeatureSwitch[] = [
|
||||
|
||||
new FeatureSwitch({
|
||||
"defaultEnabled": false,
|
||||
"forceState": -1,
|
||||
"label": "AIC",
|
||||
"name": "aic"
|
||||
}),
|
||||
|
||||
new FeatureSwitch({
|
||||
"defaultEnabled": false,
|
||||
"forceState": -1,
|
||||
"label": "AI Formations",
|
||||
"name": "ai-formations",
|
||||
"removeArtifactsIfDisabled": false
|
||||
}),
|
||||
|
||||
new FeatureSwitch({
|
||||
"defaultEnabled": false,
|
||||
"forceState": 1,
|
||||
"label": "ATC",
|
||||
"name": "atc"
|
||||
}),
|
||||
|
||||
new FeatureSwitch({
|
||||
"defaultEnabled": false,
|
||||
"forceState": -1,
|
||||
"label": "Force show unit control panel",
|
||||
"name": "forceShowUnitControlPanel"
|
||||
}),
|
||||
|
||||
new FeatureSwitch({
|
||||
"defaultEnabled": true,
|
||||
"forceState": -1,
|
||||
"label": "Show splash screen",
|
||||
"name": "splashScreen"
|
||||
})
|
||||
|
||||
];
|
||||
|
||||
|
||||
constructor() {
|
||||
|
||||
this.#testSwitches();
|
||||
|
||||
this.savePreferences();
|
||||
|
||||
}
|
||||
|
||||
|
||||
getSwitch(switchName: string) {
|
||||
|
||||
return this.#featureSwitches.find(featureSwitch => featureSwitch.name === switchName);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#testSwitches() {
|
||||
for (const featureSwitch of this.#featureSwitches) {
|
||||
if (featureSwitch.isEnabled()) {
|
||||
if (typeof featureSwitch.onEnabled === "function") {
|
||||
featureSwitch.onEnabled();
|
||||
}
|
||||
} else {
|
||||
document.querySelectorAll("[data-feature-switch='" + featureSwitch.name + "']").forEach(el => {
|
||||
if (featureSwitch.removeArtifactsIfDisabled === false) {
|
||||
el.remove();
|
||||
} else {
|
||||
el.classList.add("hide");
|
||||
}
|
||||
});
|
||||
}
|
||||
document.body.classList.toggle("feature-" + featureSwitch.name, featureSwitch.isEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
savePreferences() {
|
||||
|
||||
let preferences: any = {};
|
||||
|
||||
for (const featureSwitch of this.#featureSwitches) {
|
||||
preferences[featureSwitch.name] = featureSwitch.isEnabled();
|
||||
}
|
||||
|
||||
localStorage.setItem("featureSwitches", JSON.stringify(preferences));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
export abstract class ToggleableFeature {
|
||||
|
||||
#status: boolean = false;
|
||||
|
||||
|
||||
constructor(defaultStatus: boolean) {
|
||||
|
||||
this.#status = defaultStatus;
|
||||
|
||||
this.onStatusUpdate();
|
||||
|
||||
}
|
||||
|
||||
|
||||
getStatus(): boolean {
|
||||
return this.#status;
|
||||
}
|
||||
|
||||
|
||||
protected onStatusUpdate() { }
|
||||
|
||||
|
||||
toggleStatus(force?: boolean): void {
|
||||
|
||||
if (force) {
|
||||
this.#status = force;
|
||||
} else {
|
||||
this.#status = !this.#status;
|
||||
}
|
||||
|
||||
this.onStatusUpdate();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,292 +1,14 @@
|
||||
import { Map } from "./map/map"
|
||||
import { UnitsManager } from "./unit/unitsmanager";
|
||||
import { UnitInfoPanel } from "./panels/unitinfopanel";
|
||||
import { ConnectionStatusPanel } from "./panels/connectionstatuspanel";
|
||||
import { MissionManager } from "./mission/missionmanager";
|
||||
import { UnitControlPanel } from "./panels/unitcontrolpanel";
|
||||
import { MouseInfoPanel } from "./panels/mouseinfopanel";
|
||||
import { LogPanel } from "./panels/logpanel";
|
||||
import { getConfig, getPaused, setAddress, setCredentials, setPaused, startUpdate, toggleDemoEnabled } from "./server/server";
|
||||
import { keyEventWasInInput } from "./other/utils";
|
||||
import { Popup } from "./popups/popup";
|
||||
import { HotgroupPanel } from "./panels/hotgrouppanel";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants";
|
||||
import { ServerStatusPanel } from "./panels/serverstatuspanel";
|
||||
import { WeaponsManager } from "./weapon/weaponsmanager";
|
||||
import { ConfigParameters } from "./@types/dom";
|
||||
import { CommandModeToolbar } from "./toolbars/commandmodetoolbar";
|
||||
import { PrimaryToolbar } from "./toolbars/primarytoolbar";
|
||||
import { OlympusApp } from "./app";
|
||||
|
||||
/* Global data */
|
||||
var activeCoalition: string = "blue";
|
||||
|
||||
/* Main leaflet map, extended by custom methods */
|
||||
var map: Map;
|
||||
|
||||
/* Managers */
|
||||
var unitsManager: UnitsManager;
|
||||
var weaponsManager: WeaponsManager;
|
||||
var missionManager: MissionManager;
|
||||
|
||||
/* UI Panels */
|
||||
var unitInfoPanel: UnitInfoPanel;
|
||||
var connectionStatusPanel: ConnectionStatusPanel;
|
||||
var serverStatusPanel: ServerStatusPanel;
|
||||
var unitControlPanel: UnitControlPanel;
|
||||
var mouseInfoPanel: MouseInfoPanel;
|
||||
var logPanel: LogPanel;
|
||||
var hotgroupPanel: HotgroupPanel;
|
||||
|
||||
/* UI Toolbars */
|
||||
var primaryToolbar: PrimaryToolbar;
|
||||
var commandModeToolbar: CommandModeToolbar;
|
||||
|
||||
/* Popups */
|
||||
var infoPopup: Popup;
|
||||
var app: OlympusApp;
|
||||
|
||||
function setup() {
|
||||
|
||||
/* Initialize base functionalitites */
|
||||
map = new Map('map-container');
|
||||
|
||||
unitsManager = new UnitsManager();
|
||||
weaponsManager = new WeaponsManager();
|
||||
missionManager = new MissionManager();
|
||||
|
||||
/* Panels */
|
||||
unitInfoPanel = new UnitInfoPanel("unit-info-panel");
|
||||
unitControlPanel = new UnitControlPanel("unit-control-panel");
|
||||
connectionStatusPanel = new ConnectionStatusPanel("connection-status-panel");
|
||||
serverStatusPanel = new ServerStatusPanel("server-status-panel");
|
||||
mouseInfoPanel = new MouseInfoPanel("mouse-info-panel");
|
||||
hotgroupPanel = new HotgroupPanel("hotgroup-panel");
|
||||
logPanel = new LogPanel("log-panel");
|
||||
|
||||
/* Toolbars */
|
||||
primaryToolbar = new PrimaryToolbar("primary-toolbar");
|
||||
commandModeToolbar = new CommandModeToolbar("command-mode-toolbar");
|
||||
|
||||
/* Popups */
|
||||
infoPopup = new Popup("info-popup");
|
||||
|
||||
/* Setup event handlers */
|
||||
setupEvents();
|
||||
|
||||
/* Load the config file from the app server*/
|
||||
getConfig((config: ConfigParameters) => readConfig(config));
|
||||
app = new OlympusApp();
|
||||
app.start();
|
||||
}
|
||||
|
||||
/** Loads the configuration parameters
|
||||
*
|
||||
* @param config ConfigParameters, defines the address and port of the Olympus REST server
|
||||
*/
|
||||
function readConfig(config: ConfigParameters) {
|
||||
if (config && config.address != undefined && config.port != undefined) {
|
||||
const address = config.address;
|
||||
const port = config.port;
|
||||
if (typeof address === 'string' && typeof port == 'number')
|
||||
setAddress(address == "*" ? window.location.hostname : address, <number>port);
|
||||
}
|
||||
else {
|
||||
throw new Error('Could not read configuration file');
|
||||
}
|
||||
export function getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
|
||||
/** Setup the global window events
|
||||
*
|
||||
*/
|
||||
function setupEvents() {
|
||||
/* Generic clicks. The "data-on-click" html parameter is used to call a generic callback from the html code.
|
||||
It is used by all the statically defined elements of the UI. Dynamically generated elements should directly generate the events instead.
|
||||
*/
|
||||
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
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Keyup events */
|
||||
document.addEventListener("keyup", ev => {
|
||||
if (keyEventWasInInput(ev)) {
|
||||
return;
|
||||
}
|
||||
switch (ev.code) {
|
||||
case "KeyT":
|
||||
toggleDemoEnabled();
|
||||
break;
|
||||
case "Space":
|
||||
setPaused(!getPaused());
|
||||
break;
|
||||
case "KeyW": case "KeyA": case "KeyS": case "KeyD":
|
||||
case "ArrowLeft": case "ArrowRight": case "ArrowUp": case "ArrowDown":
|
||||
getMap().handleMapPanning(ev);
|
||||
break;
|
||||
case "Digit1": case "Digit2": case "Digit3": case "Digit4": case "Digit5": case "Digit6": case "Digit7": case "Digit8": case "Digit9":
|
||||
/* Using the substring because the key will be invalid when pressing the Shift key */
|
||||
if (ev.ctrlKey && ev.shiftKey)
|
||||
getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5)));
|
||||
else if (ev.ctrlKey && !ev.shiftKey)
|
||||
getUnitsManager().selectedUnitsSetHotgroup(parseInt(ev.code.substring(5)));
|
||||
else
|
||||
getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5)));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
/* Keydown events */
|
||||
document.addEventListener("keydown", ev => {
|
||||
if (keyEventWasInInput(ev)) {
|
||||
return;
|
||||
}
|
||||
switch (ev.code) {
|
||||
case "KeyW": case "KeyA": case "KeyS": case "KeyD": case "ArrowLeft": case "ArrowRight": case "ArrowUp": case "ArrowDown":
|
||||
getMap().handleMapPanning(ev);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: move from here in dedicated class
|
||||
document.addEventListener("closeDialog", (ev: CustomEventInit) => {
|
||||
ev.detail._element.closest(".ol-dialog").classList.add("hide");
|
||||
});
|
||||
|
||||
/* Try and connect with the Olympus REST server */
|
||||
document.addEventListener("tryConnection", () => {
|
||||
const form = document.querySelector("#splash-content")?.querySelector("#authentication-form");
|
||||
const username = (form?.querySelector("#username") as HTMLInputElement).value;
|
||||
const password = (form?.querySelector("#password") as HTMLInputElement).value;
|
||||
|
||||
/* Update the user credentials */
|
||||
setCredentials(username, password);
|
||||
|
||||
/* Start periodically requesting updates */
|
||||
startUpdate();
|
||||
|
||||
setLoginStatus("connecting");
|
||||
})
|
||||
|
||||
/* 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);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/* Getters */
|
||||
export function getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
export function getUnitsManager() {
|
||||
return unitsManager;
|
||||
}
|
||||
|
||||
export function getWeaponsManager() {
|
||||
return weaponsManager;
|
||||
}
|
||||
|
||||
export function getMissionHandler() {
|
||||
return missionManager;
|
||||
}
|
||||
|
||||
export function getUnitInfoPanel() {
|
||||
return unitInfoPanel;
|
||||
}
|
||||
|
||||
export function getUnitControlPanel() {
|
||||
return unitControlPanel;
|
||||
}
|
||||
|
||||
export function getMouseInfoPanel() {
|
||||
return mouseInfoPanel;
|
||||
}
|
||||
|
||||
export function getLogPanel() {
|
||||
return logPanel;
|
||||
}
|
||||
|
||||
export function getConnectionStatusPanel() {
|
||||
return connectionStatusPanel;
|
||||
}
|
||||
|
||||
export function getServerStatusPanel() {
|
||||
return serverStatusPanel;
|
||||
}
|
||||
|
||||
export function getHotgroupPanel() {
|
||||
return hotgroupPanel;
|
||||
}
|
||||
|
||||
export function getInfoPopup() {
|
||||
return infoPopup;
|
||||
}
|
||||
|
||||
/** Set the active coalition, i.e. the currently controlled coalition. A game master can change the active coalition, while a commander is bound to his/her coalition
|
||||
*
|
||||
* @param newActiveCoalition
|
||||
*/
|
||||
export function setActiveCoalition(newActiveCoalition: string) {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER)
|
||||
activeCoalition = newActiveCoalition;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The active coalition
|
||||
*/
|
||||
export function getActiveCoalition() {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER)
|
||||
return activeCoalition;
|
||||
else {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode == BLUE_COMMANDER)
|
||||
return "blue";
|
||||
else if (getMissionHandler().getCommandModeOptions().commandMode == RED_COMMANDER)
|
||||
return "red";
|
||||
else
|
||||
return "neutral";
|
||||
}
|
||||
}
|
||||
|
||||
/** Set a message in the login splash screen
|
||||
*
|
||||
* @param status The message to show in the login splash screen
|
||||
*/
|
||||
export function setLoginStatus(status: string) {
|
||||
const el = document.querySelector("#login-status") as HTMLElement;
|
||||
if (el)
|
||||
el.dataset["status"] = status;
|
||||
}
|
||||
|
||||
window.onload = setup;
|
||||
|
||||
window.onload = setup;
|
||||
@ -1,5 +1,5 @@
|
||||
import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet";
|
||||
import { getMap, getMissionHandler, getUnitsManager } from "../..";
|
||||
import { getApp } from "../..";
|
||||
import { CoalitionAreaHandle } from "./coalitionareahandle";
|
||||
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
|
||||
import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants";
|
||||
@ -23,8 +23,8 @@ export class CoalitionArea extends Polygon {
|
||||
this.#setColors();
|
||||
this.#registerCallbacks();
|
||||
|
||||
if ([BLUE_COMMANDER, RED_COMMANDER].includes(getMissionHandler().getCommandModeOptions().commandMode))
|
||||
this.setCoalition(getMissionHandler().getCommandedCoalition());
|
||||
if ([BLUE_COMMANDER, RED_COMMANDER].includes(getApp().getMissionManager().getCommandModeOptions().commandMode))
|
||||
this.setCoalition(getApp().getMissionManager().getCommandedCoalition());
|
||||
}
|
||||
|
||||
setCoalition(coalition: string) {
|
||||
@ -61,7 +61,7 @@ export class CoalitionArea extends Polygon {
|
||||
|
||||
/* Remove areas with less than 2 vertexes */
|
||||
if (latlngs.length <= 2)
|
||||
getMap().deleteCoalitionArea(this);
|
||||
getApp().getMap().deleteCoalitionArea(this);
|
||||
}
|
||||
|
||||
getEditing() {
|
||||
@ -89,7 +89,7 @@ export class CoalitionArea extends Polygon {
|
||||
|
||||
onRemove(map: Map): this {
|
||||
super.onRemove(map);
|
||||
this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getMap()));
|
||||
this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getApp().getMap()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -99,14 +99,14 @@ export class CoalitionArea extends Polygon {
|
||||
}
|
||||
|
||||
#setHandles() {
|
||||
this.#handles.forEach((handle: CoalitionAreaHandle) => handle.removeFrom(getMap()));
|
||||
this.#handles.forEach((handle: CoalitionAreaHandle) => handle.removeFrom(getApp().getMap()));
|
||||
this.#handles = [];
|
||||
if (this.getSelected()) {
|
||||
var latlngs = this.getLatLngs()[0] as LatLng[];
|
||||
latlngs.forEach((latlng: LatLng, idx: number) => {
|
||||
/* Add the polygon vertex handle (for moving the vertex) */
|
||||
const handle = new CoalitionAreaHandle(latlng);
|
||||
handle.addTo(getMap());
|
||||
handle.addTo(getApp().getMap());
|
||||
handle.on("drag", (e: any) => {
|
||||
var latlngs = this.getLatLngs()[0] as LatLng[];
|
||||
latlngs[idx] = e.target.getLatLng();
|
||||
@ -120,7 +120,7 @@ export class CoalitionArea extends Polygon {
|
||||
}
|
||||
|
||||
#setMiddleHandles() {
|
||||
this.#middleHandles.forEach((handle: CoalitionAreaMiddleHandle) => handle.removeFrom(getMap()));
|
||||
this.#middleHandles.forEach((handle: CoalitionAreaMiddleHandle) => handle.removeFrom(getApp().getMap()));
|
||||
this.#middleHandles = [];
|
||||
var latlngs = this.getLatLngs()[0] as LatLng[];
|
||||
if (this.getSelected() && latlngs.length >= 2) {
|
||||
@ -128,13 +128,13 @@ export class CoalitionArea extends Polygon {
|
||||
latlngs.concat([latlngs[0]]).forEach((latlng: LatLng, idx: number) => {
|
||||
/* Add the polygon middle point handle (for adding new vertexes) */
|
||||
if (lastLatLng != null) {
|
||||
const handle1Point = getMap().latLngToLayerPoint(latlng);
|
||||
const handle2Point = getMap().latLngToLayerPoint(lastLatLng);
|
||||
const handle1Point = getApp().getMap().latLngToLayerPoint(latlng);
|
||||
const handle2Point = getApp().getMap().latLngToLayerPoint(lastLatLng);
|
||||
const middlePoint = new Point((handle1Point.x + handle2Point.x) / 2, (handle1Point.y + handle2Point.y) / 2);
|
||||
const middleLatLng = getMap().layerPointToLatLng(middlePoint);
|
||||
const middleLatLng = getApp().getMap().layerPointToLatLng(middlePoint);
|
||||
|
||||
const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng);
|
||||
middleHandle.addTo(getMap());
|
||||
middleHandle.addTo(getApp().getMap());
|
||||
middleHandle.on("click", (e: any) => {
|
||||
this.#activeIndex = idx - 1;
|
||||
this.addTemporaryLatLng(middleLatLng);
|
||||
@ -148,7 +148,7 @@ export class CoalitionArea extends Polygon {
|
||||
|
||||
#registerCallbacks() {
|
||||
this.on("click", (e: any) => {
|
||||
getMap().deselectAllCoalitionAreas();
|
||||
getApp().getMap().deselectAllCoalitionAreas();
|
||||
if (!this.getSelected()) {
|
||||
this.setSelected(true);
|
||||
}
|
||||
@ -156,7 +156,7 @@ export class CoalitionArea extends Polygon {
|
||||
|
||||
this.on("contextmenu", (e: any) => {
|
||||
if (!this.getEditing()) {
|
||||
getMap().deselectAllCoalitionAreas();
|
||||
getApp().getMap().deselectAllCoalitionAreas();
|
||||
this.setSelected(true);
|
||||
}
|
||||
else
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import * as L from "leaflet"
|
||||
import { getInfoPopup, getMissionHandler, getUnitsManager } from "..";
|
||||
import { getApp } from "..";
|
||||
import { BoxSelect } from "./boxselect";
|
||||
import { MapContextMenu } from "../contextmenus/mapcontextmenu";
|
||||
import { UnitContextMenu } from "../contextmenus/unitcontextmenu";
|
||||
@ -18,10 +18,11 @@ import { CoalitionArea } from "./coalitionarea/coalitionarea";
|
||||
import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
|
||||
import { DrawingCursor } from "./coalitionarea/drawingcursor";
|
||||
import { AirbaseSpawnContextMenu } from "../contextmenus/airbasespawnmenu";
|
||||
import { Popup } from "../popups/popup";
|
||||
|
||||
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
|
||||
|
||||
// TODO would be nice to convert to ts
|
||||
// TODO would be nice to convert to ts - yes
|
||||
require("../../public/javascripts/leaflet.nauticscale.js")
|
||||
require("../../public/javascripts/L.Path.Drag.js")
|
||||
|
||||
@ -123,17 +124,17 @@ export class Map extends L.Map {
|
||||
const el = ev.detail._element;
|
||||
el?.classList.toggle("off");
|
||||
this.setHiddenType(ev.detail.coalition, !el?.classList.contains("off"));
|
||||
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
});
|
||||
|
||||
document.addEventListener("toggleMarkerVisibility", (ev: CustomEventInit) => {
|
||||
const el = ev.detail._element;
|
||||
el?.classList.toggle("off");
|
||||
ev.detail.types.forEach((type: string) => this.setHiddenType(type, !el?.classList.contains("off")));
|
||||
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
|
||||
if (ev.detail.types.includes("airbase")) {
|
||||
Object.values(getMissionHandler().getAirbases()).forEach((airbase: Airbase) => {
|
||||
Object.values(getApp().getMissionManager().getAirbases()).forEach((airbase: Airbase) => {
|
||||
if (el?.classList.contains("off"))
|
||||
airbase.removeFrom(this);
|
||||
else
|
||||
@ -178,16 +179,16 @@ export class Map extends L.Map {
|
||||
document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]);
|
||||
|
||||
/* Create the checkboxes to select the advanced visibility options */
|
||||
this.#visibilityOptions[SHOW_CONTACT_LINES] = false;
|
||||
this.#visibilityOptions[HIDE_GROUP_MEMBERS] = true;
|
||||
this.#visibilityOptions[SHOW_UNIT_PATHS] = true;
|
||||
this.#visibilityOptions[SHOW_UNIT_TARGETS] = true;
|
||||
this.#visibilityOptions[SHOW_UNIT_LABELS] = true;
|
||||
this.#mapVisibilityOptionsDropdown.setOptionsElements(Object.keys(this.#visibilityOptions).map((option: string) => {
|
||||
return createCheckboxOption(option, option, this.#visibilityOptions[option], (ev: any) => {
|
||||
this.#setVisibilityOption(option, ev);
|
||||
});
|
||||
}));
|
||||
this.addVisibilityOption(SHOW_CONTACT_LINES, false);
|
||||
this.addVisibilityOption(HIDE_GROUP_MEMBERS, true);
|
||||
this.addVisibilityOption(SHOW_UNIT_PATHS, true);
|
||||
this.addVisibilityOption(SHOW_UNIT_TARGETS, true);
|
||||
this.addVisibilityOption(SHOW_UNIT_LABELS, true);
|
||||
}
|
||||
|
||||
addVisibilityOption(option: string, defaultValue: boolean) {
|
||||
this.#visibilityOptions[option] = defaultValue;
|
||||
this.#mapVisibilityOptionsDropdown.addOptionElement(createCheckboxOption(option, option, defaultValue, (ev: any) => { this.#setVisibilityOption(option, ev); }));
|
||||
}
|
||||
|
||||
setLayer(layerName: string) {
|
||||
@ -252,7 +253,7 @@ export class Map extends L.Map {
|
||||
else {
|
||||
this.#hiddenTypes.push(key);
|
||||
}
|
||||
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
}
|
||||
|
||||
getHiddenTypes() {
|
||||
@ -359,7 +360,7 @@ export class Map extends L.Map {
|
||||
centerOnUnit(ID: number | null) {
|
||||
if (ID != null) {
|
||||
this.options.scrollWheelZoom = 'center';
|
||||
this.#centerUnit = getUnitsManager().getUnitByID(ID);
|
||||
this.#centerUnit = getApp().getUnitsManager().getUnitByID(ID);
|
||||
}
|
||||
else {
|
||||
this.options.scrollWheelZoom = undefined;
|
||||
@ -479,7 +480,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
else {
|
||||
this.setState(IDLE);
|
||||
getUnitsManager().deselectAllUnits();
|
||||
getApp().getUnitsManager().deselectAllUnits();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -517,9 +518,9 @@ export class Map extends L.Map {
|
||||
}
|
||||
else if (this.#state === MOVE_UNIT) {
|
||||
if (!e.originalEvent.ctrlKey) {
|
||||
getUnitsManager().selectedUnitsClearDestinations();
|
||||
getApp().getUnitsManager().selectedUnitsClearDestinations();
|
||||
}
|
||||
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation)
|
||||
getApp().getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation)
|
||||
|
||||
this.#destinationGroupRotation = 0;
|
||||
this.#destinationRotationCenter = null;
|
||||
@ -542,7 +543,7 @@ export class Map extends L.Map {
|
||||
this.#leftClickTimer = window.setTimeout(() => {
|
||||
this.#preventLeftClick = false;
|
||||
}, 200);
|
||||
getUnitsManager().selectFromBounds(e.selectionBounds);
|
||||
getApp().getUnitsManager().selectFromBounds(e.selectionBounds);
|
||||
this.#updateCursor();
|
||||
}
|
||||
|
||||
@ -567,15 +568,15 @@ export class Map extends L.Map {
|
||||
this.#longPressHandled = true;
|
||||
|
||||
var options: { [key: string]: { text: string, tooltip: string } } = {};
|
||||
const selectedUnits = getUnitsManager().getSelectedUnits();
|
||||
const selectedUnitTypes = getUnitsManager().getSelectedUnitsCategories();
|
||||
const selectedUnits = getApp().getUnitsManager().getSelectedUnits();
|
||||
const selectedUnitTypes = getApp().getUnitsManager().getSelectedUnitsCategories();
|
||||
|
||||
if (selectedUnitTypes.length === 1 && ["Aircraft", "Helicopter"].includes(selectedUnitTypes[0])) {
|
||||
if (selectedUnits.every((unit: Unit) => { return unit.canFulfillRole(["CAS", "Strike"]) })) {
|
||||
options["bomb"] = { text: "Precision bombing", tooltip: "Precision bombing of a specific point" };
|
||||
options["carpet-bomb"] = { text: "Carpet bombing", tooltip: "Carpet bombing close to a point" };
|
||||
} else {
|
||||
getInfoPopup().setText(`Selected units can not perform point actions.`);
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Selected units can not perform point actions.`);
|
||||
}
|
||||
}
|
||||
else if (selectedUnitTypes.length === 1 && ["GroundUnit", "NavyUnit"].includes(selectedUnitTypes[0])) {
|
||||
@ -584,10 +585,10 @@ export class Map extends L.Map {
|
||||
options["simulate-fire-fight"] = { text: "Simulate fire fight", tooltip: "Simulate a fire fight by shooting randomly in a certain large area" };
|
||||
}
|
||||
else
|
||||
getInfoPopup().setText(`Selected units can not perform point actions.`);
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Selected units can not perform point actions.`);
|
||||
}
|
||||
else if(selectedUnitTypes.length > 1) {
|
||||
getInfoPopup().setText(`Multiple unit types selected, no common actions available.`);
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Multiple unit types selected, no common actions available.`);
|
||||
}
|
||||
|
||||
if (Object.keys(options).length > 0) {
|
||||
@ -595,16 +596,16 @@ export class Map extends L.Map {
|
||||
this.getUnitContextMenu().setOptions(options, (option: string) => {
|
||||
this.hideUnitContextMenu();
|
||||
if (option === "bomb") {
|
||||
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
|
||||
getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getApp().getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
|
||||
}
|
||||
else if (option === "carpet-bomb") {
|
||||
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
|
||||
getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getApp().getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
|
||||
}
|
||||
else if (option === "fire-at-area") {
|
||||
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
|
||||
getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getApp().getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
|
||||
}
|
||||
else if (option === "simulate-fire-fight") {
|
||||
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
@ -701,7 +702,7 @@ export class Map extends L.Map {
|
||||
|
||||
#showDestinationCursors() {
|
||||
const singleCursor = !this.#shiftKey;
|
||||
const selectedUnitsCount = getUnitsManager().getSelectedUnits({ excludeHumans: false, onlyOnePerGroup: true }).length;
|
||||
const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: false, onlyOnePerGroup: true }).length;
|
||||
if (selectedUnitsCount > 0) {
|
||||
if (singleCursor && this.#destinationPreviewCursors.length != 1) {
|
||||
this.#hideDestinationCursors();
|
||||
@ -731,7 +732,7 @@ export class Map extends L.Map {
|
||||
if (this.#destinationPreviewCursors.length == 1)
|
||||
this.#destinationPreviewCursors[0].setLatLng(this.getMouseCoordinates());
|
||||
else {
|
||||
Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
|
||||
Object.values(getApp().getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
|
||||
if (idx < this.#destinationPreviewCursors.length)
|
||||
this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey ? latlng : this.getMouseCoordinates());
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getMap } from "../..";
|
||||
import { getApp } from "../..";
|
||||
|
||||
export class SmokeMarker extends CustomMarker {
|
||||
#color: string;
|
||||
@ -10,7 +10,7 @@ export class SmokeMarker extends CustomMarker {
|
||||
super(latlng, options);
|
||||
this.setZIndexOffset(9999);
|
||||
this.#color = color;
|
||||
window.setTimeout(() => { this.removeFrom(getMap()); }, 300000) /* Remove the smoke after 5 minutes */
|
||||
window.setTimeout(() => { this.removeFrom(getApp().getMap()); }, 300000) /* Remove the smoke after 5 minutes */
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
|
||||
@ -2,8 +2,7 @@ import { CustomMarker } from "./custommarker";
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../../other/utils";
|
||||
import { isCommandExecuted } from "../../server/server";
|
||||
import { getMap } from "../..";
|
||||
import { getApp } from "../..";
|
||||
|
||||
export class TemporaryUnitMarker extends CustomMarker {
|
||||
#name: string;
|
||||
@ -25,9 +24,9 @@ export class TemporaryUnitMarker extends CustomMarker {
|
||||
this.#commandHash = commandHash;
|
||||
this.#timer = window.setInterval(() => {
|
||||
if (this.#commandHash !== undefined) {
|
||||
isCommandExecuted((res: any) => {
|
||||
getApp().getServerManager().isCommandExecuted((res: any) => {
|
||||
if (res.commandExecuted) {
|
||||
this.removeFrom(getMap());
|
||||
this.removeFrom(getApp().getMap());
|
||||
window.clearInterval(this.#timer);
|
||||
}
|
||||
}, this.#commandHash)
|
||||
|
||||
@ -2,30 +2,6 @@ import { DivIcon } from 'leaflet';
|
||||
import { CustomMarker } from '../map/markers/custommarker';
|
||||
import { SVGInjector } from '@tanem/svg-injector';
|
||||
|
||||
export interface AirbaseOptions {
|
||||
name: string,
|
||||
position: L.LatLng
|
||||
}
|
||||
|
||||
|
||||
export interface AirbaseChartData {
|
||||
elevation: string,
|
||||
ICAO: string,
|
||||
TACAN: string,
|
||||
runways: AirbaseChartRunwayData[]
|
||||
}
|
||||
|
||||
export interface AirbaseChartRunwayData {
|
||||
"headings": AirbaseChartRunwayHeadingData[],
|
||||
"length": string
|
||||
}
|
||||
|
||||
export interface AirbaseChartRunwayHeadingData {
|
||||
[index: string]: {
|
||||
"magHeading": string,
|
||||
"ILS": string
|
||||
}
|
||||
}
|
||||
|
||||
export class Airbase extends CustomMarker {
|
||||
#name: string = "";
|
||||
@ -66,6 +42,12 @@ export class Airbase extends CustomMarker {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getInfoPopup, getMap } from "..";
|
||||
import { getApp } from "..";
|
||||
import { Airbase } from "./airbase";
|
||||
import { Bullseye } from "./bullseye";
|
||||
import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
|
||||
import { refreshAll, setCommandModeOptions } from "../server/server";
|
||||
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";
|
||||
|
||||
/** The MissionManager */
|
||||
export class MissionManager {
|
||||
@ -29,7 +29,7 @@ export class MissionManager {
|
||||
document.addEventListener("applycommandModeOptions", () => this.#applycommandModeOptions());
|
||||
|
||||
/* command-mode settings dialog */
|
||||
this.#commandModeDialog = <HTMLElement> document.querySelector("#command-mode-settings-dialog");
|
||||
this.#commandModeDialog = document.querySelector("#command-mode-settings-dialog") as HTMLElement;
|
||||
|
||||
this.#commandModeErasDropdown = new Dropdown("command-mode-era-options", () => {});
|
||||
}
|
||||
@ -38,7 +38,7 @@ export class MissionManager {
|
||||
for (let idx in data.bullseyes) {
|
||||
const bullseye = data.bullseyes[idx];
|
||||
if (!(idx in this.#bullseyes))
|
||||
this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getMap());
|
||||
this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getApp().getMap());
|
||||
|
||||
if (bullseye.latitude && bullseye.longitude && bullseye.coalition) {
|
||||
this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude));
|
||||
@ -54,7 +54,7 @@ export class MissionManager {
|
||||
this.#airbases[airbase.callsign] = new Airbase({
|
||||
position: new LatLng(airbase.latitude, airbase.longitude),
|
||||
name: airbase.callsign
|
||||
}).addTo(getMap());
|
||||
}).addTo(getApp().getMap());
|
||||
this.#airbases[airbase.callsign].on('contextmenu', (e) => this.#onAirbaseClick(e));
|
||||
this.#loadAirbaseChartData(airbase.callsign);
|
||||
}
|
||||
@ -72,8 +72,8 @@ export class MissionManager {
|
||||
/* Set the mission theatre */
|
||||
if (data.mission.theatre != this.#theatre) {
|
||||
this.#theatre = data.mission.theatre;
|
||||
getMap().setTheatre(this.#theatre);
|
||||
getInfoPopup().setText("Map set to " + this.#theatre);
|
||||
getApp().getMap().setTheatre(this.#theatre);
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Map set to " + this.#theatre);
|
||||
}
|
||||
|
||||
/* Set the date and time data */
|
||||
@ -190,7 +190,7 @@ export class MissionManager {
|
||||
var eras: string[] = [];
|
||||
const enabledEras = getCheckboxOptions(this.#commandModeErasDropdown);
|
||||
Object.keys(enabledEras).forEach((key: string) => {if (enabledEras[key]) eras.push(key)});
|
||||
setCommandModeOptions(restrictSpawnsCheckbox.checked, restrictToCoalitionCheckbox.checked, {blue: parseInt(blueSpawnPointsInput.value), red: parseInt(redSpawnPointsInput.value)}, eras, parseInt(setupTimeInput.value) * 60);
|
||||
getApp().getServerManager().setCommandModeOptions(restrictSpawnsCheckbox.checked, restrictToCoalitionCheckbox.checked, {blue: parseInt(blueSpawnPointsInput.value), red: parseInt(redSpawnPointsInput.value)}, eras, parseInt(setupTimeInput.value) * 60);
|
||||
}
|
||||
|
||||
#setcommandModeOptions(commandModeOptions: CommandModeOptions) {
|
||||
@ -228,11 +228,11 @@ export class MissionManager {
|
||||
document.querySelector("#command-mode-settings-button")?.classList.toggle("hide", this.getCommandModeOptions().commandMode !== GAME_MASTER);
|
||||
|
||||
if (requestRefresh)
|
||||
refreshAll();
|
||||
getApp().getServerManager().refreshAll();
|
||||
}
|
||||
|
||||
#onAirbaseClick(e: any) {
|
||||
getMap().showAirbaseContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng, e.sourceTarget);
|
||||
getApp().getMap().showAirbaseContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng, e.sourceTarget);
|
||||
}
|
||||
|
||||
#loadAirbaseChartData(callsign: string) {
|
||||
|
||||
7
client/src/other/eventsmanager.ts
Normal file
7
client/src/other/eventsmanager.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Manager } from "./manager";
|
||||
|
||||
export abstract class EventsManager extends Manager {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
33
client/src/other/manager.ts
Normal file
33
client/src/other/manager.ts
Normal file
@ -0,0 +1,33 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,7 +7,6 @@ import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import { Buffer } from "buffer";
|
||||
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { UnitBlueprint } from "../@types/unitdatabase";
|
||||
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
|
||||
|
||||
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
import { Panel } from "./panel";
|
||||
|
||||
export class ConnectionStatusPanel extends Panel {
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
constructor(ID: string) {
|
||||
super( ID );
|
||||
}
|
||||
|
||||
|
||||
update(connected: boolean) {
|
||||
this.getElement().toggleAttribute( "data-is-connected", connected );
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getUnitsManager } from "..";
|
||||
import { getApp } from "..";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { Panel } from "./panel";
|
||||
|
||||
@ -15,7 +15,7 @@ export class HotgroupPanel extends Panel {
|
||||
refreshHotgroups() {
|
||||
for (let hotgroup = 1; hotgroup <= 9; hotgroup++){
|
||||
this.removeHotgroup(hotgroup);
|
||||
if (getUnitsManager().getUnitsByHotgroup(hotgroup).length > 0)
|
||||
if (getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).length > 0)
|
||||
this.addHotgroup(hotgroup);
|
||||
|
||||
}
|
||||
@ -32,7 +32,7 @@ export class HotgroupPanel extends Panel {
|
||||
|
||||
// Hotgroup unit count
|
||||
var countDiv = document.createElement("div");
|
||||
countDiv.innerText = `x${getUnitsManager().getUnitsByHotgroup(hotgroup).length}`;
|
||||
countDiv.innerText = `x${getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).length}`;
|
||||
|
||||
var el = document.createElement("div");
|
||||
el.appendChild(hotgroupDiv);
|
||||
@ -43,15 +43,15 @@ export class HotgroupPanel extends Panel {
|
||||
this.getElement().appendChild(el);
|
||||
|
||||
el.addEventListener("click", () => {
|
||||
getUnitsManager().selectUnitsByHotgroup(hotgroup);
|
||||
getApp().getUnitsManager().selectUnitsByHotgroup(hotgroup);
|
||||
});
|
||||
|
||||
el.addEventListener("mouseover", () => {
|
||||
getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(true));
|
||||
getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(true));
|
||||
});
|
||||
|
||||
el.addEventListener("mouseout", () => {
|
||||
getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(false));
|
||||
getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(false));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { getMouseInfoPanel } from "..";
|
||||
import { getApp } from "..";
|
||||
import { MouseInfoPanel } from "./mouseinfopanel";
|
||||
import { Panel } from "./panel";
|
||||
|
||||
export class LogPanel extends Panel {
|
||||
@ -37,7 +38,7 @@ export class LogPanel extends Panel {
|
||||
});
|
||||
|
||||
|
||||
const mouseInfoPanel = getMouseInfoPanel();
|
||||
const mouseInfoPanel = getApp().getPanelsManager().get("mouseInfo") as MouseInfoPanel;
|
||||
new ResizeObserver(() => this.#calculateHeight()).observe(mouseInfoPanel.getElement())
|
||||
}
|
||||
|
||||
@ -89,7 +90,7 @@ export class LogPanel extends Panel {
|
||||
}
|
||||
|
||||
#calculateHeight() {
|
||||
const mouseInfoPanel = getMouseInfoPanel();
|
||||
const mouseInfoPanel = getApp().getPanelsManager().get("mouseInfo");
|
||||
if (this.#open)
|
||||
this.getElement().style.height = `${mouseInfoPanel.getElement().offsetTop - this.getElement().offsetTop - 10}px`;
|
||||
else
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Icon, LatLng, Marker, Polyline } from "leaflet";
|
||||
import { getMap, getMissionHandler, getUnitsManager } from "..";
|
||||
import { getApp } from "..";
|
||||
import { distance, bearing, zeroAppend, mToNm, nmToFt } from "../other/utils";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { Panel } from "./panel";
|
||||
@ -12,12 +12,8 @@ export class MouseInfoPanel extends Panel {
|
||||
#measureLine: Polyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1, interactive: false });
|
||||
#measureBox: HTMLElement;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
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 });
|
||||
@ -26,19 +22,19 @@ export class MouseInfoPanel extends Panel {
|
||||
this.#measureBox.classList.add("ol-measure-box", "hide");
|
||||
document.body.appendChild(this.#measureBox);
|
||||
|
||||
getMap()?.on("click", (e: any) => this.#onMapClick(e));
|
||||
getMap()?.on('zoom', (e: any) => this.#onZoom(e));
|
||||
getMap()?.on('mousemove', (e: any) => this.#onMouseMove(e));
|
||||
getApp().getMap()?.on("click", (e: any) => this.#onMapClick(e));
|
||||
getApp().getMap()?.on('zoom', (e: any) => this.#onZoom(e));
|
||||
getApp().getMap()?.on('mousemove', (e: any) => this.#onMouseMove(e));
|
||||
|
||||
document.addEventListener('unitsSelection', (e: CustomEvent<Unit[]>) => this.#update());
|
||||
document.addEventListener('clearSelection', () => this.#update());
|
||||
}
|
||||
|
||||
#update() {
|
||||
const mousePosition = getMap().getMouseCoordinates();
|
||||
const mousePosition = getApp().getMap().getMouseCoordinates();
|
||||
|
||||
var selectedUnitPosition = null;
|
||||
var selectedUnits = getUnitsManager().getSelectedUnits();
|
||||
var selectedUnits = getApp().getUnitsManager().getSelectedUnits();
|
||||
if (selectedUnits && selectedUnits.length == 1)
|
||||
selectedUnitPosition = new LatLng(selectedUnits[0].getPosition().lat, selectedUnits[0].getPosition().lng);
|
||||
|
||||
@ -48,7 +44,7 @@ export class MouseInfoPanel extends Panel {
|
||||
|
||||
this.getElement().querySelector(`#measuring-tool`)?.classList.toggle("hide", this.#measurePoint === null && selectedUnitPosition === null);
|
||||
|
||||
var bullseyes = getMissionHandler().getBullseyes();
|
||||
var bullseyes = getApp().getMissionManager().getBullseyes();
|
||||
for (let idx in bullseyes)
|
||||
this.#drawMeasure(null, `bullseye-${idx}`, bullseyes[idx].getLatLng(), mousePosition);
|
||||
|
||||
@ -65,19 +61,19 @@ export class MouseInfoPanel extends Panel {
|
||||
this.#measureBox.classList.toggle("hide", false);
|
||||
this.#measurePoint = e.latlng;
|
||||
this.#measureMarker.setLatLng(e.latlng);
|
||||
this.#measureMarker.addTo(getMap());
|
||||
if (!getMap().hasLayer(this.#measureLine))
|
||||
this.#measureLine.addTo(getMap());
|
||||
this.#measureMarker.addTo(getApp().getMap());
|
||||
if (!getApp().getMap().hasLayer(this.#measureLine))
|
||||
this.#measureLine.addTo(getApp().getMap());
|
||||
}
|
||||
else {
|
||||
this.#measureBox.classList.toggle("hide", true);
|
||||
this.#measurePoint = null;
|
||||
if (getMap().hasLayer(this.#measureMarker))
|
||||
getMap().removeLayer(this.#measureMarker);
|
||||
if (getApp().getMap().hasLayer(this.#measureMarker))
|
||||
getApp().getMap().removeLayer(this.#measureMarker);
|
||||
|
||||
this.#measureLine.setLatLngs([]);
|
||||
if (getMap().hasLayer(this.#measureLine))
|
||||
getMap().removeLayer(this.#measureLine);
|
||||
if (getApp().getMap().hasLayer(this.#measureLine))
|
||||
getApp().getMap().removeLayer(this.#measureLine);
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,15 +81,15 @@ export class MouseInfoPanel extends Panel {
|
||||
}
|
||||
|
||||
#drawMeasureLine() {
|
||||
var mouseLatLng = getMap().containerPointToLatLng(getMap().getMousePosition());
|
||||
var mouseLatLng = getApp().getMap().containerPointToLatLng(getApp().getMap().getMousePosition());
|
||||
if (this.#measurePoint != null) {
|
||||
var points = [this.#measurePoint, mouseLatLng];
|
||||
this.#measureLine.setLatLngs(points);
|
||||
var dist = distance(this.#measurePoint.lat, this.#measurePoint.lng, mouseLatLng.lat, mouseLatLng.lng);
|
||||
var bear = bearing(this.#measurePoint.lat, this.#measurePoint.lng, mouseLatLng.lat, mouseLatLng.lng);
|
||||
var startXY = getMap().latLngToContainerPoint(this.#measurePoint);
|
||||
var dx = (getMap().getMousePosition().x - startXY.x);
|
||||
var dy = (getMap().getMousePosition().y - startXY.y);
|
||||
var startXY = getApp().getMap().latLngToContainerPoint(this.#measurePoint);
|
||||
var dx = (getApp().getMap().getMousePosition().x - startXY.x);
|
||||
var dy = (getApp().getMap().getMousePosition().y - startXY.y);
|
||||
|
||||
var angle = Math.atan2(dy, dx);
|
||||
if (angle > Math.PI / 2)
|
||||
@ -112,8 +108,8 @@ export class MouseInfoPanel extends Panel {
|
||||
let data = [`${bng}°`, `${str} ${unit}`];
|
||||
|
||||
this.#measureBox.innerText = data.join(" / ");
|
||||
this.#measureBox.style.left = (getMap().getMousePosition().x + startXY.x) / 2 - this.#measureBox.offsetWidth / 2 + "px";
|
||||
this.#measureBox.style.top = (getMap().getMousePosition().y + startXY.y) / 2 - this.#measureBox.offsetHeight / 2 + "px";
|
||||
this.#measureBox.style.left = (getApp().getMap().getMousePosition().x + startXY.x) / 2 - this.#measureBox.offsetWidth / 2 + "px";
|
||||
this.#measureBox.style.top = (getApp().getMap().getMousePosition().y + startXY.y) / 2 - this.#measureBox.offsetHeight / 2 + "px";
|
||||
this.#measureBox.style.rotate = angle + "rad";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,30 +1,31 @@
|
||||
export class Panel {
|
||||
#element: HTMLElement
|
||||
#visible: boolean = true;
|
||||
import { PanelEventsManager } from "./paneleventsmanager";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
this.#element = document.getElementById(ID) as HTMLElement;
|
||||
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.#visible = true;
|
||||
this.getEventsManager()?.trigger("show", {});
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.#element.classList.toggle("hide", true);
|
||||
this.#visible = false;
|
||||
this.getEventsManager()?.trigger("hide", {});
|
||||
}
|
||||
|
||||
toggle() {
|
||||
// Simple way to track if currently visible
|
||||
if (this.#visible)
|
||||
if (this.getVisible())
|
||||
this.hide();
|
||||
else
|
||||
else
|
||||
this.show();
|
||||
}
|
||||
|
||||
@ -32,7 +33,11 @@ export class Panel {
|
||||
return this.#element;
|
||||
}
|
||||
|
||||
getVisible(){
|
||||
return this.#visible;
|
||||
getVisible() {
|
||||
return (!this.getElement().classList.contains("hide"));
|
||||
}
|
||||
|
||||
getEventsManager() {
|
||||
return this.#eventsManager;
|
||||
}
|
||||
}
|
||||
29
client/src/panels/paneleventsmanager.ts
Normal file
29
client/src/panels/paneleventsmanager.ts
Normal file
@ -0,0 +1,29 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,8 @@
|
||||
import { Panel } from "./panel";
|
||||
|
||||
export class ServerStatusPanel extends Panel {
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
constructor(ID: string) {
|
||||
super( ID );
|
||||
}
|
||||
|
||||
update(frameRate: number, load: number) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getUnitsManager } from "..";
|
||||
import { getApp } from "..";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Slider } from "../controls/slider";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
@ -8,7 +8,6 @@ import { Panel } from "./panel";
|
||||
import { Switch } from "../controls/switch";
|
||||
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants";
|
||||
import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils";
|
||||
import { GeneralSettings, Radio, TACAN } from "../@types/unit";
|
||||
|
||||
export class UnitControlPanel extends Panel {
|
||||
#altitudeSlider: Slider;
|
||||
@ -33,24 +32,24 @@ export class UnitControlPanel extends Panel {
|
||||
super(ID);
|
||||
|
||||
/* Unit control sliders */
|
||||
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getUnitsManager().selectedUnitsSetAltitude(ftToM(value)); });
|
||||
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetAltitudeType(value? "ASL": "AGL"); });
|
||||
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getApp().getUnitsManager().selectedUnitsSetAltitude(ftToM(value)); });
|
||||
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getApp().getUnitsManager().selectedUnitsSetAltitudeType(value? "ASL": "AGL"); });
|
||||
|
||||
this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getUnitsManager().selectedUnitsSetSpeed(knotsToMs(value)); });
|
||||
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "CAS": "GS"); });
|
||||
this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getApp().getUnitsManager().selectedUnitsSetSpeed(knotsToMs(value)); });
|
||||
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getApp().getUnitsManager().selectedUnitsSetSpeedType(value? "CAS": "GS"); });
|
||||
|
||||
/* Option buttons */
|
||||
// Reversing the ROEs so that the least "aggressive" option is always on the left
|
||||
this.#optionButtons["ROE"] = ROEs.slice(0).reverse().map((option: string, index: number) => {
|
||||
return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions.slice(0).reverse()[index], () => { getUnitsManager().selectedUnitsSetROE(option); });
|
||||
return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions.slice(0).reverse()[index], () => { getApp().getUnitsManager().selectedUnitsSetROE(option); });
|
||||
}).filter((button: HTMLButtonElement, index: number) => {return ROEs[index] !== "";});
|
||||
|
||||
this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string, index: number) => {
|
||||
return this.#createOptionButton(option, `threat/${option.toLowerCase()}.svg`, reactionsToThreatDescriptions[index],() => { getUnitsManager().selectedUnitsSetReactionToThreat(option); });
|
||||
return this.#createOptionButton(option, `threat/${option.toLowerCase()}.svg`, reactionsToThreatDescriptions[index],() => { getApp().getUnitsManager().selectedUnitsSetReactionToThreat(option); });
|
||||
});
|
||||
|
||||
this.#optionButtons["emissionsCountermeasures"] = emissionsCountermeasures.map((option: string, index: number) => {
|
||||
return this.#createOptionButton(option, `emissions/${option.toLowerCase()}.svg`, emissionsCountermeasuresDescriptions[index],() => { getUnitsManager().selectedUnitsSetEmissionsCountermeasures(option); });
|
||||
return this.#createOptionButton(option, `emissions/${option.toLowerCase()}.svg`, emissionsCountermeasuresDescriptions[index],() => { getApp().getUnitsManager().selectedUnitsSetEmissionsCountermeasures(option); });
|
||||
});
|
||||
|
||||
this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]);
|
||||
@ -59,12 +58,12 @@ export class UnitControlPanel extends Panel {
|
||||
|
||||
/* On off switch */
|
||||
this.#onOffSwitch = new Switch("on-off-switch", (value: boolean) => {
|
||||
getUnitsManager().selectedUnitsSetOnOff(value);
|
||||
getApp().getUnitsManager().selectedUnitsSetOnOff(value);
|
||||
});
|
||||
|
||||
/* Follow roads switch */
|
||||
this.#followRoadsSwitch = new Switch("follow-roads-switch", (value: boolean) => {
|
||||
getUnitsManager().selectedUnitsSetFollowRoads(value);
|
||||
getApp().getUnitsManager().selectedUnitsSetFollowRoads(value);
|
||||
});
|
||||
|
||||
/* Advanced settings dialog */
|
||||
@ -84,7 +83,7 @@ export class UnitControlPanel extends Panel {
|
||||
document.addEventListener("clearSelection", () => { this.hide() });
|
||||
document.addEventListener("applyAdvancedSettings", () => {this.#applyAdvancedSettings();})
|
||||
document.addEventListener("showAdvancedSettings", () => {
|
||||
this.#updateAdvancedSettingsDialog(getUnitsManager().getSelectedUnits());
|
||||
this.#updateAdvancedSettingsDialog(getApp().getUnitsManager().getSelectedUnits());
|
||||
this.#advancedSettingsDialog.classList.remove("hide");
|
||||
});
|
||||
|
||||
@ -108,8 +107,8 @@ export class UnitControlPanel extends Panel {
|
||||
}
|
||||
|
||||
addButtons() {
|
||||
this.#units = getUnitsManager().getSelectedUnits();
|
||||
this.#selectedUnitsTypes = getUnitsManager().getSelectedUnitsCategories();
|
||||
this.#units = getApp().getUnitsManager().getSelectedUnits();
|
||||
this.#selectedUnitsTypes = getApp().getUnitsManager().getSelectedUnitsCategories();
|
||||
|
||||
if (this.#units.length < 20) {
|
||||
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...this.#units.map((unit: Unit, index: number) => {
|
||||
@ -127,12 +126,12 @@ export class UnitControlPanel extends Panel {
|
||||
button.addEventListener("click", ( ev:MouseEventInit ) => {
|
||||
// Ctrl-click deselection
|
||||
if ( ev.ctrlKey === true && ev.shiftKey === false && ev.altKey === false ) {
|
||||
getUnitsManager().deselectUnit( unit.ID );
|
||||
getApp().getUnitsManager().deselectUnit( unit.ID );
|
||||
button.remove();
|
||||
// Deselect all
|
||||
} else {
|
||||
getUnitsManager().deselectAllUnits();
|
||||
getUnitsManager().selectUnit(unit.ID, true);
|
||||
getApp().getUnitsManager().deselectAllUnits();
|
||||
getApp().getUnitsManager().selectUnit(unit.ID, true);
|
||||
}
|
||||
});
|
||||
return (button);
|
||||
@ -161,12 +160,12 @@ export class UnitControlPanel extends Panel {
|
||||
|
||||
if (this.#selectedUnitsTypes.length == 1) {
|
||||
/* Flight controls */
|
||||
var desiredAltitude = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()});
|
||||
var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()});
|
||||
var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()});
|
||||
var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()});
|
||||
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()});
|
||||
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()});
|
||||
var desiredAltitude = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()});
|
||||
var desiredAltitudeType = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()});
|
||||
var desiredSpeed = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()});
|
||||
var desiredSpeedType = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()});
|
||||
var onOff = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()});
|
||||
var followRoads = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()});
|
||||
|
||||
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "ASL": undefined, false);
|
||||
this.#speedTypeSwitch.setValue(desiredSpeedType != undefined? desiredSpeedType == "CAS": undefined, false);
|
||||
@ -328,7 +327,7 @@ export class UnitControlPanel extends Panel {
|
||||
}
|
||||
|
||||
/* Send command and close */
|
||||
var units = getUnitsManager().getSelectedUnits();
|
||||
var units = getApp().getUnitsManager().getSelectedUnits();
|
||||
if (units.length > 0)
|
||||
units[0].setAdvancedOptions(isTanker, isAWACS, TACAN, radio, generalSettings);
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { Ammo } from "../@types/unit";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { Panel } from "./panel";
|
||||
@ -13,12 +12,8 @@ export class UnitInfoPanel extends Panel {
|
||||
#unitLabel: HTMLElement;
|
||||
#unitName: HTMLElement;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
constructor(ID: string) {
|
||||
super( ID );
|
||||
|
||||
this.#currentTask = (this.getElement().querySelector("#current-task")) as HTMLElement;
|
||||
this.#fuelBar = (this.getElement().querySelector("#fuel-bar")) as HTMLElement;
|
||||
|
||||
73
client/src/plugin/pluginmanager.ts
Normal file
73
client/src/plugin/pluginmanager.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import path from "path";
|
||||
import { Manager } from "../other/manager";
|
||||
import { getApp } from "..";
|
||||
|
||||
/** 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();
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,11 @@
|
||||
import { Panel } from "../panels/panel";
|
||||
|
||||
export class Popup extends Panel {
|
||||
|
||||
constructor( elementId:string ) {
|
||||
super( elementId );
|
||||
}
|
||||
|
||||
#fadeTime: number = 2000; // Milliseconds
|
||||
#hideTimer: number | undefined = undefined;
|
||||
#visibilityTimer: number | undefined = undefined;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../@types/unit";
|
||||
|
||||
export class DataExtractor {
|
||||
#seekPosition = 0;
|
||||
|
||||
493
client/src/server/servermanager.ts
Normal file
493
client/src/server/servermanager.ts
Normal file
@ -0,0 +1,493 @@
|
||||
import { LatLng } from 'leaflet';
|
||||
import { getApp } from '..';
|
||||
import { AIRBASES_URI, BULLSEYE_URI, COMMANDS_URI, LOGS_URI, MISSION_URI, NONE, ROEs, UNITS_URI, WEAPONS_URI, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
|
||||
import { ServerStatusPanel } from '../panels/serverstatuspanel';
|
||||
import { LogPanel } from '../panels/logpanel';
|
||||
import { Popup } from '../popups/popup';
|
||||
import { ConnectionStatusPanel } from '../panels/connectionstatuspanel';
|
||||
|
||||
export class ServerManager {
|
||||
#connected: boolean = false;
|
||||
#paused: boolean = false;
|
||||
#REST_ADDRESS = "http://localhost:30000/olympus";
|
||||
#DEMO_ADDRESS = window.location.href + "demo";
|
||||
#username = "";
|
||||
#password = "";
|
||||
#sessionHash: string | null = null;
|
||||
#lastUpdateTimes: {[key: string]: number} = {}
|
||||
#demoEnabled = false;
|
||||
|
||||
constructor() {
|
||||
this.#lastUpdateTimes[UNITS_URI] = Date.now();
|
||||
this.#lastUpdateTimes[WEAPONS_URI] = Date.now();
|
||||
this.#lastUpdateTimes[LOGS_URI] = Date.now();
|
||||
this.#lastUpdateTimes[AIRBASES_URI] = Date.now();
|
||||
this.#lastUpdateTimes[BULLSEYE_URI] = Date.now();
|
||||
this.#lastUpdateTimes[MISSION_URI] = Date.now();
|
||||
}
|
||||
|
||||
toggleDemoEnabled() {
|
||||
this.#demoEnabled = !this.#demoEnabled;
|
||||
}
|
||||
|
||||
setCredentials(newUsername: string, newPassword: string) {
|
||||
this.#username = newUsername;
|
||||
this.#password = newPassword;
|
||||
}
|
||||
|
||||
GET(callback: CallableFunction, uri: string, options?: ServerRequestOptions, responseType?: string) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
|
||||
/* Assemble the request options string */
|
||||
var optionsString = '';
|
||||
if (options?.time != undefined)
|
||||
optionsString = `time=${options.time}`;
|
||||
if (options?.commandHash != undefined)
|
||||
optionsString = `commandHash=${options.commandHash}`;
|
||||
|
||||
/* On the connection */
|
||||
xmlHttp.open("GET", `${this.#demoEnabled ? this.#DEMO_ADDRESS : this.#REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true);
|
||||
|
||||
/* If provided, set the credentials */
|
||||
if (this.#username && this.#password)
|
||||
xmlHttp.setRequestHeader("Authorization", "Basic " + btoa(`${this.#username}:${this.#password}`));
|
||||
|
||||
/* If specified, set the response type */
|
||||
if (responseType)
|
||||
xmlHttp.responseType = responseType as XMLHttpRequestResponseType;
|
||||
|
||||
xmlHttp.onload = (e) => {
|
||||
if (xmlHttp.status == 200) {
|
||||
/* Success */
|
||||
this.setConnected(true);
|
||||
if (xmlHttp.responseType == 'arraybuffer')
|
||||
this.#lastUpdateTimes[uri] = callback(xmlHttp.response);
|
||||
else {
|
||||
const result = JSON.parse(xmlHttp.responseText);
|
||||
this.#lastUpdateTimes[uri] = callback(result);
|
||||
|
||||
if (result.frameRate !== undefined && result.load !== undefined)
|
||||
(getApp().getPanelsManager().get("serverStatus") as ServerStatusPanel).update(result.frameRate, result.load);
|
||||
}
|
||||
} else if (xmlHttp.status == 401) {
|
||||
/* Bad credentials */
|
||||
console.error("Incorrect username/password");
|
||||
getApp().setLoginStatus("failed");
|
||||
} else {
|
||||
/* Failure, probably disconnected */
|
||||
this.setConnected(false);
|
||||
}
|
||||
};
|
||||
xmlHttp.onerror = (res) => {
|
||||
console.error("An error occurred during the XMLHttpRequest");
|
||||
this.setConnected(false);
|
||||
};
|
||||
xmlHttp.send(null);
|
||||
}
|
||||
|
||||
POST(request: object, callback: CallableFunction) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.open("PUT", this.#demoEnabled ? this.#DEMO_ADDRESS : this.#REST_ADDRESS);
|
||||
xmlHttp.setRequestHeader("Content-Type", "application/json");
|
||||
if (this.#username && this.#password)
|
||||
xmlHttp.setRequestHeader("Authorization", "Basic " + btoa(`${this.#username}:${this.#password}`));
|
||||
xmlHttp.onload = (res: any) => {
|
||||
var res = JSON.parse(xmlHttp.responseText);
|
||||
callback(res);
|
||||
};
|
||||
xmlHttp.send(JSON.stringify(request));
|
||||
}
|
||||
|
||||
getConfig(callback: CallableFunction) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.open("GET", window.location.href + "config", true);
|
||||
xmlHttp.onload = function (e) {
|
||||
var data = JSON.parse(xmlHttp.responseText);
|
||||
callback(data);
|
||||
};
|
||||
xmlHttp.onerror = function () {
|
||||
console.error("An error occurred during the XMLHttpRequest, could not retrieve configuration file");
|
||||
};
|
||||
xmlHttp.send(null);
|
||||
}
|
||||
|
||||
setAddress(address: string, port: number) {
|
||||
this.#REST_ADDRESS = `http://${address}:${port}/olympus`
|
||||
console.log(`Setting REST address to ${this.#REST_ADDRESS}`)
|
||||
}
|
||||
|
||||
getAirbases(callback: CallableFunction) {
|
||||
this.GET(callback, AIRBASES_URI);
|
||||
}
|
||||
|
||||
getBullseye(callback: CallableFunction) {
|
||||
this.GET(callback, BULLSEYE_URI);
|
||||
}
|
||||
|
||||
getLogs(callback: CallableFunction, refresh: boolean = false) {
|
||||
this.GET(callback, LOGS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[LOGS_URI]});
|
||||
}
|
||||
|
||||
getMission(callback: CallableFunction) {
|
||||
this.GET(callback, MISSION_URI);
|
||||
}
|
||||
|
||||
getUnits(callback: CallableFunction, refresh: boolean = false) {
|
||||
this.GET(callback, UNITS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[UNITS_URI] }, 'arraybuffer');
|
||||
}
|
||||
|
||||
getWeapons(callback: CallableFunction, refresh: boolean = false) {
|
||||
this.GET(callback, WEAPONS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[WEAPONS_URI] }, 'arraybuffer');
|
||||
}
|
||||
|
||||
isCommandExecuted(callback: CallableFunction, commandHash: string) {
|
||||
this.GET(callback, COMMANDS_URI, { commandHash: commandHash});
|
||||
}
|
||||
|
||||
addDestination(ID: number, path: any, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "path": path }
|
||||
var data = { "setPath": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
spawnSmoke(color: string, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "color": color, "location": latlng };
|
||||
var data = { "smoke": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
spawnExplosion(intensity: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "intensity": intensity, "location": latlng };
|
||||
var data = { "explosion": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
spawnAircrafts(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };
|
||||
var data = { "spawnAircrafts": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
spawnHelicopters(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };
|
||||
var data = { "spawnHelicopters": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
spawnGroundUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "units": units, "coalition": coalition, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };;
|
||||
var data = { "spawnGroundUnits": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
spawnNavyUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "units": units, "coalition": coalition, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };
|
||||
var data = { "spawnNavyUnits": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
attackUnit(ID: number, targetID: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "targetID": targetID };
|
||||
var data = { "attackUnit": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
followUnit(ID: number, targetID: number, offset: { "x": number, "y": number, "z": number }, callback: CallableFunction = () => {}) {
|
||||
// X: front-rear, positive front
|
||||
// Y: top-bottom, positive bottom
|
||||
// Z: left-right, positive right
|
||||
|
||||
var command = { "ID": ID, "targetID": targetID, "offsetX": offset["x"], "offsetY": offset["y"], "offsetZ": offset["z"] };
|
||||
var data = { "followUnit": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
cloneUnits(units: {ID: number, location: LatLng}[], deleteOriginal: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "units": units, "deleteOriginal": deleteOriginal, "spawnPoints": spawnPoints };
|
||||
var data = { "cloneUnits": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
deleteUnit(ID: number, explosion: boolean, immediate: boolean, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "explosion": explosion, "immediate": immediate };
|
||||
var data = { "deleteUnit": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
landAt(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "location": latlng };
|
||||
var data = { "landAt": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
changeSpeed(ID: number, speedChange: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "change": speedChange }
|
||||
var data = { "changeSpeed": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
setSpeed(ID: number, speed: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "speed": speed }
|
||||
var data = { "setSpeed": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
setSpeedType(ID: number, speedType: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "speedType": speedType }
|
||||
var data = { "setSpeedType": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
changeAltitude(ID: number, altitudeChange: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "change": altitudeChange }
|
||||
var data = { "changeAltitude": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
setAltitudeType(ID: number, altitudeType: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "altitudeType": altitudeType }
|
||||
var data = { "setAltitudeType": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
setAltitude(ID: number, altitude: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "altitude": altitude }
|
||||
var data = { "setAltitude": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
createFormation(ID: number, isLeader: boolean, wingmenIDs: number[], callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader }
|
||||
var data = { "setLeader": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
setROE(ID: number, ROE: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "ROE": ROEs.indexOf(ROE) }
|
||||
var data = { "setROE": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
setReactionToThreat(ID: number, reactionToThreat: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "reactionToThreat": reactionsToThreat.indexOf(reactionToThreat) }
|
||||
var data = { "setReactionToThreat": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
setEmissionsCountermeasures(ID: number, emissionCountermeasure: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "emissionsCountermeasures": emissionsCountermeasures.indexOf(emissionCountermeasure) }
|
||||
var data = { "setEmissionsCountermeasures": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
setOnOff(ID: number, onOff: boolean, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "onOff": onOff }
|
||||
var data = { "setOnOff": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
setFollowRoads(ID: number, followRoads: boolean, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "followRoads": followRoads }
|
||||
var data = { "setFollowRoads": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
refuel(ID: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID };
|
||||
var data = { "refuel": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
bombPoint(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "location": latlng }
|
||||
var data = { "bombPoint": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
carpetBomb(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "location": latlng }
|
||||
var data = { "carpetBomb": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
bombBuilding(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "location": latlng }
|
||||
var data = { "bombBuilding": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
fireAtArea(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "location": latlng }
|
||||
var data = { "fireAtArea": command }
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback: CallableFunction = () => {}) {
|
||||
var command = {
|
||||
"ID": ID,
|
||||
"isTanker": isTanker,
|
||||
"isAWACS": isAWACS,
|
||||
"TACAN": TACAN,
|
||||
"radio": radio,
|
||||
"generalSettings": generalSettings
|
||||
};
|
||||
|
||||
var data = { "setAdvancedOptions": command };
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: {blue: number, red: number}, eras: string[], setupTime: number, callback: CallableFunction = () => {}) {
|
||||
var command = {
|
||||
"restrictSpawns": restrictSpawns,
|
||||
"restrictToCoalition": restrictToCoalition,
|
||||
"spawnPoints": spawnPoints,
|
||||
"eras": eras,
|
||||
"setupTime": setupTime
|
||||
};
|
||||
|
||||
var data = { "setCommandModeOptions": command };
|
||||
this.POST(data, callback);
|
||||
}
|
||||
|
||||
startUpdate() {
|
||||
window.setInterval(() => {
|
||||
if (!this.getPaused()) {
|
||||
this.getMission((data: MissionData) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
getApp().getMissionManager()?.updateMission(data);
|
||||
return data.time;
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
|
||||
this.getAirbases((data: AirbasesData) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
getApp().getMissionManager()?.updateAirbases(data);
|
||||
return data.time;
|
||||
});
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE){
|
||||
this.getBullseye((data: BullseyesData) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
getApp().getMissionManager()?.updateBullseyes(data);
|
||||
return data.time;
|
||||
});
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
|
||||
this.getLogs((data: any) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
(getApp().getPanelsManager().get("log") as LogPanel).appendLogs(data.logs)
|
||||
return data.time;
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
|
||||
this.getUnits((buffer: ArrayBuffer) => {
|
||||
var time = getApp().getUnitsManager()?.update(buffer);
|
||||
return time;
|
||||
}, false);
|
||||
}
|
||||
}, 250);
|
||||
|
||||
window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
|
||||
this.getWeapons((buffer: ArrayBuffer) => {
|
||||
var time = getApp().getWeaponsManager()?.update(buffer);
|
||||
return time;
|
||||
}, false);
|
||||
}
|
||||
}, 250);
|
||||
|
||||
window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
|
||||
this.getUnits((buffer: ArrayBuffer) => {
|
||||
var time = getApp().getUnitsManager()?.update(buffer);
|
||||
return time;
|
||||
}, true);
|
||||
(getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel).update(this.getConnected());
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
|
||||
this.getWeapons((buffer: ArrayBuffer) => {
|
||||
var time = getApp().getWeaponsManager()?.update(buffer);
|
||||
return time;
|
||||
}, true);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
refreshAll() {
|
||||
this.getAirbases((data: AirbasesData) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
getApp().getMissionManager()?.updateAirbases(data);
|
||||
return data.time;
|
||||
});
|
||||
|
||||
this.getBullseye((data: BullseyesData) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
getApp().getMissionManager()?.updateBullseyes(data);
|
||||
return data.time;
|
||||
});
|
||||
|
||||
this.getLogs((data: any) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
(getApp().getPanelsManager().get("log") as LogPanel).appendLogs(data.logs)
|
||||
return data.time;
|
||||
});
|
||||
|
||||
this.getWeapons((buffer: ArrayBuffer) => {
|
||||
var time = getApp().getWeaponsManager()?.update(buffer);
|
||||
return time;
|
||||
}, true);
|
||||
|
||||
this.getUnits((buffer: ArrayBuffer) => {
|
||||
var time = getApp().getUnitsManager()?.update(buffer);
|
||||
return time;
|
||||
}, true);
|
||||
}
|
||||
|
||||
checkSessionHash(newSessionHash: string) {
|
||||
if (this.#sessionHash != null) {
|
||||
if (newSessionHash !== this.#sessionHash)
|
||||
location.reload();
|
||||
}
|
||||
else
|
||||
this.#sessionHash = newSessionHash;
|
||||
}
|
||||
|
||||
setConnected(newConnected: boolean) {
|
||||
if (this.#connected != newConnected)
|
||||
newConnected ? (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Connected to DCS Olympus server") : (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Disconnected from DCS Olympus server");
|
||||
this.#connected = newConnected;
|
||||
|
||||
if (this.#connected) {
|
||||
document.querySelector("#splash-screen")?.classList.add("hide");
|
||||
document.querySelector("#gray-out")?.classList.add("hide");
|
||||
}
|
||||
}
|
||||
|
||||
getConnected() {
|
||||
return this.#connected;
|
||||
}
|
||||
|
||||
setPaused(newPaused: boolean) {
|
||||
this.#paused = newPaused;
|
||||
this.#paused ? (getApp().getPopupsManager().get("infoPopup") as Popup).setText("View paused") : (getApp().getPopupsManager().get("infoPopup") as Popup).setText("View unpaused");
|
||||
}
|
||||
|
||||
getPaused() {
|
||||
return this.#paused;
|
||||
}
|
||||
}
|
||||
42
client/src/shortcut/shortcut.ts
Normal file
42
client/src/shortcut/shortcut.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { keyEventWasInInput } from "../other/utils";
|
||||
|
||||
export abstract class Shortcut {
|
||||
#config: ShortcutOptions
|
||||
|
||||
constructor(config: ShortcutOptions) {
|
||||
this.#config = config;
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
return this.#config;
|
||||
}
|
||||
}
|
||||
|
||||
export class ShortcutKeyboard extends Shortcut {
|
||||
constructor(config: KeyboardShortcutOptions) {
|
||||
config.event = config.event || "keyup";
|
||||
super(config);
|
||||
|
||||
document.addEventListener(config.event, (ev: any) => {
|
||||
if (ev instanceof KeyboardEvent === false || keyEventWasInInput(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.code !== ev.code) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (((typeof config.altKey !== "boolean") || (typeof config.altKey === "boolean" && ev.altKey === config.altKey))
|
||||
&& ((typeof config.ctrlKey !== "boolean") || (typeof config.ctrlKey === "boolean" && ev.ctrlKey === config.ctrlKey))
|
||||
&& ((typeof config.shiftKey !== "boolean") || (typeof config.shiftKey === "boolean" && ev.shiftKey === config.shiftKey))) {
|
||||
config.callback(ev);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ShortcutMouse extends Shortcut {
|
||||
constructor(config: MouseShortcutOptions) {
|
||||
super(config);
|
||||
}
|
||||
}
|
||||
53
client/src/shortcut/shortcutmanager.ts
Normal file
53
client/src/shortcut/shortcutmanager.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Manager } from "../other/manager";
|
||||
import { Shortcut } from "./shortcut";
|
||||
|
||||
export class ShortcutManager extends Manager {
|
||||
|
||||
#keysBeingHeld: string[] = [];
|
||||
#keyDownCallbacks: CallableFunction[] = [];
|
||||
#keyUpCallbacks: CallableFunction[] = [];
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
document.addEventListener("keydown", (ev: KeyboardEvent) => {
|
||||
if (this.#keysBeingHeld.indexOf(ev.code) < 0) {
|
||||
this.#keysBeingHeld.push(ev.code)
|
||||
}
|
||||
this.#keyDownCallbacks.forEach(callback => callback(ev));
|
||||
});
|
||||
|
||||
document.addEventListener("keyup", (ev: KeyboardEvent) => {
|
||||
this.#keysBeingHeld = this.#keysBeingHeld.filter(held => held !== ev.code);
|
||||
this.#keyUpCallbacks.forEach(callback => callback(ev));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
add(name: string, shortcut: Shortcut) {
|
||||
super.add(name, shortcut);
|
||||
return this;
|
||||
}
|
||||
|
||||
getKeysBeingHeld() {
|
||||
return this.#keysBeingHeld;
|
||||
}
|
||||
|
||||
keyComboMatches(combo: string[]) {
|
||||
const heldKeys = this.getKeysBeingHeld();
|
||||
if (combo.length !== heldKeys.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return combo.every(key => heldKeys.indexOf(key) > -1);
|
||||
}
|
||||
|
||||
onKeyDown(callback: CallableFunction) {
|
||||
this.#keyDownCallbacks.push(callback);
|
||||
}
|
||||
|
||||
onKeyUp(callback: CallableFunction) {
|
||||
this.#keyUpCallbacks.push(callback);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { getMissionHandler } from "../..";
|
||||
import { getApp } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
@ -12,7 +12,7 @@ export class AircraftDatabase extends UnitDatabase {
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns)
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return 0;
|
||||
|
||||
const blueprint = this.getByName(name);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getMissionHandler } from "../..";
|
||||
import { getApp } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
@ -8,7 +8,7 @@ export class GroundUnitDatabase extends UnitDatabase {
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns)
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return 0;
|
||||
|
||||
const blueprint = this.getByName(name);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getMissionHandler } from "../..";
|
||||
import { getApp } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
@ -8,7 +8,7 @@ export class HelicopterDatabase extends UnitDatabase {
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns)
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return 0;
|
||||
|
||||
const blueprint = this.getByName(name);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getMissionHandler } from "../..";
|
||||
import { getApp } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
@ -8,7 +8,7 @@ export class NavyUnitDatabase extends UnitDatabase {
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns)
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return 0;
|
||||
|
||||
const blueprint = this.getByName(name);
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getMissionHandler, getUnitsManager } from "../..";
|
||||
import { getApp } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitBlueprint } from "../../@types/unitdatabase";
|
||||
|
||||
export class UnitDatabase {
|
||||
blueprints: { [key: string]: UnitBlueprint } = {};
|
||||
@ -44,15 +43,15 @@ export class UnitDatabase {
|
||||
}
|
||||
|
||||
getBlueprints() {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || !getMissionHandler().getCommandModeOptions().restrictSpawns)
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return this.blueprints;
|
||||
else {
|
||||
var filteredBlueprints: { [key: string]: UnitBlueprint } = {};
|
||||
for (let unit in this.blueprints) {
|
||||
const blueprint = this.blueprints[unit];
|
||||
if (this.getSpawnPointsByName(blueprint.name) <= getMissionHandler().getAvailableSpawnPoints() &&
|
||||
getMissionHandler().getCommandModeOptions().eras.includes(blueprint.era) &&
|
||||
(!getMissionHandler().getCommandModeOptions().restrictToCoalition || blueprint.coalition === getMissionHandler().getCommandedCoalition() || blueprint.coalition === undefined)) {
|
||||
if (this.getSpawnPointsByName(blueprint.name) <= getApp().getMissionManager().getAvailableSpawnPoints() &&
|
||||
getApp().getMissionManager().getCommandModeOptions().eras.includes(blueprint.era) &&
|
||||
(!getApp().getMissionManager().getCommandModeOptions().restrictToCoalition || blueprint.coalition === getApp().getMissionManager().getCommandedCoalition() || blueprint.coalition === undefined)) {
|
||||
filteredBlueprints[unit] = blueprint;
|
||||
}
|
||||
}
|
||||
@ -201,7 +200,7 @@ export class UnitDatabase {
|
||||
var row = Math.floor(idx / gridSize);
|
||||
var col = idx - row * gridSize;
|
||||
var location = new LatLng(initialPosition.lat + col * step, initialPosition.lng + row * step)
|
||||
getUnitsManager().spawnUnits(this.getCategory(), [{unitType: unitBlueprint.name, location: location, altitude: 1000, loadout: "", liveryID: ""}]);
|
||||
getApp().getUnitsManager().spawnUnits(this.getCategory(), [{unitType: unitBlueprint.name, location: location, altitude: 1000, loadout: "", liveryID: ""}]);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1,18 +1,15 @@
|
||||
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point } from 'leaflet';
|
||||
import { getMap, getMissionHandler, getUnitsManager, getWeaponsManager } from '..';
|
||||
import { getApp } from '..';
|
||||
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM } from '../other/utils';
|
||||
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea, simulateFireFight } from '../server/server';
|
||||
import { CustomMarker } from '../map/markers/custommarker';
|
||||
import { SVGInjector } from '@tanem/svg-injector';
|
||||
import { UnitDatabase } from './databases/unitdatabase';
|
||||
import { TargetMarker } from '../map/markers/targetmarker';
|
||||
import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_CONTACT_LINES, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
|
||||
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, ObjectIconOptions, UnitData } from '../@types/unit';
|
||||
import { DataExtractor } from '../server/dataextractor';
|
||||
import { groundUnitDatabase } from './databases/groundunitdatabase';
|
||||
import { navyUnitDatabase } from './databases/navyunitdatabase';
|
||||
import { Weapon } from '../weapon/weapon';
|
||||
import { LoadoutBlueprint } from '../@types/unitdatabase';
|
||||
|
||||
var pathIcon = new Icon({
|
||||
iconUrl: '/resources/theme/images/markers/marker-icon.png',
|
||||
@ -147,7 +144,7 @@ export class Unit extends CustomMarker {
|
||||
this.#selectable = true;
|
||||
|
||||
this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 });
|
||||
this.#pathPolyline.addTo(getMap());
|
||||
this.#pathPolyline.addTo(getApp().getMap());
|
||||
this.#contactsPolylines = [];
|
||||
this.#targetPositionMarker = new TargetMarker(new LatLng(0, 0));
|
||||
this.#targetPositionPolyline = new Polyline([], { color: '#FF0000', weight: 3, opacity: 0.5, smoothFactor: 1 });
|
||||
@ -155,9 +152,17 @@ export class Unit extends CustomMarker {
|
||||
this.on('click', (e) => this.#onClick(e));
|
||||
this.on('dblclick', (e) => this.#onDoubleClick(e));
|
||||
this.on('contextmenu', (e) => this.#onContextMenu(e));
|
||||
this.on('mouseover', () => { if (this.belongsToCommandedCoalition()) this.setHighlighted(true); })
|
||||
this.on('mouseout', () => { this.setHighlighted(false); })
|
||||
getMap().on("zoomend", () => { this.#onZoom(); })
|
||||
this.on('mouseover', () => {
|
||||
if (this.belongsToCommandedCoalition()) {
|
||||
this.setHighlighted(true);
|
||||
document.dispatchEvent(new CustomEvent("unitMouseover", { detail: this }));
|
||||
}
|
||||
});
|
||||
this.on('mouseout', () => {
|
||||
this.setHighlighted(false);
|
||||
document.dispatchEvent(new CustomEvent("unitMouseout", { detail: this }));
|
||||
});
|
||||
getApp().getMap().on("zoomend", () => { this.#onZoom(); })
|
||||
|
||||
/* Deselect units if they are hidden */
|
||||
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
|
||||
@ -182,7 +187,7 @@ export class Unit extends CustomMarker {
|
||||
|
||||
/********************** Unit data *************************/
|
||||
setData(dataExtractor: DataExtractor) {
|
||||
var updateMarker = !getMap().hasLayer(this);
|
||||
var updateMarker = !getApp().getMap().hasLayer(this);
|
||||
|
||||
var datumIndex = 0;
|
||||
while (datumIndex != DataIndexes.endOfData) {
|
||||
@ -235,7 +240,7 @@ export class Unit extends CustomMarker {
|
||||
if (updateMarker)
|
||||
this.#updateMarker();
|
||||
|
||||
if (this.getSelected() || getMap().getCenterUnit() === this)
|
||||
if (this.getSelected() || getApp().getMap().getCenterUnit() === this)
|
||||
document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
|
||||
}
|
||||
|
||||
@ -325,24 +330,29 @@ export class Unit extends CustomMarker {
|
||||
this.#selected = selected;
|
||||
|
||||
if (selected) {
|
||||
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
|
||||
this.#updateMarker();
|
||||
}
|
||||
else {
|
||||
document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this }));
|
||||
this.#clearContacts();
|
||||
this.#clearPath();
|
||||
this.#clearTarget();
|
||||
}
|
||||
|
||||
this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected);
|
||||
if (this.getCategory() === "GroundUnit" && getMap().getZoom() < 13) {
|
||||
if (this.getCategory() === "GroundUnit" && getApp().getMap().getZoom() < 13) {
|
||||
if (this.#isLeader)
|
||||
this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected));
|
||||
else
|
||||
this.#updateMarker();
|
||||
}
|
||||
|
||||
// Trigger events after all (de-)selecting has been done
|
||||
if (selected) {
|
||||
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
|
||||
} else {
|
||||
document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this }));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,11 +390,11 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
getGroupMembers() {
|
||||
return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => { return unit != this && unit.#groupName === this.#groupName; });
|
||||
return Object.values(getApp().getUnitsManager().getUnits()).filter((unit: Unit) => { return unit != this && unit.#groupName === this.#groupName; });
|
||||
}
|
||||
|
||||
belongsToCommandedCoalition() {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER && getMissionHandler().getCommandedCoalition() !== this.#coalition)
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER && getApp().getMissionManager().getCommandedCoalition() !== this.#coalition)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@ -510,13 +520,13 @@ export class Unit extends CustomMarker {
|
||||
|
||||
/********************** Visibility *************************/
|
||||
updateVisibility() {
|
||||
const hiddenUnits = getMap().getHiddenTypes();
|
||||
const hiddenUnits = getApp().getMap().getHiddenTypes();
|
||||
var hidden = ((this.#human && hiddenUnits.includes("human")) ||
|
||||
(this.#controlled == false && hiddenUnits.includes("dcs")) ||
|
||||
(hiddenUnits.includes(this.getMarkerCategory())) ||
|
||||
(hiddenUnits.includes(this.#coalition)) ||
|
||||
(!this.belongsToCommandedCoalition() && (this.#detectionMethods.length == 0 || (this.#detectionMethods.length == 1 && this.#detectionMethods[0] === RWR))) ||
|
||||
(getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getMap().getZoom() < 13 && (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) &&
|
||||
(getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getApp().getMap().getZoom() < 13 && (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) &&
|
||||
!(this.getSelected());
|
||||
|
||||
this.setHidden(hidden || !this.#alive);
|
||||
@ -526,16 +536,16 @@ export class Unit extends CustomMarker {
|
||||
this.#hidden = hidden;
|
||||
|
||||
/* Add the marker if not present */
|
||||
if (!getMap().hasLayer(this) && !this.getHidden()) {
|
||||
if (getMap().isZooming())
|
||||
this.once("zoomend", () => { this.addTo(getMap()) })
|
||||
if (!getApp().getMap().hasLayer(this) && !this.getHidden()) {
|
||||
if (getApp().getMap().isZooming())
|
||||
this.once("zoomend", () => { this.addTo(getApp().getMap()) })
|
||||
else
|
||||
this.addTo(getMap());
|
||||
this.addTo(getApp().getMap());
|
||||
}
|
||||
|
||||
/* Hide the marker if necessary*/
|
||||
if (getMap().hasLayer(this) && this.getHidden()) {
|
||||
getMap().removeLayer(this);
|
||||
if (getApp().getMap().hasLayer(this) && this.getHidden()) {
|
||||
getApp().getMap().removeLayer(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -560,7 +570,7 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
getLeader() {
|
||||
return getUnitsManager().getUnitByID(this.#leaderID);
|
||||
return getApp().getUnitsManager().getUnitByID(this.#leaderID);
|
||||
}
|
||||
|
||||
canFulfillRole(roles: string | string[]) {
|
||||
@ -578,7 +588,7 @@ export class Unit extends CustomMarker {
|
||||
|
||||
isInViewport() {
|
||||
|
||||
const mapBounds = getMap().getBounds();
|
||||
const mapBounds = getApp().getMap().getBounds();
|
||||
const unitPos = this.getPosition();
|
||||
|
||||
return (unitPos.lng > mapBounds.getWest()
|
||||
@ -599,7 +609,7 @@ export class Unit extends CustomMarker {
|
||||
else {
|
||||
path = [latlng];
|
||||
}
|
||||
addDestination(this.ID, path);
|
||||
getApp().getServerManager().addDestination(this.ID, path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -612,109 +622,104 @@ export class Unit extends CustomMarker {
|
||||
/* Units can't attack themselves */
|
||||
if (!this.#human)
|
||||
if (this.ID != targetID)
|
||||
attackUnit(this.ID, targetID);
|
||||
getApp().getServerManager().attackUnit(this.ID, targetID);
|
||||
}
|
||||
|
||||
followUnit(targetID: number, offset: { "x": number, "y": number, "z": number }) {
|
||||
/* Units can't follow themselves */
|
||||
if (!this.#human)
|
||||
if (this.ID != targetID)
|
||||
followUnit(this.ID, targetID, offset);
|
||||
getApp().getServerManager().followUnit(this.ID, targetID, offset);
|
||||
}
|
||||
|
||||
landAt(latlng: LatLng) {
|
||||
if (!this.#human)
|
||||
landAt(this.ID, latlng);
|
||||
getApp().getServerManager().landAt(this.ID, latlng);
|
||||
}
|
||||
|
||||
changeSpeed(speedChange: string) {
|
||||
if (!this.#human)
|
||||
changeSpeed(this.ID, speedChange);
|
||||
getApp().getServerManager().changeSpeed(this.ID, speedChange);
|
||||
}
|
||||
|
||||
changeAltitude(altitudeChange: string) {
|
||||
if (!this.#human)
|
||||
changeAltitude(this.ID, altitudeChange);
|
||||
getApp().getServerManager().changeAltitude(this.ID, altitudeChange);
|
||||
}
|
||||
|
||||
setSpeed(speed: number) {
|
||||
if (!this.#human)
|
||||
setSpeed(this.ID, speed);
|
||||
getApp().getServerManager().setSpeed(this.ID, speed);
|
||||
}
|
||||
|
||||
setSpeedType(speedType: string) {
|
||||
if (!this.#human)
|
||||
setSpeedType(this.ID, speedType);
|
||||
getApp().getServerManager().setSpeedType(this.ID, speedType);
|
||||
}
|
||||
|
||||
setAltitude(altitude: number) {
|
||||
if (!this.#human)
|
||||
setAltitude(this.ID, altitude);
|
||||
getApp().getServerManager().setAltitude(this.ID, altitude);
|
||||
}
|
||||
|
||||
setAltitudeType(altitudeType: string) {
|
||||
if (!this.#human)
|
||||
setAltitudeType(this.ID, altitudeType);
|
||||
getApp().getServerManager().setAltitudeType(this.ID, altitudeType);
|
||||
}
|
||||
|
||||
setROE(ROE: string) {
|
||||
if (!this.#human)
|
||||
setROE(this.ID, ROE);
|
||||
getApp().getServerManager().setROE(this.ID, ROE);
|
||||
}
|
||||
|
||||
setReactionToThreat(reactionToThreat: string) {
|
||||
if (!this.#human)
|
||||
setReactionToThreat(this.ID, reactionToThreat);
|
||||
getApp().getServerManager().setReactionToThreat(this.ID, reactionToThreat);
|
||||
}
|
||||
|
||||
setEmissionsCountermeasures(emissionCountermeasure: string) {
|
||||
if (!this.#human)
|
||||
setEmissionsCountermeasures(this.ID, emissionCountermeasure);
|
||||
}
|
||||
|
||||
setLeader(isLeader: boolean, wingmenIDs: number[] = []) {
|
||||
if (!this.#human)
|
||||
setLeader(this.ID, isLeader, wingmenIDs);
|
||||
getApp().getServerManager().setEmissionsCountermeasures(this.ID, emissionCountermeasure);
|
||||
}
|
||||
|
||||
setOnOff(onOff: boolean) {
|
||||
if (!this.#human)
|
||||
setOnOff(this.ID, onOff);
|
||||
getApp().getServerManager().setOnOff(this.ID, onOff);
|
||||
}
|
||||
|
||||
setFollowRoads(followRoads: boolean) {
|
||||
if (!this.#human)
|
||||
setFollowRoads(this.ID, followRoads);
|
||||
getApp().getServerManager().setFollowRoads(this.ID, followRoads);
|
||||
}
|
||||
|
||||
delete(explosion: boolean, immediate: boolean) {
|
||||
deleteUnit(this.ID, explosion, immediate);
|
||||
getApp().getServerManager().deleteUnit(this.ID, explosion, immediate);
|
||||
}
|
||||
|
||||
refuel() {
|
||||
if (!this.#human)
|
||||
refuel(this.ID);
|
||||
getApp().getServerManager().refuel(this.ID);
|
||||
}
|
||||
|
||||
setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) {
|
||||
if (!this.#human)
|
||||
setAdvacedOptions(this.ID, isTanker, isAWACS, TACAN, radio, generalSettings);
|
||||
getApp().getServerManager().setAdvacedOptions(this.ID, isTanker, isAWACS, TACAN, radio, generalSettings);
|
||||
}
|
||||
|
||||
bombPoint(latlng: LatLng) {
|
||||
bombPoint(this.ID, latlng);
|
||||
getApp().getServerManager().bombPoint(this.ID, latlng);
|
||||
}
|
||||
|
||||
carpetBomb(latlng: LatLng) {
|
||||
carpetBomb(this.ID, latlng);
|
||||
getApp().getServerManager().carpetBomb(this.ID, latlng);
|
||||
}
|
||||
|
||||
bombBuilding(latlng: LatLng) {
|
||||
bombBuilding(this.ID, latlng);
|
||||
getApp().getServerManager().bombBuilding(this.ID, latlng);
|
||||
}
|
||||
|
||||
fireAtArea(latlng: LatLng) {
|
||||
fireAtArea(this.ID, latlng);
|
||||
getApp().getServerManager().fireAtArea(this.ID, latlng);
|
||||
}
|
||||
|
||||
simulateFireFight(latlng: LatLng) {
|
||||
@ -730,9 +735,9 @@ export class Unit extends CustomMarker {
|
||||
/***********************************************/
|
||||
#onClick(e: any) {
|
||||
if (!this.#preventClick) {
|
||||
if (getMap().getState() === IDLE || getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) {
|
||||
if (getApp().getMap().getState() === IDLE || getApp().getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) {
|
||||
if (!e.originalEvent.ctrlKey)
|
||||
getUnitsManager().deselectAllUnits();
|
||||
getApp().getUnitsManager().deselectAllUnits();
|
||||
|
||||
this.setSelected(!this.getSelected());
|
||||
const detail = { "detail": { "unit": this } };
|
||||
@ -747,7 +752,7 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
#onDoubleClick(e: any) {
|
||||
const unitsManager = getUnitsManager();
|
||||
const unitsManager = getApp().getUnitsManager();
|
||||
Object.values(unitsManager.getUnits()).forEach((unit: Unit) => {
|
||||
if (unit.getAlive() === true && unit.getName() === this.getName() && unit.isInViewport())
|
||||
unitsManager.selectUnit(unit.ID, false);
|
||||
@ -759,14 +764,14 @@ export class Unit extends CustomMarker {
|
||||
|
||||
#onContextMenu(e: any) {
|
||||
var options: { [key: string]: { text: string, tooltip: string } } = {};
|
||||
const selectedUnits = getUnitsManager().getSelectedUnits();
|
||||
const selectedUnitTypes = getUnitsManager().getSelectedUnitsCategories();
|
||||
const selectedUnits = getApp().getUnitsManager().getSelectedUnits();
|
||||
const selectedUnitTypes = getApp().getUnitsManager().getSelectedUnitsCategories();
|
||||
|
||||
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it" };
|
||||
|
||||
if (selectedUnits.length > 0 && !(selectedUnits.length == 1 && (selectedUnits.includes(this)))) {
|
||||
options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons" };
|
||||
if (getUnitsManager().getSelectedUnitsCategories().length == 1 && getUnitsManager().getSelectedUnitsCategories()[0] === "Aircraft")
|
||||
if (getApp().getUnitsManager().getSelectedUnitsCategories().length == 1 && getApp().getUnitsManager().getSelectedUnitsCategories()[0] === "Aircraft")
|
||||
options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position" };;
|
||||
}
|
||||
else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) {
|
||||
@ -775,13 +780,13 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedUnitTypes.length === 1 && ["NavyUnit", "GroundUnit"].includes(selectedUnitTypes[0]) && getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) !== undefined)
|
||||
if (selectedUnitTypes.length === 1 && ["NavyUnit", "GroundUnit"].includes(selectedUnitTypes[0]) && getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) !== undefined)
|
||||
options["group"] = { text: "Create group", tooltip: "Create a group from the selected units." };
|
||||
|
||||
if (Object.keys(options).length > 0) {
|
||||
getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
|
||||
getMap().getUnitContextMenu().setOptions(options, (option: string) => {
|
||||
getMap().hideUnitContextMenu();
|
||||
getApp().getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
|
||||
getApp().getMap().getUnitContextMenu().setOptions(options, (option: string) => {
|
||||
getApp().getMap().hideUnitContextMenu();
|
||||
this.#executeAction(e, option);
|
||||
});
|
||||
}
|
||||
@ -789,13 +794,13 @@ export class Unit extends CustomMarker {
|
||||
|
||||
#executeAction(e: any, action: string) {
|
||||
if (action === "center-map")
|
||||
getMap().centerOnUnit(this.ID);
|
||||
getApp().getMap().centerOnUnit(this.ID);
|
||||
if (action === "attack")
|
||||
getUnitsManager().selectedUnitsAttackUnit(this.ID);
|
||||
getApp().getUnitsManager().selectedUnitsAttackUnit(this.ID);
|
||||
else if (action === "refuel")
|
||||
getUnitsManager().selectedUnitsRefuel();
|
||||
getApp().getUnitsManager().selectedUnitsRefuel();
|
||||
else if (action === "group")
|
||||
getUnitsManager().selectedUnitsCreateGroup();
|
||||
getApp().getUnitsManager().selectedUnitsCreateGroup();
|
||||
else if (action === "follow")
|
||||
this.#showFollowOptions(e);
|
||||
}
|
||||
@ -814,12 +819,12 @@ export class Unit extends CustomMarker {
|
||||
'custom': { text: "Custom", tooltip: "Set a custom formation position" },
|
||||
}
|
||||
|
||||
getMap().getUnitContextMenu().setOptions(options, (option: string) => {
|
||||
getMap().hideUnitContextMenu();
|
||||
getApp().getMap().getUnitContextMenu().setOptions(options, (option: string) => {
|
||||
getApp().getMap().hideUnitContextMenu();
|
||||
this.#applyFollowOptions(option);
|
||||
});
|
||||
|
||||
getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
|
||||
getApp().getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
|
||||
}
|
||||
|
||||
#applyFollowOptions(action: string) {
|
||||
@ -847,12 +852,12 @@ export class Unit extends CustomMarker {
|
||||
var y = upDown;
|
||||
var z = distance * Math.sin(angleRad);
|
||||
|
||||
getUnitsManager().selectedUnitsFollowUnit(this.ID, { "x": x, "y": y, "z": z });
|
||||
getApp().getUnitsManager().selectedUnitsFollowUnit(this.ID, { "x": x, "y": y, "z": z });
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
getUnitsManager().selectedUnitsFollowUnit(this.ID, undefined, action);
|
||||
getApp().getUnitsManager().selectedUnitsFollowUnit(this.ID, undefined, action);
|
||||
}
|
||||
}
|
||||
|
||||
@ -870,7 +875,7 @@ export class Unit extends CustomMarker {
|
||||
this.#miniMapMarker.setStyle({ color: "#ff5858" });
|
||||
else
|
||||
this.#miniMapMarker.setStyle({ color: "#247be2" });
|
||||
this.#miniMapMarker.addTo(getMap().getMiniMapLayerGroup());
|
||||
this.#miniMapMarker.addTo(getApp().getMap().getMiniMapLayerGroup());
|
||||
this.#miniMapMarker.bringToBack();
|
||||
}
|
||||
else {
|
||||
@ -881,8 +886,8 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.#miniMapMarker != null && getMap().getMiniMapLayerGroup().hasLayer(this.#miniMapMarker)) {
|
||||
getMap().getMiniMapLayerGroup().removeLayer(this.#miniMapMarker);
|
||||
if (this.#miniMapMarker != null && getApp().getMap().getMiniMapLayerGroup().hasLayer(this.#miniMapMarker)) {
|
||||
getApp().getMap().getMiniMapLayerGroup().removeLayer(this.#miniMapMarker);
|
||||
this.#miniMapMarker = null;
|
||||
}
|
||||
}
|
||||
@ -967,25 +972,25 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
/* Set vertical offset for altitude stacking */
|
||||
var pos = getMap().latLngToLayerPoint(this.getLatLng()).round();
|
||||
var pos = getApp().getMap().latLngToLayerPoint(this.getLatLng()).round();
|
||||
this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0));
|
||||
}
|
||||
}
|
||||
|
||||
#drawPath() {
|
||||
if (this.#activePath != undefined && getMap().getVisibilityOptions()[SHOW_UNIT_PATHS]) {
|
||||
if (this.#activePath != undefined && getApp().getMap().getVisibilityOptions()[SHOW_UNIT_PATHS]) {
|
||||
var points = [];
|
||||
points.push(new LatLng(this.#position.lat, this.#position.lng));
|
||||
|
||||
/* Add markers if missing */
|
||||
while (this.#pathMarkers.length < Object.keys(this.#activePath).length) {
|
||||
var marker = new Marker([0, 0], { icon: pathIcon }).addTo(getMap());
|
||||
var marker = new Marker([0, 0], { icon: pathIcon }).addTo(getApp().getMap());
|
||||
this.#pathMarkers.push(marker);
|
||||
}
|
||||
|
||||
/* Remove markers if too many */
|
||||
while (this.#pathMarkers.length > Object.keys(this.#activePath).length) {
|
||||
getMap().removeLayer(this.#pathMarkers[this.#pathMarkers.length - 1]);
|
||||
getApp().getMap().removeLayer(this.#pathMarkers[this.#pathMarkers.length - 1]);
|
||||
this.#pathMarkers.splice(this.#pathMarkers.length - 1, 1)
|
||||
}
|
||||
|
||||
@ -1007,7 +1012,7 @@ export class Unit extends CustomMarker {
|
||||
|
||||
#clearPath() {
|
||||
for (let WP in this.#pathMarkers) {
|
||||
getMap().removeLayer(this.#pathMarkers[WP]);
|
||||
getApp().getMap().removeLayer(this.#pathMarkers[WP]);
|
||||
}
|
||||
this.#pathMarkers = [];
|
||||
this.#pathPolyline.setLatLngs([]);
|
||||
@ -1015,25 +1020,25 @@ export class Unit extends CustomMarker {
|
||||
|
||||
#drawContacts() {
|
||||
this.#clearContacts();
|
||||
if (getMap().getVisibilityOptions()[SHOW_CONTACT_LINES]) {
|
||||
if (getApp().getMap().getVisibilityOptions()[SHOW_CONTACT_LINES]) {
|
||||
for (let index in this.#contacts) {
|
||||
var contactData = this.#contacts[index];
|
||||
var contact: Unit | Weapon | null;
|
||||
|
||||
if (contactData.ID in getUnitsManager().getUnits())
|
||||
contact = getUnitsManager().getUnitByID(contactData.ID);
|
||||
if (contactData.ID in getApp().getUnitsManager().getUnits())
|
||||
contact = getApp().getUnitsManager().getUnitByID(contactData.ID);
|
||||
else
|
||||
contact = getWeaponsManager().getWeaponByID(contactData.ID);
|
||||
contact = getApp().getWeaponsManager().getWeaponByID(contactData.ID);
|
||||
|
||||
if (contact != null && contact.getAlive()) {
|
||||
var startLatLng = new LatLng(this.#position.lat, this.#position.lng);
|
||||
var endLatLng: LatLng;
|
||||
if (contactData.detectionMethod === RWR) {
|
||||
var bearingToContact = bearing(this.#position.lat, this.#position.lng, contact.getPosition().lat, contact.getPosition().lng);
|
||||
var startXY = getMap().latLngToContainerPoint(startLatLng);
|
||||
var startXY = getApp().getMap().latLngToContainerPoint(startLatLng);
|
||||
var endX = startXY.x + 80 * Math.sin(deg2rad(bearingToContact));
|
||||
var endY = startXY.y - 80 * Math.cos(deg2rad(bearingToContact));
|
||||
endLatLng = getMap().containerPointToLatLng(new Point(endX, endY));
|
||||
endLatLng = getApp().getMap().containerPointToLatLng(new Point(endX, endY));
|
||||
}
|
||||
else
|
||||
endLatLng = new LatLng(contact.getPosition().lat, contact.getPosition().lng);
|
||||
@ -1048,7 +1053,7 @@ export class Unit extends CustomMarker {
|
||||
else
|
||||
color = "#FFFFFF";
|
||||
var contactPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 1, smoothFactor: 1, dashArray: "4, 8" });
|
||||
contactPolyline.addTo(getMap());
|
||||
contactPolyline.addTo(getApp().getMap());
|
||||
this.#contactsPolylines.push(contactPolyline)
|
||||
}
|
||||
}
|
||||
@ -1057,17 +1062,17 @@ export class Unit extends CustomMarker {
|
||||
|
||||
#clearContacts() {
|
||||
for (let index in this.#contactsPolylines) {
|
||||
getMap().removeLayer(this.#contactsPolylines[index])
|
||||
getApp().getMap().removeLayer(this.#contactsPolylines[index])
|
||||
}
|
||||
}
|
||||
|
||||
#drawTarget() {
|
||||
if (this.#targetPosition.lat != 0 && this.#targetPosition.lng != 0 && getMap().getVisibilityOptions()[SHOW_UNIT_PATHS]) {
|
||||
if (this.#targetPosition.lat != 0 && this.#targetPosition.lng != 0 && getApp().getMap().getVisibilityOptions()[SHOW_UNIT_PATHS]) {
|
||||
this.#drawTargetPosition(this.#targetPosition);
|
||||
}
|
||||
else if (this.#targetID != 0 && getMap().getVisibilityOptions()[SHOW_UNIT_TARGETS]) {
|
||||
const target = getUnitsManager().getUnitByID(this.#targetID);
|
||||
if (target && (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || (this.belongsToCommandedCoalition() && getUnitsManager().getUnitDetectedMethods(target).some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))))) {
|
||||
else if (this.#targetID != 0 && getApp().getMap().getVisibilityOptions()[SHOW_UNIT_TARGETS]) {
|
||||
const target = getApp().getUnitsManager().getUnitByID(this.#targetID);
|
||||
if (target && (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || (this.belongsToCommandedCoalition() && getApp().getUnitsManager().getUnitDetectedMethods(target).some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))))) {
|
||||
this.#drawTargetPosition(target.getPosition());
|
||||
}
|
||||
}
|
||||
@ -1076,20 +1081,20 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
#drawTargetPosition(targetPosition: LatLng) {
|
||||
if (!getMap().hasLayer(this.#targetPositionMarker))
|
||||
this.#targetPositionMarker.addTo(getMap());
|
||||
if (!getMap().hasLayer(this.#targetPositionPolyline))
|
||||
this.#targetPositionPolyline.addTo(getMap());
|
||||
if (!getApp().getMap().hasLayer(this.#targetPositionMarker))
|
||||
this.#targetPositionMarker.addTo(getApp().getMap());
|
||||
if (!getApp().getMap().hasLayer(this.#targetPositionPolyline))
|
||||
this.#targetPositionPolyline.addTo(getApp().getMap());
|
||||
this.#targetPositionMarker.setLatLng(new LatLng(targetPosition.lat, targetPosition.lng));
|
||||
this.#targetPositionPolyline.setLatLngs([new LatLng(this.#position.lat, this.#position.lng), new LatLng(targetPosition.lat, targetPosition.lng)])
|
||||
}
|
||||
|
||||
#clearTarget() {
|
||||
if (getMap().hasLayer(this.#targetPositionMarker))
|
||||
this.#targetPositionMarker.removeFrom(getMap());
|
||||
if (getApp().getMap().hasLayer(this.#targetPositionMarker))
|
||||
this.#targetPositionMarker.removeFrom(getApp().getMap());
|
||||
|
||||
if (getMap().hasLayer(this.#targetPositionPolyline))
|
||||
this.#targetPositionPolyline.removeFrom(getMap());
|
||||
if (getApp().getMap().hasLayer(this.#targetPositionPolyline))
|
||||
this.#targetPositionPolyline.removeFrom(getApp().getMap());
|
||||
}
|
||||
|
||||
#onZoom() {
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitsManager, getWeaponsManager } from "..";
|
||||
import { getApp } from "..";
|
||||
import { Unit } from "./unit";
|
||||
import { cloneUnits, deleteUnit, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server";
|
||||
import { bearingAndDistanceToLatLng, deg2rad, getUnitDatabaseByCategory, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils";
|
||||
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
|
||||
import { groundUnitDatabase } from "./databases/groundunitdatabase";
|
||||
import { DataIndexes, GAME_MASTER, IADSDensities, IDLE, MOVE_UNIT } from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { Contact, UnitData, UnitSpawnTable } from "../@types/unit";
|
||||
import { citiesDatabase } from "./citiesDatabase";
|
||||
import { aircraftDatabase } from "./databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "./databases/helicopterdatabase";
|
||||
import { navyUnitDatabase } from "./databases/navyunitdatabase";
|
||||
import { TemporaryUnitMarker } from "../map/markers/temporaryunitmarker";
|
||||
import { Popup } from "../popups/popup";
|
||||
import { HotgroupPanel } from "../panels/hotgrouppanel";
|
||||
|
||||
/** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only
|
||||
* result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows
|
||||
@ -124,12 +124,12 @@ export class UnitsManager {
|
||||
/* If we are not in Game Master mode, visibility of units by the user is determined by the detections of the units themselves. This is performed here.
|
||||
This operation is computationally expensive, therefore it is only performed when #requestDetectionUpdate is true. This happens whenever a change in the detectionUpdates is detected
|
||||
*/
|
||||
if (this.#requestDetectionUpdate && getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) {
|
||||
if (this.#requestDetectionUpdate && getApp().getMissionManager().getCommandModeOptions().commandMode != GAME_MASTER) {
|
||||
/* Create a dictionary of empty detection methods arrays */
|
||||
var detectionMethods: { [key: string]: number[] } = {};
|
||||
for (let ID in this.#units)
|
||||
detectionMethods[ID] = [];
|
||||
for (let ID in getWeaponsManager().getWeapons())
|
||||
for (let ID in getApp().getWeaponsManager().getWeapons())
|
||||
detectionMethods[ID] = [];
|
||||
|
||||
/* Fill the array with the detection methods */
|
||||
@ -152,8 +152,8 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
/* Set the detection methods for every weapon (weapons must be detected too) */
|
||||
for (let ID in getWeaponsManager().getWeapons()) {
|
||||
const weapon = getWeaponsManager().getWeaponByID(parseInt(ID));
|
||||
for (let ID in getApp().getWeaponsManager().getWeapons()) {
|
||||
const weapon = getApp().getWeaponsManager().getWeaponByID(parseInt(ID));
|
||||
weapon?.setDetectionMethods(detectionMethods[ID]);
|
||||
}
|
||||
|
||||
@ -646,9 +646,9 @@ export class UnitsManager {
|
||||
var unit = selectedUnits[idx];
|
||||
units.push({ ID: unit.ID, location: unit.getPosition() });
|
||||
}
|
||||
cloneUnits(units, true, 0 /* No spawn points, we delete the original units */);
|
||||
getApp().getServerManager().cloneUnits(units, true, 0 /* No spawn points, we delete the original units */);
|
||||
} else {
|
||||
getInfoPopup().setText(`Groups can only be created from units of the same category`);
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Groups can only be created from units of the same category`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -671,7 +671,7 @@ export class UnitsManager {
|
||||
selectedUnits[idx].setHotgroup(hotgroup);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `added to hotgroup ${hotgroup}`);
|
||||
getHotgroupPanel().refreshHotgroups();
|
||||
(getApp().getPanelsManager().get("hotgroup") as HotgroupPanel).refreshHotgroups();
|
||||
}
|
||||
|
||||
/** Delete the selected units
|
||||
@ -742,7 +742,7 @@ export class UnitsManager {
|
||||
selectedUnitsCopy() {
|
||||
/* A JSON is used to deepcopy the units, creating a "snapshot" of their properties at the time of the copy */
|
||||
this.#copiedUnits = JSON.parse(JSON.stringify(this.getSelectedUnits().map((unit: Unit) => { return unit.getData() }))); /* Can be applied to humans too */
|
||||
getInfoPopup().setText(`${this.#copiedUnits.length} units copied`);
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${this.#copiedUnits.length} units copied`);
|
||||
}
|
||||
|
||||
/*********************** Unit manipulation functions ************************/
|
||||
@ -754,9 +754,9 @@ export class UnitsManager {
|
||||
let spawnPoints = 0;
|
||||
|
||||
/* If spawns are restricted, check that the user has the necessary spawn points */
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) {
|
||||
if (getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0) {
|
||||
getInfoPopup().setText(`Units can be pasted only during SETUP phase`);
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode != GAME_MASTER) {
|
||||
if (getApp().getMissionManager().getCommandModeOptions().restrictSpawns && getApp().getMissionManager().getRemainingSetupTime() < 0) {
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Units can be pasted only during SETUP phase`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -766,8 +766,8 @@ export class UnitsManager {
|
||||
spawnPoints += unitSpawnPoints;
|
||||
})
|
||||
|
||||
if (spawnPoints > getMissionHandler().getAvailableSpawnPoints()) {
|
||||
getInfoPopup().setText("Not enough spawn points available!");
|
||||
if (spawnPoints > getApp().getMissionManager().getAvailableSpawnPoints()) {
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Not enough spawn points available!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -796,12 +796,12 @@ export class UnitsManager {
|
||||
var units: { ID: number, location: LatLng }[] = [];
|
||||
let markers: TemporaryUnitMarker[] = [];
|
||||
groups[groupName].forEach((unit: UnitData) => {
|
||||
var position = new LatLng(getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getMap().getMouseCoordinates().lng + unit.position.lng - avgLng);
|
||||
markers.push(getMap().addTemporaryMarker(position, unit.name, unit.coalition));
|
||||
var position = new LatLng(getApp().getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getApp().getMap().getMouseCoordinates().lng + unit.position.lng - avgLng);
|
||||
markers.push(getApp().getMap().addTemporaryMarker(position, unit.name, unit.coalition));
|
||||
units.push({ ID: unit.ID, location: position });
|
||||
});
|
||||
|
||||
cloneUnits(units, false, spawnPoints, (res: any) => {
|
||||
getApp().getServerManager().cloneUnits(units, false, spawnPoints, (res: any) => {
|
||||
if (res.commandHash !== undefined) {
|
||||
markers.forEach((marker: TemporaryUnitMarker) => {
|
||||
marker.setCommandHash(res.commandHash);
|
||||
@ -809,10 +809,10 @@ export class UnitsManager {
|
||||
}
|
||||
});
|
||||
}
|
||||
getInfoPopup().setText(`${this.#copiedUnits.length} units pasted`);
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${this.#copiedUnits.length} units pasted`);
|
||||
}
|
||||
else {
|
||||
getInfoPopup().setText("No units copied!");
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("No units copied!");
|
||||
}
|
||||
}
|
||||
|
||||
@ -900,7 +900,7 @@ export class UnitsManager {
|
||||
var units = aliveUnits.map((unit: UnitData) => {
|
||||
return { unitType: unit.name, location: unit.position, liveryID: "" }
|
||||
});
|
||||
getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true);
|
||||
getApp().getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -923,46 +923,46 @@ export class UnitsManager {
|
||||
spawnUnits(category: string, units: UnitSpawnTable[], coalition: string = "blue", immediate: boolean = true, airbase: string = "", country: string = "", callback: CallableFunction = () => {}) {
|
||||
var spawnPoints = 0;
|
||||
var spawnFunction = () => {};
|
||||
var spawnsRestricted = getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER;
|
||||
var spawnsRestricted = getApp().getMissionManager().getCommandModeOptions().restrictSpawns && getApp().getMissionManager().getRemainingSetupTime() < 0 && getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER;
|
||||
|
||||
if (category === "Aircraft") {
|
||||
if (airbase == "" && spawnsRestricted) {
|
||||
getInfoPopup().setText("Aircrafts can be air spawned during the SETUP phase only");
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Aircrafts can be air spawned during the SETUP phase only");
|
||||
return false;
|
||||
}
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {return points + aircraftDatabase.getSpawnPointsByName(unit.unitType)}, 0);
|
||||
spawnFunction = () => spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints, callback);
|
||||
spawnFunction = () => getApp().getServerManager().spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints, callback);
|
||||
} else if (category === "Helicopter") {
|
||||
if (airbase == "" && spawnsRestricted) {
|
||||
getInfoPopup().setText("Helicopters can be air spawned during the SETUP phase only");
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Helicopters can be air spawned during the SETUP phase only");
|
||||
return false;
|
||||
}
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {return points + helicopterDatabase.getSpawnPointsByName(unit.unitType)}, 0);
|
||||
spawnFunction = () => spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints, callback);
|
||||
spawnFunction = () => getApp().getServerManager().spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints, callback);
|
||||
|
||||
} else if (category === "GroundUnit") {
|
||||
if (spawnsRestricted) {
|
||||
getInfoPopup().setText("Ground units can be spawned during the SETUP phase only");
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Ground units can be spawned during the SETUP phase only");
|
||||
return false;
|
||||
}
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0);
|
||||
spawnFunction = () => spawnGroundUnits(units, coalition, country, immediate, spawnPoints, callback);
|
||||
spawnFunction = () => getApp().getServerManager().spawnGroundUnits(units, coalition, country, immediate, spawnPoints, callback);
|
||||
|
||||
} else if (category === "NavyUnit") {
|
||||
if (spawnsRestricted) {
|
||||
getInfoPopup().setText("Navy units can be spawned during the SETUP phase only");
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Navy units can be spawned during the SETUP phase only");
|
||||
return false;
|
||||
}
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {return points + navyUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0);
|
||||
spawnFunction = () => spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback);
|
||||
spawnFunction = () => getApp().getServerManager().spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback);
|
||||
}
|
||||
|
||||
if (spawnPoints <= getMissionHandler().getAvailableSpawnPoints()) {
|
||||
getMissionHandler().setSpentSpawnPoints(spawnPoints);
|
||||
if (spawnPoints <= getApp().getMissionManager().getAvailableSpawnPoints()) {
|
||||
getApp().getMissionManager().setSpentSpawnPoints(spawnPoints);
|
||||
spawnFunction();
|
||||
return true;
|
||||
} else {
|
||||
getInfoPopup().setText("Not enough spawn points available!");
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Not enough spawn points available!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -981,7 +981,7 @@ export class UnitsManager {
|
||||
if (this.getSelectedUnits().length > 0) {
|
||||
/* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */
|
||||
if (!this.#selectionEventDisabled) {
|
||||
getMap().setState(MOVE_UNIT);
|
||||
getApp().getMap().setState(MOVE_UNIT);
|
||||
window.setTimeout(() => {
|
||||
document.dispatchEvent(new CustomEvent("unitsSelection", { detail: this.getSelectedUnits() }));
|
||||
this.#selectionEventDisabled = false;
|
||||
@ -990,14 +990,14 @@ export class UnitsManager {
|
||||
}
|
||||
}
|
||||
else {
|
||||
getMap().setState(IDLE);
|
||||
getApp().getMap().setState(IDLE);
|
||||
document.dispatchEvent(new CustomEvent("clearSelection"));
|
||||
}
|
||||
}
|
||||
|
||||
#onUnitDeselection(unit: Unit) {
|
||||
if (this.getSelectedUnits().length == 0) {
|
||||
getMap().setState(IDLE);
|
||||
getApp().getMap().setState(IDLE);
|
||||
document.dispatchEvent(new CustomEvent("clearSelection"));
|
||||
}
|
||||
else
|
||||
@ -1006,8 +1006,8 @@ export class UnitsManager {
|
||||
|
||||
#showActionMessage(units: Unit[], message: string) {
|
||||
if (units.length == 1)
|
||||
getInfoPopup().setText(`${units[0].getUnitName()} ${message}`);
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${units[0].getUnitName()} ${message}`);
|
||||
else if (units.length > 1)
|
||||
getInfoPopup().setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`);
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`);
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,9 @@
|
||||
import { LatLng, DivIcon, Map } from 'leaflet';
|
||||
import { getMap, getMissionHandler, getUnitsManager } from '..';
|
||||
import { getApp } from '..';
|
||||
import { enumToCoalition, mToFt, msToKnots, rad2deg } from '../other/utils';
|
||||
import { CustomMarker } from '../map/markers/custommarker';
|
||||
import { SVGInjector } from '@tanem/svg-injector';
|
||||
import { DLINK, DataIndexes, GAME_MASTER, IRST, OPTIC, RADAR, VISUAL } from '../constants/constants';
|
||||
import { ObjectIconOptions } from '../@types/unit';
|
||||
import { DataExtractor } from '../server/dataextractor';
|
||||
|
||||
export class Weapon extends CustomMarker {
|
||||
@ -58,7 +57,7 @@ export class Weapon extends CustomMarker {
|
||||
|
||||
/********************** Unit data *************************/
|
||||
setData(dataExtractor: DataExtractor) {
|
||||
var updateMarker = !getMap().hasLayer(this);
|
||||
var updateMarker = !getApp().getMap().hasLayer(this);
|
||||
|
||||
var datumIndex = 0;
|
||||
while (datumIndex != DataIndexes.endOfData) {
|
||||
@ -116,7 +115,7 @@ export class Weapon extends CustomMarker {
|
||||
}
|
||||
|
||||
belongsToCommandedCoalition() {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER && getMissionHandler().getCommandedCoalition() !== this.#coalition)
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER && getApp().getMissionManager().getCommandedCoalition() !== this.#coalition)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@ -166,7 +165,7 @@ export class Weapon extends CustomMarker {
|
||||
|
||||
/********************** Visibility *************************/
|
||||
updateVisibility() {
|
||||
const hiddenUnits = getMap().getHiddenTypes();
|
||||
const hiddenUnits = getApp().getMap().getHiddenTypes();
|
||||
var hidden = (hiddenUnits.includes(this.getMarkerCategory())) ||
|
||||
(hiddenUnits.includes(this.#coalition)) ||
|
||||
(!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0);
|
||||
@ -178,16 +177,16 @@ export class Weapon extends CustomMarker {
|
||||
this.#hidden = hidden;
|
||||
|
||||
/* Add the marker if not present */
|
||||
if (!getMap().hasLayer(this) && !this.getHidden()) {
|
||||
if (getMap().isZooming())
|
||||
this.once("zoomend", () => {this.addTo(getMap())})
|
||||
if (!getApp().getMap().hasLayer(this) && !this.getHidden()) {
|
||||
if (getApp().getMap().isZooming())
|
||||
this.once("zoomend", () => {this.addTo(getApp().getMap())})
|
||||
else
|
||||
this.addTo(getMap());
|
||||
this.addTo(getApp().getMap());
|
||||
}
|
||||
|
||||
/* Hide the marker if necessary*/
|
||||
if (getMap().hasLayer(this) && this.getHidden()) {
|
||||
getMap().removeLayer(this);
|
||||
if (getApp().getMap().hasLayer(this) && this.getHidden()) {
|
||||
getApp().getMap().removeLayer(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,7 +249,7 @@ export class Weapon extends CustomMarker {
|
||||
}
|
||||
|
||||
/* Set vertical offset for altitude stacking */
|
||||
var pos = getMap().latLngToLayerPoint(this.getLatLng()).round();
|
||||
var pos = getApp().getMap().latLngToLayerPoint(this.getLatLng()).round();
|
||||
this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { getMissionHandler, getUnitsManager } from "..";
|
||||
import { getApp } from "..";
|
||||
import { Weapon } from "./weapon";
|
||||
import { DataIndexes, GAME_MASTER } from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { Contact } from "../@types/unit";
|
||||
|
||||
/** The WeaponsManager handles the creation and update of weapons. Data is strictly updated by the server ONLY. */
|
||||
export class WeaponsManager {
|
||||
@ -93,7 +92,7 @@ export class WeaponsManager {
|
||||
*/
|
||||
getWeaponDetectedMethods(weapon: Weapon) {
|
||||
var detectionMethods: number[] = [];
|
||||
var units = getUnitsManager().getUnits();
|
||||
var units = getApp().getUnitsManager().getUnits();
|
||||
for (let idx in units) {
|
||||
if (units[idx].getAlive() && units[idx].getIsLeader() && units[idx].getCoalition() !== "neutral" && units[idx].getCoalition() != weapon.getCoalition())
|
||||
{
|
||||
|
||||
@ -23,19 +23,22 @@
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||
"rootDirs": ["./src", "./@types"], /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
"./node_modules/@types",
|
||||
"./@types"
|
||||
], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"types": [
|
||||
"leaflet",
|
||||
"geojson",
|
||||
"node",
|
||||
"formatcoords"
|
||||
"formatcoords",
|
||||
"olympus",
|
||||
"dom"
|
||||
], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
@ -100,6 +103,7 @@
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
"src/**/*.ts",
|
||||
"@types/*.d.ts"
|
||||
]
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
<div class="ol-panel aic-panel" id="aic-control-panel" data-feature-switch="aic">
|
||||
<div class="olympus-button" id="toggle-aic-button"></div>
|
||||
<div class="olympus-button" id="aic-help-button"></div>
|
||||
</div>
|
||||
|
||||
<div id="aic-help" class="olympus-dialog hide" data-feature-switch="aic">
|
||||
<div class="olympus-dialog-close">×</div>
|
||||
<div class="olympus-dialog-header">AIC Help</div>
|
||||
<div class="olympus-dialog-content">
|
||||
<p>How to be a good AIC and get people to do stuff good, too.</p>
|
||||
<div
|
||||
style="align-items: center; background:black; color:white; display:flex; height:250px; justify-content: center; justify-self: center; width:450px;">
|
||||
<div>[DCS with Volvo video]</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="aic-teleprompt" data-feature-switch="aic"></div>
|
||||
|
||||
<div id="aic-callsign-panel" class="aic-panel" data-feature-switch="aic">
|
||||
|
||||
<div class="aic-panel">
|
||||
<h2>My callsign</h2>
|
||||
<div>Magic</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div id="aic-toolbox" class="aic-panel" data-feature-switch="aic">
|
||||
|
||||
<div id="aic-control-type" class="aic-toolbox-panel">
|
||||
<h2>Control</h2>
|
||||
<div>
|
||||
<input type="radio" name="control-type" id="control-type-broadcast" value="broadcast" checked="checked" />
|
||||
<label for="control-type-broadcast">Broadcast</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" name="control-type" id="control-type-tactical" value="tactical" />
|
||||
<label for="control-type-tactical">Tactical</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="aic-formation-panel" class="aic-toolbox-panel">
|
||||
|
||||
<h2>Formations</h2>
|
||||
|
||||
<div id="aic-formation-list"></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -1,5 +0,0 @@
|
||||
<form class="ol-strip-board-add-flight">
|
||||
<div class="ol-auto-suggest"></div>
|
||||
<input type="text" name="unitName" placeholder="Flight search" />
|
||||
<button class="add-flight-by-click" title="Add unit via click"><img src="/resources/theme/images/icons/crosshairs-solid.svg" /></button>
|
||||
</form>
|
||||
@ -1,11 +0,0 @@
|
||||
<%- include('board.ejs', {
|
||||
"boardId": "strip-board-tower",
|
||||
"boardType": "tower",
|
||||
"headers": [ "Flight", "a. Alt", "alt", "a. Speed", "Speed" ]
|
||||
}) %>
|
||||
|
||||
<%- include('board.ejs', {
|
||||
"boardId": "strip-board-ground",
|
||||
"boardType": "ground",
|
||||
"headers": [ "Flight", "Status", "T/O Time", "TTG" ]
|
||||
}) %>
|
||||
@ -1,22 +0,0 @@
|
||||
<div id="<%= boardId %>" class="ol-panel ol-dialog ol-strip-board ol-draggable hide" data-board-type="<%= boardType %>" data-feature-switch="atc">
|
||||
|
||||
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
|
||||
|
||||
<div class="ol-dialog-header">
|
||||
<h3><%= boardType %></h3>
|
||||
<%- include('addflight.ejs') %>
|
||||
<div class="ol-strip-board-clock"></div>
|
||||
</div>
|
||||
|
||||
<div class="ol-dialog-content">
|
||||
<div class="ol-strip-board-headers">
|
||||
<div><!-- handles --></div>
|
||||
<% headers.forEach( header => { %>
|
||||
<div><%= header %></div>
|
||||
<% }); %>
|
||||
<div><!-- delete --></div>
|
||||
</div>
|
||||
<div class="ol-strip-board-strips ol-sortable"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -1,13 +0,0 @@
|
||||
<div id="unit-data-table" class="ol-panel ol-dialog scrollable" oncontextmenu="return false;">
|
||||
|
||||
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
|
||||
|
||||
<div class="ol-dialog-header">
|
||||
<h4>Unit list</h4>
|
||||
</div>
|
||||
|
||||
<div id="unit-list" class="ol-dialog-content">
|
||||
<!-- Here the list of units is shown -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
Loading…
x
Reference in New Issue
Block a user