Merge and fix
152
client/@types/olympus/index.d.ts
vendored
@@ -254,6 +254,7 @@ declare module "constants/constants" {
|
||||
operateAs = 41,
|
||||
shotsScatter = 42,
|
||||
shotsIntensity = 43,
|
||||
health = 44,
|
||||
endOfData = 255
|
||||
}
|
||||
export const MGRS_PRECISION_10KM = 2;
|
||||
@@ -313,7 +314,7 @@ declare module "controls/dropdown" {
|
||||
#private;
|
||||
constructor(ID: string | null, callback: CallableFunction, options?: string[] | null, defaultText?: string);
|
||||
getContainer(): HTMLElement;
|
||||
setOptions(optionsList: string[], sort?: "" | "string" | "number"): void;
|
||||
setOptions(optionsList: string[], sort?: "" | "string" | "number" | "string+number"): void;
|
||||
setOptionsElements(optionsElements: HTMLElement[]): void;
|
||||
getOptionElements(): HTMLCollection;
|
||||
addOptionElement(optionElement: HTMLElement): void;
|
||||
@@ -449,6 +450,7 @@ declare module "interfaces" {
|
||||
export interface ObjectIconOptions {
|
||||
showState: boolean;
|
||||
showVvi: boolean;
|
||||
showHealth: boolean;
|
||||
showHotgroup: boolean;
|
||||
showUnitIcon: boolean;
|
||||
showShortLabel: boolean;
|
||||
@@ -537,6 +539,7 @@ declare module "interfaces" {
|
||||
operateAs: string;
|
||||
shotsScatter: number;
|
||||
shotsIntensity: number;
|
||||
health: number;
|
||||
}
|
||||
export interface LoadoutItemBlueprint {
|
||||
name: string;
|
||||
@@ -576,6 +579,7 @@ declare module "interfaces" {
|
||||
shotsBaseScatter?: number;
|
||||
description?: string;
|
||||
abilities?: string;
|
||||
tags?: string;
|
||||
acquisitionRange?: number;
|
||||
engagementRange?: number;
|
||||
targetingRange?: number;
|
||||
@@ -585,6 +589,8 @@ declare module "interfaces" {
|
||||
canRearm?: boolean;
|
||||
canAAA?: boolean;
|
||||
indirectFire?: boolean;
|
||||
markerFile?: string;
|
||||
unitWhenGrouped?: string;
|
||||
}
|
||||
export interface UnitSpawnOptions {
|
||||
roleType: string;
|
||||
@@ -768,7 +774,7 @@ declare module "other/utils" {
|
||||
ranges?: string[];
|
||||
eras?: string[];
|
||||
}): UnitBlueprint | null;
|
||||
export function getMarkerCategoryByName(name: string): "aircraft" | "helicopter" | "groundunit-sam" | "groundunit-other" | "groundunit-sam-radar" | "groundunit-sam-launcher" | "groundunit-ewr";
|
||||
export function getMarkerCategoryByName(name: string): "aircraft" | "helicopter" | "groundunit-other" | "navyunit" | "groundunit";
|
||||
export function getUnitDatabaseByCategory(category: string): import("unit/databases/aircraftdatabase").AircraftDatabase | import("unit/databases/helicopterdatabase").HelicopterDatabase | import("unit/databases/groundunitdatabase").GroundUnitDatabase | import("unit/databases/navyunitdatabase").NavyUnitDatabase | null;
|
||||
export function base64ToBytes(base64: string): ArrayBufferLike;
|
||||
export function enumToState(state: number): string;
|
||||
@@ -1021,6 +1027,7 @@ declare module "weapon/weapon" {
|
||||
getIconOptions(): {
|
||||
showState: boolean;
|
||||
showVvi: boolean;
|
||||
showHealth: boolean;
|
||||
showHotgroup: boolean;
|
||||
showUnitIcon: boolean;
|
||||
showShortLabel: boolean;
|
||||
@@ -1038,6 +1045,7 @@ declare module "weapon/weapon" {
|
||||
getIconOptions(): {
|
||||
showState: boolean;
|
||||
showVvi: boolean;
|
||||
showHealth: boolean;
|
||||
showHotgroup: boolean;
|
||||
showUnitIcon: boolean;
|
||||
showShortLabel: boolean;
|
||||
@@ -1049,12 +1057,29 @@ declare module "weapon/weapon" {
|
||||
};
|
||||
}
|
||||
}
|
||||
declare module "map/rangecircle" {
|
||||
import { Circle } from 'leaflet';
|
||||
/**
|
||||
* This custom Circle object implements a faster render method for very big circles. When zoomed in, the default ctx.arc method
|
||||
* is very slow since the circle is huge. Also, when zoomed in most of the circle points will be outside the screen and not needed. This
|
||||
* simpler, faster renderer approximates the circle with line segements and only draws those currently visibile.
|
||||
* A more refined version using arcs could be implemented but this works good enough.
|
||||
*/
|
||||
export class RangeCircle extends Circle {
|
||||
_updatePath(): void;
|
||||
}
|
||||
}
|
||||
declare module "unit/unit" {
|
||||
import { LatLng, Map } from 'leaflet';
|
||||
import { CustomMarker } from "map/markers/custommarker";
|
||||
import { UnitDatabase } from "unit/databases/unitdatabase";
|
||||
import { DataExtractor } from "server/dataextractor";
|
||||
import { Ammo, Contact, GeneralSettings, ObjectIconOptions, Offset, Radio, TACAN, UnitData } from "interfaces";
|
||||
/**
|
||||
* Unit class which controls unit behaviour
|
||||
*
|
||||
* Just about everything is a unit - even missiles!
|
||||
*/
|
||||
export class Unit extends CustomMarker {
|
||||
#private;
|
||||
ID: number;
|
||||
@@ -1074,8 +1099,8 @@ declare module "unit/unit" {
|
||||
getHorizontalVelocity(): number;
|
||||
getVerticalVelocity(): number;
|
||||
getHeading(): number;
|
||||
getIsActiveTanker(): boolean;
|
||||
getIsActiveAWACS(): boolean;
|
||||
getIsActiveTanker(): boolean;
|
||||
getOnOff(): boolean;
|
||||
getFollowRoads(): boolean;
|
||||
getFuel(): number;
|
||||
@@ -1100,26 +1125,88 @@ declare module "unit/unit" {
|
||||
getOperateAs(): string;
|
||||
getShotsScatter(): number;
|
||||
getShotsIntensity(): number;
|
||||
getHealth(): number;
|
||||
static getConstructor(type: string): typeof GroundUnit | undefined;
|
||||
constructor(ID: number);
|
||||
getCategory(): string;
|
||||
/********************** Unit data *************************/
|
||||
setData(dataExtractor: DataExtractor): void;
|
||||
drawLines(): void;
|
||||
/** Get unit data collated into an object
|
||||
*
|
||||
* @returns object populated by unit information which can also be retrieved using getters
|
||||
*/
|
||||
getData(): UnitData;
|
||||
/**
|
||||
*
|
||||
* @returns string containing the marker category
|
||||
*/
|
||||
getMarkerCategory(): string;
|
||||
/** Get a database of information also in this unit's category
|
||||
*
|
||||
* @returns UnitDatabase
|
||||
*/
|
||||
getDatabase(): UnitDatabase | null;
|
||||
/** Get the icon options
|
||||
* Used to configure how the marker appears on the map
|
||||
*
|
||||
* @returns ObjectIconOptions
|
||||
*/
|
||||
getIconOptions(): ObjectIconOptions;
|
||||
/** Set the unit as alive or dead
|
||||
*
|
||||
* @param newAlive (boolean) true = alive, false = dead
|
||||
*/
|
||||
setAlive(newAlive: boolean): void;
|
||||
/** Set the unit as user-selected
|
||||
*
|
||||
* @param selected (boolean)
|
||||
*/
|
||||
setSelected(selected: boolean): void;
|
||||
/** Is this unit selected?
|
||||
*
|
||||
* @returns boolean
|
||||
*/
|
||||
getSelected(): boolean;
|
||||
/** Set whether this unit is selectable
|
||||
*
|
||||
* @param selectable (boolean)
|
||||
*/
|
||||
setSelectable(selectable: boolean): void;
|
||||
/** Get whether this unit is selectable
|
||||
*
|
||||
* @returns boolean
|
||||
*/
|
||||
getSelectable(): boolean;
|
||||
/** Set the number of the hotgroup to which the unit belongs
|
||||
*
|
||||
* @param hotgroup (number)
|
||||
*/
|
||||
setHotgroup(hotgroup: number | null): void;
|
||||
/** Get the unit's hotgroup number
|
||||
*
|
||||
* @returns number
|
||||
*/
|
||||
getHotgroup(): number | null;
|
||||
/** Set the unit as highlighted
|
||||
*
|
||||
* @param highlighted (boolean)
|
||||
*/
|
||||
setHighlighted(highlighted: boolean): void;
|
||||
/** Get whether the unit is highlighted or not
|
||||
*
|
||||
* @returns boolean
|
||||
*/
|
||||
getHighlighted(): boolean;
|
||||
/** Get the other members of the group which this unit is in
|
||||
*
|
||||
* @returns Unit[]
|
||||
*/
|
||||
getGroupMembers(): Unit[];
|
||||
/** Returns whether the user is allowed to command this unit, based on coalition
|
||||
*
|
||||
* @returns boolean
|
||||
*/
|
||||
belongsToCommandedCoalition(): boolean;
|
||||
getType(): string;
|
||||
getSpawnPoints(): number | undefined;
|
||||
@@ -1163,7 +1250,7 @@ declare module "unit/unit" {
|
||||
setOnOff(onOff: boolean): void;
|
||||
setFollowRoads(followRoads: boolean): void;
|
||||
setOperateAs(operateAs: string): void;
|
||||
delete(explosion: boolean, immediate: boolean): void;
|
||||
delete(explosion: boolean, explosionType: string, immediate: boolean): void;
|
||||
refuel(): void;
|
||||
setAdvancedOptions(isActiveTanker: boolean, isActiveAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings): void;
|
||||
bombPoint(latlng: LatLng): void;
|
||||
@@ -1193,6 +1280,7 @@ declare module "unit/unit" {
|
||||
getIconOptions(): {
|
||||
showState: boolean;
|
||||
showVvi: boolean;
|
||||
showHealth: boolean;
|
||||
showHotgroup: boolean;
|
||||
showUnitIcon: boolean;
|
||||
showShortLabel: boolean;
|
||||
@@ -1223,6 +1311,7 @@ declare module "unit/unit" {
|
||||
getIconOptions(): {
|
||||
showState: boolean;
|
||||
showVvi: boolean;
|
||||
showHealth: boolean;
|
||||
showHotgroup: boolean;
|
||||
showUnitIcon: boolean;
|
||||
showShortLabel: boolean;
|
||||
@@ -1247,6 +1336,7 @@ declare module "unit/unit" {
|
||||
getIconOptions(): {
|
||||
showState: boolean;
|
||||
showVvi: boolean;
|
||||
showHealth: boolean;
|
||||
showHotgroup: boolean;
|
||||
showUnitIcon: boolean;
|
||||
showShortLabel: boolean;
|
||||
@@ -1618,6 +1708,30 @@ declare module "panels/serverstatuspanel" {
|
||||
update(frameRate: number, load: number): void;
|
||||
}
|
||||
}
|
||||
declare module "toolbars/toolbar" {
|
||||
export class Toolbar {
|
||||
#private;
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string);
|
||||
show(): void;
|
||||
hide(): void;
|
||||
toggle(): void;
|
||||
getElement(): HTMLElement;
|
||||
getVisible(): boolean;
|
||||
}
|
||||
}
|
||||
declare module "toolbars/primarytoolbar" {
|
||||
import { Dropdown } from "controls/dropdown";
|
||||
import { Toolbar } from "toolbars/toolbar";
|
||||
export class PrimaryToolbar extends Toolbar {
|
||||
#private;
|
||||
constructor(ID: string);
|
||||
getMainDropdown(): Dropdown;
|
||||
}
|
||||
}
|
||||
declare module "panels/unitcontrolpanel" {
|
||||
import { Panel } from "panels/panel";
|
||||
export class UnitControlPanel extends Panel {
|
||||
@@ -1680,35 +1794,11 @@ declare module "shortcut/shortcutmanager" {
|
||||
onKeyUp(callback: CallableFunction): void;
|
||||
}
|
||||
}
|
||||
declare module "toolbars/toolbar" {
|
||||
export class Toolbar {
|
||||
#private;
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string);
|
||||
show(): void;
|
||||
hide(): void;
|
||||
toggle(): void;
|
||||
getElement(): HTMLElement;
|
||||
getVisible(): boolean;
|
||||
}
|
||||
}
|
||||
declare module "toolbars/commandmodetoolbar" {
|
||||
import { Toolbar } from "toolbars/toolbar";
|
||||
export class CommandModeToolbar extends Toolbar {
|
||||
}
|
||||
}
|
||||
declare module "toolbars/primarytoolbar" {
|
||||
import { Dropdown } from "controls/dropdown";
|
||||
import { Toolbar } from "toolbars/toolbar";
|
||||
export class PrimaryToolbar extends Toolbar {
|
||||
#private;
|
||||
constructor(ID: string);
|
||||
getMainDropdown(): Dropdown;
|
||||
}
|
||||
}
|
||||
declare module "unit/citiesDatabase" {
|
||||
export var citiesDatabase: {
|
||||
lat: number;
|
||||
@@ -1996,7 +2086,7 @@ declare module "unit/unitsmanager" {
|
||||
* @param explosion If true, the unit will be deleted using an explosion
|
||||
* @returns
|
||||
*/
|
||||
selectedUnitsDelete(explosion?: boolean): void;
|
||||
selectedUnitsDelete(explosion?: boolean, explosionType?: string): void;
|
||||
/** Compute the destinations of every unit in the selected units. This function preserves the relative positions of the units, and rotates the whole formation by rotation.
|
||||
*
|
||||
* @param latlng Center of the group after the translation
|
||||
@@ -2116,7 +2206,7 @@ declare module "server/servermanager" {
|
||||
isCommandExecuted(callback: CallableFunction, commandHash: string): void;
|
||||
addDestination(ID: number, path: any, callback?: CallableFunction): void;
|
||||
spawnSmoke(color: string, latlng: LatLng, callback?: CallableFunction): void;
|
||||
spawnExplosion(intensity: number, latlng: LatLng, callback?: CallableFunction): void;
|
||||
spawnExplosion(intensity: number, explosionType: string, latlng: LatLng, callback?: CallableFunction): void;
|
||||
spawnAircrafts(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback?: CallableFunction): void;
|
||||
spawnHelicopters(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback?: CallableFunction): void;
|
||||
spawnGroundUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback?: CallableFunction): void;
|
||||
@@ -2131,7 +2221,7 @@ declare module "server/servermanager" {
|
||||
ID: number;
|
||||
location: LatLng;
|
||||
}[], deleteOriginal: boolean, spawnPoints: number, callback?: CallableFunction): void;
|
||||
deleteUnit(ID: number, explosion: boolean, immediate: boolean, callback?: CallableFunction): void;
|
||||
deleteUnit(ID: number, explosion: boolean, explosionType: string, immediate: boolean, callback?: CallableFunction): void;
|
||||
landAt(ID: number, latlng: LatLng, callback?: CallableFunction): void;
|
||||
changeSpeed(ID: number, speedChange: string, callback?: CallableFunction): void;
|
||||
setSpeed(ID: number, speed: number, callback?: CallableFunction): void;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var fs = require('fs');
|
||||
let rawdata = fs.readFileSync('../olympus.json');
|
||||
let config = JSON.parse(rawdata);
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
@@ -12,8 +16,14 @@ var http = require('http');
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
var configPort = null;
|
||||
if (config["client"] != undefined && config["client"]["port"] != undefined) {
|
||||
configPort = config["client"]["port"];
|
||||
}
|
||||
|
||||
var port = normalizePort(configPort || '3000');
|
||||
app.set('port', port);
|
||||
console.log("Express server listening on port: " + port)
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
|
||||
296
client/demo.js
@@ -2,124 +2,15 @@ const { random } = require('@turf/turf');
|
||||
var basicAuth = require('express-basic-auth')
|
||||
var enc = new TextEncoder();
|
||||
|
||||
const DEMO_UNIT_DATA = {
|
||||
["1"]:{ category: "Aircraft", alive: true, human: false, controlled: true, coalition: 2, country: 0, name: "KC-135", unitName: "Cool guy 1-1 who also has a very long name", groupName: "Cool group 1", state: 1, task: "Being cool!",
|
||||
hasTask: true, position: { lat: 37, lng: -116, alt: 1000 }, speed: 200, horizontalVelocity: 200, verticalVelicity: 0, heading: 45, isActiveTanker: true, isActiveAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 0,
|
||||
targetPosition: { lat: 0, lng: 0, alt: 0 },
|
||||
ROE: 1,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 }, { quantity: 2, name: "A cool missile with a longer name\0Ciao", guidance: 0, category: 0, missileCategory: 0 }, { quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } , { quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } , { quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } , { quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } , { quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } , { quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } , { quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [{ID: 2, detectionMethod: 1}, {ID: 3, detectionMethod: 4}, {ID: 4, detectionMethod: 1}],
|
||||
activePath: [{lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0}]
|
||||
},
|
||||
["2"]:{ category: "Aircraft", alive: true, human: false, controlled: true, coalition: 1, country: 0, name: "E-3A", unitName: "Cool guy 1-2", groupName: "Cool group 2", state: 1, task: "Being cool",
|
||||
hasTask: true, position: { lat: 36.9, lng: -116, alt: 1000 }, speed: 200, horizontalVelocity: 200, verticalVelicity: 0, heading: 315 * Math.PI / 180, isActiveTanker: false, isActiveAWACS: true, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 0,
|
||||
targetPosition: { lat: 0, lng: 0, alt: 0 },
|
||||
ROE: 1,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [{ID: 4, detectionMethod: 1}],
|
||||
activePath: [ ]
|
||||
}, ["3"]:{ category: "Helicopter", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "AH-64D_BLK_II", unitName: "Cool guy 1-4", groupName: "Cool group 3", state: 1, task: "Being cool",
|
||||
hasTask: false, position: { lat: 37.1, lng: -116.1, alt: 1000 }, speed: 200, horizontalVelocity: 200, verticalVelicity: 0, heading: 315 * Math.PI / 180, isActiveTanker: false, isActiveAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 0,
|
||||
targetPosition: { lat: 0, lng: 0, alt: 0 },
|
||||
ROE: 1,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [{ID: 1, detectionMethod: 16}],
|
||||
activePath: [ ]
|
||||
}, ["4"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 2, country: 0, name: "Tor 9A331", unitName: "Cool guy 2-1", groupName: "Cool group 4", state: 1, task: "Being cool",
|
||||
hasTask: false, position: { lat: 37.2, lng: -116.1, alt: 1000 }, speed: 200, horizontalVelocity: 200, verticalVelicity: 0, heading: 315 * Math.PI / 180, isActiveTanker: false, isActiveAWACS: false, onOff: false, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 0,
|
||||
targetPosition: { lat: 0, lng: 0, alt: 0 },
|
||||
ROE: 1,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [{ID: 1001, detectionMethod: 16}],
|
||||
activePath: [ ],
|
||||
isLeader: true,
|
||||
operateAs: 2
|
||||
}, ["5"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 2, country: 0, name: "Gepard", unitName: "Cool guy 2-2", groupName: "Cool group 4", state: 1, task: "Being cool",
|
||||
hasTask: false, position: { lat: 37.21, lng: -116.1, alt: 1000 }, speed: 200, horizontalVelocity: 200, verticalVelicity: 0, heading: 315 * Math.PI / 180, isActiveTanker: false, isActiveAWACS: false, onOff: false, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 0,
|
||||
targetPosition: { lat: 0, lng: 0, alt: 0 },
|
||||
ROE: 1,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [],
|
||||
activePath: [ ],
|
||||
isLeader: false,
|
||||
operateAs: 2
|
||||
},
|
||||
["6"]:{ category: "Aircraft", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "FA-18C_hornet", unitName: "Bad boi 1-2", groupName: "Bad group 1", state: 1, task: "Being bad",
|
||||
hasTask: false, position: { lat: 36.8, lng: -116, alt: 1000 }, speed: 200, horizontalVelocity: 200, verticalVelicity: 0, heading: 315 * Math.PI / 180, isActiveTanker: false, isActiveAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 0,
|
||||
targetPosition: { lat: 0, lng: 0, alt: 0 },
|
||||
ROE: 1,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [{ID: 1, detectionMethod: 16}],
|
||||
activePath: [ ]
|
||||
}, ["7"]:{ category: "GroundUnit", alive: true, human: false, controlled: true, coalition: 1, country: 0, name: "Tor 9A331", unitName: "Cool guy 2-1", groupName: "Cool group 10", state: 1, task: "Being cool",
|
||||
hasTask: false, position: { lat: 37.2, lng: -116.2, alt: 1000 }, speed: 200, horizontalVelocity: 200, verticalVelicity: 0, heading: 315 * Math.PI / 180, isActiveTanker: false, isActiveAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 0,
|
||||
targetPosition: { lat: 0, lng: 0, alt: 0 },
|
||||
ROE: 1,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [{ID: 1001, detectionMethod: 16}],
|
||||
activePath: [ ],
|
||||
isLeader: true
|
||||
},
|
||||
}
|
||||
const aircraftDatabase = require('./public/databases/units/aircraftDatabase.json');
|
||||
const helicopterDatabase = require('./public/databases/units/helicopterDatabase.json');
|
||||
const groundUnitDatabase = require('./public/databases/units/groundUnitDatabase.json');
|
||||
const navyUnitDatabase = require('./public/databases/units/navyUnitDatabase.json');
|
||||
|
||||
const DEMO_UNIT_DATA = {}
|
||||
|
||||
const DEMO_WEAPONS_DATA = {
|
||||
["1001"]:{ category: "Missile", alive: true, coalition: 2, name: "", position: { lat: 37.1, lng: -116, alt: 1000 }, speed: 200, heading: 45 * Math.PI / 180 },
|
||||
/*["1001"]:{ category: "Missile", alive: true, coalition: 2, name: "", position: { lat: 37.1, lng: -116, alt: 1000 }, speed: 200, heading: 45 * Math.PI / 180 }, */
|
||||
}
|
||||
|
||||
class DemoDataGenerator {
|
||||
@@ -142,6 +33,87 @@ class DemoDataGenerator {
|
||||
},
|
||||
}))
|
||||
|
||||
|
||||
let baseData = { alive: true, human: false, controlled: true, coalition: 2, country: 0, unitName: "Cool guy", groupName: "Cool group 1", state: 1, task: "Being cool!",
|
||||
hasTask: true, position: { lat: 37, lng: -116, alt: 1000 }, speed: 200, horizontalVelocity: 200, verticalVelicity: 0, heading: 45, isActiveTanker: false, isActiveAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 0,
|
||||
targetPosition: { lat: 0, lng: 0, alt: 0 },
|
||||
ROE: 1,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [],
|
||||
contacts: [],
|
||||
activePath: [],
|
||||
isLeader: true
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
UNCOMMENT TO TEST ALL UNITS
|
||||
|
||||
var databases = Object.assign({}, aircraftDatabase, helicopterDatabase, groundUnitDatabase, navyUnitDatabase);
|
||||
var t = Object.keys(databases).length;
|
||||
var l = Math.floor(Math.sqrt(t));
|
||||
let latIdx = 0;
|
||||
let lngIdx = 0;
|
||||
let idx = 1;
|
||||
console.log(l)
|
||||
for (let name in databases) {
|
||||
if (databases[name].enabled) {
|
||||
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
|
||||
DEMO_UNIT_DATA[idx].name = name;
|
||||
DEMO_UNIT_DATA[idx].groupName = `Group-${idx}`;
|
||||
DEMO_UNIT_DATA[idx].position.lat += latIdx / 5;
|
||||
DEMO_UNIT_DATA[idx].position.lng += lngIdx / 5;
|
||||
|
||||
latIdx += 1;
|
||||
if (latIdx === l) {
|
||||
latIdx = 0;
|
||||
lngIdx += 1;
|
||||
}
|
||||
|
||||
if (name in aircraftDatabase)
|
||||
DEMO_UNIT_DATA[idx].category = "Aircraft";
|
||||
else if (name in helicopterDatabase)
|
||||
DEMO_UNIT_DATA[idx].category = "Helicopter";
|
||||
else if (name in groundUnitDatabase)
|
||||
DEMO_UNIT_DATA[idx].category = "GroundUnit";
|
||||
else if (name in navyUnitDatabase)
|
||||
DEMO_UNIT_DATA[idx].category = "NavyUnit";
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
let idx = 1;
|
||||
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
|
||||
DEMO_UNIT_DATA[idx].name = "S_75M_Volhov";
|
||||
DEMO_UNIT_DATA[idx].groupName = `Group`;
|
||||
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
|
||||
DEMO_UNIT_DATA[idx].category = "GroundUnit";
|
||||
DEMO_UNIT_DATA[idx].isLeader = true;
|
||||
|
||||
idx += 1;
|
||||
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
|
||||
DEMO_UNIT_DATA[idx].name = "SNR_75V";
|
||||
DEMO_UNIT_DATA[idx].groupName = `Group`;
|
||||
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
|
||||
DEMO_UNIT_DATA[idx].category = "GroundUnit";
|
||||
DEMO_UNIT_DATA[idx].isLeader = false;
|
||||
|
||||
idx += 1;
|
||||
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
|
||||
DEMO_UNIT_DATA[idx].name = "Ural-4320 APA-5D";
|
||||
DEMO_UNIT_DATA[idx].groupName = `Group`;
|
||||
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
|
||||
DEMO_UNIT_DATA[idx].category = "GroundUnit";
|
||||
DEMO_UNIT_DATA[idx].isLeader = false;
|
||||
|
||||
this.startTime = Date.now();
|
||||
}
|
||||
@@ -150,51 +122,53 @@ class DemoDataGenerator {
|
||||
var array = new Uint8Array();
|
||||
var time = Date.now();
|
||||
array = this.concat(array, this.uint64ToByteArray(BigInt(time)));
|
||||
for (let idx in DEMO_UNIT_DATA) {
|
||||
const unit = DEMO_UNIT_DATA[idx];
|
||||
array = this.concat(array, this.uint32ToByteArray(idx));
|
||||
array = this.appendString(array, unit.category, 1);
|
||||
array = this.appendUint8(array, unit.alive, 2);
|
||||
array = this.appendUint8(array, unit.human, 3);
|
||||
array = this.appendUint8(array, unit.controlled, 4);
|
||||
array = this.appendUint16(array, unit.coalition, 5);
|
||||
array = this.appendUint8(array, unit.country, 6);
|
||||
array = this.appendString(array, unit.name, 7);
|
||||
array = this.appendString(array, unit.unitName, 8);
|
||||
array = this.appendString(array, unit.groupName, 9);
|
||||
array = this.appendUint8(array, unit.state, 10);
|
||||
array = this.appendString(array, unit.task, 11);
|
||||
array = this.appendUint8(array, unit.hasTask, 12);
|
||||
array = this.appendCoordinates(array, unit.position, 13);
|
||||
array = this.appendDouble(array, unit.speed, 14);
|
||||
array = this.appendDouble(array, unit.horizontalVelocity, 15);
|
||||
array = this.appendDouble(array, unit.verticalVelicity, 16);
|
||||
array = this.appendDouble(array, unit.heading, 17);
|
||||
array = this.appendUint8(array, unit.isActiveTanker, 18);
|
||||
array = this.appendUint8(array, unit.isActiveAWACS, 19);
|
||||
array = this.appendUint8(array, unit.onOff, 20);
|
||||
array = this.appendUint8(array, unit.followRoads, 21);
|
||||
array = this.appendUint16(array, unit.fuel, 22);
|
||||
array = this.appendDouble(array, unit.desiredSpeed, 23);
|
||||
array = this.appendUint8(array, unit.desiredSpeedType, 24);
|
||||
array = this.appendDouble(array, unit.desiredAltitude, 25);
|
||||
array = this.appendUint8(array, unit.desiredAltitudeType, 26);
|
||||
array = this.appendUint32(array, unit.leaderID, 27);
|
||||
array = this.appendOffset(array, unit.formationOffset, 28);
|
||||
array = this.appendUint32(array, unit.targetID, 29);
|
||||
array = this.appendCoordinates(array, unit.targetPosition, 30);
|
||||
array = this.appendUint8(array, unit.ROE, 31);
|
||||
array = this.appendUint8(array, unit.reactionToThreat, 32);
|
||||
array = this.appendUint8(array, unit.emissionsCountermeasures, 33);
|
||||
array = this.appendTACAN(array, unit.TACAN, 34);
|
||||
array = this.appendRadio(array, unit.radio, 35);
|
||||
array = this.appendRadio(array, unit.generalSettings, 36);
|
||||
array = this.appendAmmo(array, unit.ammo, 37);
|
||||
array = this.appendContacts(array, unit.contacts, 38);
|
||||
array = this.appendActivePath(array, unit.activePath, 39);
|
||||
array = this.appendUint8(array, unit.isLeader, 40);
|
||||
array = this.appendUint8(array, unit.operateAs, 41);
|
||||
array = this.concat(array, this.uint8ToByteArray(255));
|
||||
if (req.query["time"] == 0){
|
||||
for (let idx in DEMO_UNIT_DATA) {
|
||||
const unit = DEMO_UNIT_DATA[idx];
|
||||
array = this.concat(array, this.uint32ToByteArray(idx));
|
||||
array = this.appendString(array, unit.category, 1);
|
||||
array = this.appendUint8(array, unit.alive, 2);
|
||||
array = this.appendUint8(array, unit.human, 3);
|
||||
array = this.appendUint8(array, unit.controlled, 4);
|
||||
array = this.appendUint16(array, unit.coalition, 5);
|
||||
array = this.appendUint8(array, unit.country, 6);
|
||||
array = this.appendString(array, unit.name, 7);
|
||||
array = this.appendString(array, unit.unitName, 8);
|
||||
array = this.appendString(array, unit.groupName, 9);
|
||||
array = this.appendUint8(array, unit.state, 10);
|
||||
array = this.appendString(array, unit.task, 11);
|
||||
array = this.appendUint8(array, unit.hasTask, 12);
|
||||
array = this.appendCoordinates(array, unit.position, 13);
|
||||
array = this.appendDouble(array, unit.speed, 14);
|
||||
array = this.appendDouble(array, unit.horizontalVelocity, 15);
|
||||
array = this.appendDouble(array, unit.verticalVelicity, 16);
|
||||
array = this.appendDouble(array, unit.heading, 17);
|
||||
array = this.appendUint8(array, unit.isActiveTanker, 18);
|
||||
array = this.appendUint8(array, unit.isActiveAWACS, 19);
|
||||
array = this.appendUint8(array, unit.onOff, 20);
|
||||
array = this.appendUint8(array, unit.followRoads, 21);
|
||||
array = this.appendUint16(array, unit.fuel, 22);
|
||||
array = this.appendDouble(array, unit.desiredSpeed, 23);
|
||||
array = this.appendUint8(array, unit.desiredSpeedType, 24);
|
||||
array = this.appendDouble(array, unit.desiredAltitude, 25);
|
||||
array = this.appendUint8(array, unit.desiredAltitudeType, 26);
|
||||
array = this.appendUint32(array, unit.leaderID, 27);
|
||||
array = this.appendOffset(array, unit.formationOffset, 28);
|
||||
array = this.appendUint32(array, unit.targetID, 29);
|
||||
array = this.appendCoordinates(array, unit.targetPosition, 30);
|
||||
array = this.appendUint8(array, unit.ROE, 31);
|
||||
array = this.appendUint8(array, unit.reactionToThreat, 32);
|
||||
array = this.appendUint8(array, unit.emissionsCountermeasures, 33);
|
||||
array = this.appendTACAN(array, unit.TACAN, 34);
|
||||
array = this.appendRadio(array, unit.radio, 35);
|
||||
array = this.appendRadio(array, unit.generalSettings, 36);
|
||||
array = this.appendAmmo(array, unit.ammo, 37);
|
||||
array = this.appendContacts(array, unit.contacts, 38);
|
||||
array = this.appendActivePath(array, unit.activePath, 39);
|
||||
array = this.appendUint8(array, unit.isLeader, 40);
|
||||
array = this.appendUint8(array, unit.operateAs, 41);
|
||||
array = this.concat(array, this.uint8ToByteArray(255));
|
||||
}
|
||||
}
|
||||
res.end(Buffer.from(array, 'binary'));
|
||||
};
|
||||
|
||||
4
client/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "DCSOlympus",
|
||||
"version": "v0.4.5-alpha",
|
||||
"version": "v0.4.6-alpha",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "DCSOlympus",
|
||||
"version": "v0.4.5-alpha",
|
||||
"version": "v0.4.6-alpha",
|
||||
"dependencies": {
|
||||
"@turf/turf": "^6.5.0",
|
||||
"body-parser": "^1.20.2",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "DCSOlympus",
|
||||
"node-main": "./bin/www",
|
||||
"main": "http://localhost:3000",
|
||||
"version": "v0.4.5-alpha",
|
||||
"version": "v0.4.6-alpha",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "browserify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] && copy.bat",
|
||||
|
||||
@@ -54,7 +54,7 @@ class AirUnitEditor extends uniteditor_1.UnitEditor {
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Cost", (_b = String(blueprint.cost)) !== null && _b !== void 0 ? _b : "", "number", (value) => { blueprint.cost = parseFloat(value); });
|
||||
(0, utils_1.addCheckboxInput)(this.contentDiv2, "Can target point", (_c = blueprint.canTargetPoint) !== null && _c !== void 0 ? _c : false, (value) => { blueprint.canTargetPoint = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Description", (_d = blueprint.description) !== null && _d !== void 0 ? _d : "", "text", (value) => { blueprint.description = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Abilities", (_e = blueprint.abilities) !== null && _e !== void 0 ? _e : "", "text", (value) => { blueprint.abilities = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Tags", (_e = blueprint.tags) !== null && _e !== void 0 ? _e : "", "text", (value) => { blueprint.tags = value; });
|
||||
/* Add a scrollable list of loadouts that the user can edit */
|
||||
var title = document.createElement("label");
|
||||
title.innerText = "Loadouts";
|
||||
@@ -206,13 +206,13 @@ class DatabaseManagerPlugin {
|
||||
__classPrivateFieldGet(this, _DatabaseManagerPlugin_mainContentContainer, "f").classList.add("dm-container");
|
||||
__classPrivateFieldGet(this, _DatabaseManagerPlugin_element, "f").appendChild(__classPrivateFieldGet(this, _DatabaseManagerPlugin_mainContentContainer, "f"));
|
||||
__classPrivateFieldSet(this, _DatabaseManagerPlugin_contentDiv1, document.createElement("div"), "f");
|
||||
__classPrivateFieldGet(this, _DatabaseManagerPlugin_contentDiv1, "f").classList.add("dm-content-container");
|
||||
__classPrivateFieldGet(this, _DatabaseManagerPlugin_contentDiv1, "f").classList.add("dm-content-container", "ol-scrollable");
|
||||
__classPrivateFieldGet(this, _DatabaseManagerPlugin_mainContentContainer, "f").appendChild(__classPrivateFieldGet(this, _DatabaseManagerPlugin_contentDiv1, "f"));
|
||||
__classPrivateFieldSet(this, _DatabaseManagerPlugin_contentDiv2, document.createElement("div"), "f");
|
||||
__classPrivateFieldGet(this, _DatabaseManagerPlugin_contentDiv2, "f").classList.add("dm-content-container");
|
||||
__classPrivateFieldGet(this, _DatabaseManagerPlugin_contentDiv2, "f").classList.add("dm-content-container", "ol-scrollable");
|
||||
__classPrivateFieldGet(this, _DatabaseManagerPlugin_mainContentContainer, "f").appendChild(__classPrivateFieldGet(this, _DatabaseManagerPlugin_contentDiv2, "f"));
|
||||
__classPrivateFieldSet(this, _DatabaseManagerPlugin_contentDiv3, document.createElement("div"), "f");
|
||||
__classPrivateFieldGet(this, _DatabaseManagerPlugin_contentDiv3, "f").classList.add("dm-content-container");
|
||||
__classPrivateFieldGet(this, _DatabaseManagerPlugin_contentDiv3, "f").classList.add("dm-content-container", "ol-scrollable");
|
||||
__classPrivateFieldGet(this, _DatabaseManagerPlugin_mainContentContainer, "f").appendChild(__classPrivateFieldGet(this, _DatabaseManagerPlugin_contentDiv3, "f"));
|
||||
/* Create the database editors, which use the three divs created before */
|
||||
__classPrivateFieldSet(this, _DatabaseManagerPlugin_aircraftEditor, new airuniteditor_1.AirUnitEditor(__classPrivateFieldGet(this, _DatabaseManagerPlugin_contentDiv1, "f"), __classPrivateFieldGet(this, _DatabaseManagerPlugin_contentDiv2, "f"), __classPrivateFieldGet(this, _DatabaseManagerPlugin_contentDiv3, "f")), "f");
|
||||
@@ -508,7 +508,7 @@ class GroundUnitEditor extends uniteditor_1.UnitEditor {
|
||||
* @param blueprint The blueprint to edit
|
||||
*/
|
||||
setBlueprint(blueprint) {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
|
||||
__classPrivateFieldSet(this, _GroundUnitEditor_blueprint, blueprint, "f");
|
||||
if (__classPrivateFieldGet(this, _GroundUnitEditor_blueprint, "f") !== null) {
|
||||
this.contentDiv2.replaceChildren();
|
||||
@@ -519,25 +519,29 @@ class GroundUnitEditor extends uniteditor_1.UnitEditor {
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Label", blueprint.label, "text", (value) => { blueprint.label = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value) => { blueprint.shortLabel = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Type", (_a = blueprint.type) !== null && _a !== void 0 ? _a : "", "text", (value) => { blueprint.type = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Unit when grouped", (_b = blueprint.unitWhenGrouped) !== null && _b !== void 0 ? _b : "", "text", (value) => { blueprint.unitWhenGrouped = value; });
|
||||
(0, utils_1.addDropdownInput)(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value) => { blueprint.coalition = value; });
|
||||
(0, utils_1.addDropdownInput)(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value) => { blueprint.era = value; });
|
||||
//addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Cost", (_b = String(blueprint.cost)) !== null && _b !== void 0 ? _b : "", "number", (value) => { blueprint.cost = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Acquisition range [m]", (_c = String(blueprint.acquisitionRange)) !== null && _c !== void 0 ? _c : "", "number", (value) => { blueprint.acquisitionRange = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Engagement range [m]", (_d = String(blueprint.engagementRange)) !== null && _d !== void 0 ? _d : "", "number", (value) => { blueprint.engagementRange = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Targeting range [m]", (_e = String(blueprint.targetingRange)) !== null && _e !== void 0 ? _e : "", "number", (value) => { blueprint.targetingRange = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Barrel height [m]", (_f = String(blueprint.barrelHeight)) !== null && _f !== void 0 ? _f : "", "number", (value) => { blueprint.barrelHeight = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Muzzle velocity [m/s]", (_g = String(blueprint.muzzleVelocity)) !== null && _g !== void 0 ? _g : "", "number", (value) => { blueprint.muzzleVelocity = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Aim time [s]", (_h = String(blueprint.aimTime)) !== null && _h !== void 0 ? _h : "", "number", (value) => { blueprint.aimTime = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Burst quantity", (_j = String(blueprint.shotsToFire)) !== null && _j !== void 0 ? _j : "", "number", (value) => { blueprint.shotsToFire = Math.round(parseFloat(value)); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Burst base interval [s]", (_k = String(blueprint.shotsBaseInterval)) !== null && _k !== void 0 ? _k : "", "number", (value) => { blueprint.shotsBaseInterval = Math.round(parseFloat(value)); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Base scatter [°]", (_l = String(blueprint.shotsBaseScatter)) !== null && _l !== void 0 ? _l : "", "number", (value) => { blueprint.shotsBaseScatter = Math.round(parseFloat(value)); });
|
||||
(0, utils_1.addCheckboxInput)(this.contentDiv2, "Can target point", (_m = blueprint.canTargetPoint) !== null && _m !== void 0 ? _m : false, (value) => { blueprint.canTargetPoint = value; });
|
||||
(0, utils_1.addCheckboxInput)(this.contentDiv2, "Can rearm", (_o = blueprint.canRearm) !== null && _o !== void 0 ? _o : false, (value) => { blueprint.canRearm = value; });
|
||||
(0, utils_1.addCheckboxInput)(this.contentDiv2, "Can operate as AAA", (_p = blueprint.canAAA) !== null && _p !== void 0 ? _p : false, (value) => { blueprint.canAAA = value; });
|
||||
(0, utils_1.addCheckboxInput)(this.contentDiv2, "Indirect fire (e.g. mortar)", (_q = blueprint.indirectFire) !== null && _q !== void 0 ? _q : false, (value) => { blueprint.indirectFire = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Description", (_r = blueprint.description) !== null && _r !== void 0 ? _r : "", "text", (value) => { blueprint.description = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Abilities", (_s = blueprint.abilities) !== null && _s !== void 0 ? _s : "", "text", (value) => { blueprint.abilities = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Cost", (_c = String(blueprint.cost)) !== null && _c !== void 0 ? _c : "", "number", (value) => { blueprint.cost = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Acquisition range [m]", (_d = String(blueprint.acquisitionRange)) !== null && _d !== void 0 ? _d : "", "number", (value) => { blueprint.acquisitionRange = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Engagement range [m]", (_e = String(blueprint.engagementRange)) !== null && _e !== void 0 ? _e : "", "number", (value) => { blueprint.engagementRange = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Targeting range [m]", (_f = String(blueprint.targetingRange)) !== null && _f !== void 0 ? _f : "", "number", (value) => { blueprint.targetingRange = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Aim method range [m]", (_g = String(blueprint.aimMethodRange)) !== null && _g !== void 0 ? _g : "", "number", (value) => { blueprint.aimMethodRange = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Barrel height [m]", (_h = String(blueprint.barrelHeight)) !== null && _h !== void 0 ? _h : "", "number", (value) => { blueprint.barrelHeight = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Muzzle velocity [m/s]", (_j = String(blueprint.muzzleVelocity)) !== null && _j !== void 0 ? _j : "", "number", (value) => { blueprint.muzzleVelocity = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Aim time [s]", (_k = String(blueprint.aimTime)) !== null && _k !== void 0 ? _k : "", "number", (value) => { blueprint.aimTime = parseFloat(value); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Shots to fire", (_l = String(blueprint.shotsToFire)) !== null && _l !== void 0 ? _l : "", "number", (value) => { blueprint.shotsToFire = Math.round(parseFloat(value)); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Shots base interval [s]", (_m = String(blueprint.shotsBaseInterval)) !== null && _m !== void 0 ? _m : "", "number", (value) => { blueprint.shotsBaseInterval = Math.round(parseFloat(value)); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Shots base scatter [°]", (_o = String(blueprint.shotsBaseScatter)) !== null && _o !== void 0 ? _o : "", "number", (value) => { blueprint.shotsBaseScatter = Math.round(parseFloat(value)); });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Alertness time constant [s]", (_p = String(blueprint.alertnessTimeConstant)) !== null && _p !== void 0 ? _p : "", "number", (value) => { blueprint.alertnessTimeConstant = Math.round(parseFloat(value)); });
|
||||
(0, utils_1.addCheckboxInput)(this.contentDiv2, "Can target point", (_q = blueprint.canTargetPoint) !== null && _q !== void 0 ? _q : false, (value) => { blueprint.canTargetPoint = value; });
|
||||
(0, utils_1.addCheckboxInput)(this.contentDiv2, "Can rearm", (_r = blueprint.canRearm) !== null && _r !== void 0 ? _r : false, (value) => { blueprint.canRearm = value; });
|
||||
(0, utils_1.addCheckboxInput)(this.contentDiv2, "Can operate as AAA", (_s = blueprint.canAAA) !== null && _s !== void 0 ? _s : false, (value) => { blueprint.canAAA = value; });
|
||||
(0, utils_1.addCheckboxInput)(this.contentDiv2, "Indirect fire (e.g. mortar)", (_t = blueprint.indirectFire) !== null && _t !== void 0 ? _t : false, (value) => { blueprint.indirectFire = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Description", (_u = blueprint.description) !== null && _u !== void 0 ? _u : "", "text", (value) => { blueprint.description = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Tags", (_v = blueprint.tags) !== null && _v !== void 0 ? _v : "", "text", (value) => { blueprint.tags = value; });
|
||||
(0, utils_1.addStringInput)(this.contentDiv2, "Marker file", (_w = blueprint.markerFile) !== null && _w !== void 0 ? _w : "", "text", (value) => { blueprint.markerFile = value; });
|
||||
}
|
||||
}
|
||||
/** Add a new empty blueprint
|
||||
@@ -751,9 +755,9 @@ class UnitEditor {
|
||||
this.database = JSON.parse(JSON.stringify({ blueprints: database.getBlueprints(true) }));
|
||||
}
|
||||
/** Show the editor
|
||||
*
|
||||
* @param filter String filter
|
||||
*/
|
||||
show() {
|
||||
show(filter = "") {
|
||||
this.visible = true;
|
||||
this.contentDiv1.replaceChildren();
|
||||
this.contentDiv2.replaceChildren();
|
||||
@@ -763,17 +767,16 @@ class UnitEditor {
|
||||
var title = document.createElement("label");
|
||||
title.innerText = "Units list";
|
||||
this.contentDiv1.appendChild(title);
|
||||
(0, utils_1.addBlueprintsScroll)(this.contentDiv1, this.database, (key) => {
|
||||
if (this.database != null)
|
||||
this.setBlueprint(this.database.blueprints[key]);
|
||||
});
|
||||
(0, utils_1.addNewElementInput)(this.contentDiv1, (ev, input) => {
|
||||
if (input.value != "")
|
||||
this.addBlueprint((input).value);
|
||||
});
|
||||
var filterInput = document.createElement("input");
|
||||
filterInput.value = filter;
|
||||
this.contentDiv1.appendChild(filterInput);
|
||||
filterInput.onchange = (e) => {
|
||||
this.show(e.target.value);
|
||||
};
|
||||
this.addBlueprints(filter);
|
||||
}
|
||||
}
|
||||
/** Hid the editor
|
||||
/** Hide the editor
|
||||
*
|
||||
*/
|
||||
hide() {
|
||||
@@ -789,6 +792,22 @@ class UnitEditor {
|
||||
getDatabase() {
|
||||
return this.database;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param filter String filter
|
||||
*/
|
||||
addBlueprints(filter = "") {
|
||||
if (this.database) {
|
||||
(0, utils_1.addBlueprintsScroll)(this.contentDiv1, this.database, filter, (key) => {
|
||||
if (this.database != null)
|
||||
this.setBlueprint(this.database.blueprints[key]);
|
||||
});
|
||||
(0, utils_1.addNewElementInput)(this.contentDiv1, (ev, input) => {
|
||||
if (input.value != "")
|
||||
this.addBlueprint((input).value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.UnitEditor = UnitEditor;
|
||||
|
||||
@@ -958,36 +977,56 @@ exports.addNewElementInput = addNewElementInput;
|
||||
*
|
||||
* @param div The HTMLElement that will contain the list
|
||||
* @param database The database that will be used to fill the list of blueprints
|
||||
* @param filter A string filter that will be executed to filter the blueprints to add
|
||||
* @param callback Callback called when the user clicks on one of the elements
|
||||
*/
|
||||
function addBlueprintsScroll(div, database, callback) {
|
||||
function addBlueprintsScroll(div, database, filter, callback) {
|
||||
var scrollDiv = document.createElement("div");
|
||||
scrollDiv.classList.add("dm-scroll-container");
|
||||
if (database !== null) {
|
||||
var blueprints = database.blueprints;
|
||||
for (let key of Object.keys(blueprints).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))) {
|
||||
var rowDiv = document.createElement("div");
|
||||
scrollDiv.appendChild(rowDiv);
|
||||
var text = document.createElement("label");
|
||||
text.textContent = key;
|
||||
text.onclick = () => callback(key);
|
||||
rowDiv.appendChild(text);
|
||||
let checkbox = document.createElement("input");
|
||||
checkbox.type = "checkbox";
|
||||
checkbox.checked = blueprints[key].enabled;
|
||||
checkbox.onclick = () => {
|
||||
console.log(checkbox.checked);
|
||||
blueprints[key].enabled = checkbox.checked;
|
||||
};
|
||||
rowDiv.appendChild(checkbox);
|
||||
/* This button allows to remove an element from the list. It requires a refresh. */
|
||||
var button = document.createElement("button");
|
||||
button.innerText = "X";
|
||||
button.onclick = () => {
|
||||
delete blueprints[key];
|
||||
div.dispatchEvent(new Event("refresh"));
|
||||
};
|
||||
rowDiv.appendChild(button);
|
||||
var addKey = true;
|
||||
if (filter !== "") {
|
||||
try {
|
||||
var blueprint = blueprints[key];
|
||||
addKey = eval(filter);
|
||||
}
|
||||
catch (_a) {
|
||||
console.error("An error has occurred evaluating the blueprint filter");
|
||||
}
|
||||
}
|
||||
if (addKey) {
|
||||
var rowDiv = document.createElement("div");
|
||||
scrollDiv.appendChild(rowDiv);
|
||||
let text = document.createElement("div");
|
||||
text.innerHTML = `<div>${key}</div> <div>${blueprints[key].label}</div>`;
|
||||
text.onclick = () => {
|
||||
callback(key);
|
||||
const collection = document.getElementsByClassName("blueprint-selected");
|
||||
for (let i = 0; i < collection.length; i++) {
|
||||
collection[i].classList.remove("blueprint-selected");
|
||||
}
|
||||
text.classList.add("blueprint-selected");
|
||||
};
|
||||
rowDiv.appendChild(text);
|
||||
let checkbox = document.createElement("input");
|
||||
checkbox.type = "checkbox";
|
||||
checkbox.checked = blueprints[key].enabled;
|
||||
checkbox.onclick = () => {
|
||||
console.log(checkbox.checked);
|
||||
blueprints[key].enabled = checkbox.checked;
|
||||
};
|
||||
rowDiv.appendChild(checkbox);
|
||||
/* This button allows to remove an element from the list. It requires a refresh. */
|
||||
var button = document.createElement("button");
|
||||
button.innerText = "X";
|
||||
button.onclick = () => {
|
||||
delete blueprints[key];
|
||||
div.dispatchEvent(new Event("refresh"));
|
||||
};
|
||||
rowDiv.appendChild(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
div.appendChild(scrollDiv);
|
||||
@@ -1042,8 +1081,14 @@ function arrayToString(array) {
|
||||
return "[" + array.join(", ") + "]";
|
||||
}
|
||||
exports.arrayToString = arrayToString;
|
||||
/** Converts an a single string like [val1, val2, val3] into an array
|
||||
*
|
||||
* @param input The input string
|
||||
* @returns The array
|
||||
*/
|
||||
function stringToArray(input) {
|
||||
return input.match(/(\w)+/g) || [];
|
||||
var _a;
|
||||
return (_a = input.match(/(\w)+/g)) !== null && _a !== void 0 ? _a : [];
|
||||
}
|
||||
exports.stringToArray = stringToArray;
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export class AirUnitEditor extends UnitEditor {
|
||||
addStringInput(this.contentDiv2, "Cost", String(blueprint.cost) ?? "", "number", (value: string) => { blueprint.cost = parseFloat(value); });
|
||||
addCheckboxInput(this.contentDiv2, "Can target point", blueprint.canTargetPoint ?? false, (value: boolean) => {blueprint.canTargetPoint = value;})
|
||||
addStringInput(this.contentDiv2, "Description", blueprint.description ?? "", "text", (value: string) => {blueprint.description = value; });
|
||||
addStringInput(this.contentDiv2, "Abilities", blueprint.abilities ?? "", "text", (value: string) => {blueprint.abilities = value; });
|
||||
addStringInput(this.contentDiv2, "Tags", blueprint.tags ?? "", "text", (value: string) => {blueprint.tags = value; });
|
||||
|
||||
/* Add a scrollable list of loadouts that the user can edit */
|
||||
var title = document.createElement("label");
|
||||
|
||||
@@ -89,15 +89,15 @@ export class DatabaseManagerPlugin implements OlympusPlugin {
|
||||
this.#element.appendChild(this.#mainContentContainer);
|
||||
|
||||
this.#contentDiv1 = document.createElement("div");
|
||||
this.#contentDiv1.classList.add("dm-content-container");
|
||||
this.#contentDiv1.classList.add("dm-content-container", "ol-scrollable");
|
||||
this.#mainContentContainer.appendChild(this.#contentDiv1);
|
||||
|
||||
this.#contentDiv2 = document.createElement("div");
|
||||
this.#contentDiv2.classList.add("dm-content-container");
|
||||
this.#contentDiv2.classList.add("dm-content-container", "ol-scrollable");
|
||||
this.#mainContentContainer.appendChild(this.#contentDiv2);
|
||||
|
||||
this.#contentDiv3 = document.createElement("div");
|
||||
this.#contentDiv3.classList.add("dm-content-container");
|
||||
this.#contentDiv3.classList.add("dm-content-container", "ol-scrollable");
|
||||
this.#mainContentContainer.appendChild(this.#contentDiv3);
|
||||
|
||||
/* Create the database editors, which use the three divs created before */
|
||||
|
||||
@@ -30,6 +30,7 @@ export class GroundUnitEditor extends UnitEditor {
|
||||
addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => {blueprint.label = value; });
|
||||
addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => {blueprint.shortLabel = value; });
|
||||
addStringInput(this.contentDiv2, "Type", blueprint.type?? "", "text", (value: string) => {blueprint.type = value; });
|
||||
addStringInput(this.contentDiv2, "Unit when grouped", blueprint.unitWhenGrouped?? "", "text", (value: string) => {blueprint.unitWhenGrouped = value; });
|
||||
addDropdownInput(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value: string) => {blueprint.coalition = value; });
|
||||
addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value: string) => {blueprint.era = value; });
|
||||
//addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; });
|
||||
@@ -50,7 +51,8 @@ export class GroundUnitEditor extends UnitEditor {
|
||||
addCheckboxInput(this.contentDiv2, "Can operate as AAA", blueprint.canAAA ?? false, (value: boolean) => {blueprint.canAAA = value;})
|
||||
addCheckboxInput(this.contentDiv2, "Indirect fire (e.g. mortar)", blueprint.indirectFire ?? false, (value: boolean) => {blueprint.indirectFire = value;})
|
||||
addStringInput(this.contentDiv2, "Description", blueprint.description ?? "", "text", (value: string) => {blueprint.description = value; });
|
||||
addStringInput(this.contentDiv2, "Abilities", blueprint.abilities ?? "", "text", (value: string) => {blueprint.abilities = value; });
|
||||
addStringInput(this.contentDiv2, "Tags", blueprint.tags ?? "", "text", (value: string) => {blueprint.tags = value; });
|
||||
addStringInput(this.contentDiv2, "Marker file", blueprint.markerFile ?? "", "text", (value: string) => {blueprint.markerFile = value; });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -198,8 +198,8 @@ export function addBlueprintsScroll(div: HTMLElement, database: {blueprints: {[k
|
||||
var rowDiv = document.createElement("div");
|
||||
scrollDiv.appendChild(rowDiv);
|
||||
|
||||
let text = document.createElement("label");
|
||||
text.textContent = key;
|
||||
let text = document.createElement("div");
|
||||
text.innerHTML = `<div>${key}</div> <div>${blueprints[key].label}</div>`;
|
||||
text.onclick = () => {
|
||||
callback(key);
|
||||
const collection = document.getElementsByClassName("blueprint-selected");
|
||||
@@ -287,7 +287,11 @@ export function arrayToString(array: string[]) {
|
||||
return "[" + array.join( ", " ) + "]";
|
||||
}
|
||||
|
||||
|
||||
/** Converts an a single string like [val1, val2, val3] into an array
|
||||
*
|
||||
* @param input The input string
|
||||
* @returns The array
|
||||
*/
|
||||
export function stringToArray(input: string) {
|
||||
return input.match( /(\w)+/g ) ?? [];
|
||||
}
|
||||
@@ -63,6 +63,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 5px;
|
||||
max-height: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
@@ -71,7 +73,7 @@
|
||||
}
|
||||
|
||||
.dm-content-container:nth-of-type(1) {
|
||||
width: 300px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.dm-content-container:nth-of-type(2) {
|
||||
@@ -151,12 +153,29 @@
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div>div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div>button {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div>div>div:nth-child(1) {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div>div>div:nth-child(2) {
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -18529,7 +18529,7 @@
|
||||
"coalition": "blue",
|
||||
"label": "MB-339A",
|
||||
"era": "Mid Cold War",
|
||||
"shortLabel": "399",
|
||||
"shortLabel": "339",
|
||||
"loadouts": [
|
||||
{
|
||||
"items": [
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
:root {
|
||||
--right-panel-width:190px;
|
||||
}
|
||||
|
||||
/* Page style */
|
||||
#map-container {
|
||||
height: 100%;
|
||||
@@ -17,54 +13,10 @@
|
||||
top: 10px;
|
||||
z-index: 99999;
|
||||
column-gap: 10px;
|
||||
row-gap: 10px;
|
||||
margin-right: 320px;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
@media (max-width: 1820px) {
|
||||
#toolbar-container {
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
row-gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#primary-toolbar {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: fit-content;
|
||||
min-width: 650px;
|
||||
}
|
||||
|
||||
@media (max-width: 1820px) {
|
||||
#primary-toolbar {
|
||||
row-gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
#command-mode-toolbar {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#app-icon>.ol-select-options {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
#toolbar-summary {
|
||||
background-image: url("/images/icon-round.png");
|
||||
background-position: 20px 22px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 45px 45px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
text-indent: 60px;
|
||||
}
|
||||
|
||||
#toolbar-summary {
|
||||
white-space: nowrap;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#connection-status-panel {
|
||||
@@ -72,7 +24,7 @@
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
width: var( --right-panel-width );
|
||||
width: 190px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
@@ -84,35 +36,21 @@
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
row-gap: 10px;
|
||||
width: var( --right-panel-width );
|
||||
width: 190px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#unit-control-panel {
|
||||
height: fit-content;
|
||||
width: fit-content;
|
||||
left: 10px;
|
||||
position: absolute;
|
||||
top: 80px;
|
||||
width: 320px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
@media (max-width: 1820px) {
|
||||
#unit-control-panel {
|
||||
top: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1350px) {
|
||||
#unit-control-panel {
|
||||
top: 190px;
|
||||
}
|
||||
}
|
||||
|
||||
#unit-info-panel {
|
||||
bottom: 20px;
|
||||
font-size: 12px;
|
||||
left: 10px;
|
||||
position: absolute;
|
||||
width: fit-content;
|
||||
z-index: 9999;
|
||||
@@ -120,12 +58,8 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
@media (max-width: 1525px) {
|
||||
#unit-info-panel {
|
||||
flex-direction: column;
|
||||
}
|
||||
right: 210px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
#info-popup {
|
||||
|
||||
@@ -97,6 +97,22 @@
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
[data-object|="unit-groundunit"] .unit-short-label {
|
||||
transform: translateY(7px);
|
||||
}
|
||||
|
||||
/*** Health indicator ***/
|
||||
[data-object|="unit"] .unit-health {
|
||||
background: white;
|
||||
border: var(--unit-health-border-width) solid var(--secondary-dark-steel);
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: none;
|
||||
height: var(--unit-health-height);
|
||||
position: absolute;
|
||||
translate: var(--unit-health-x) var(--unit-health-y);
|
||||
width: var(--unit-health-width);
|
||||
}
|
||||
|
||||
/*** Fuel indicator ***/
|
||||
[data-object|="unit"] .unit-fuel {
|
||||
background: white;
|
||||
@@ -109,7 +125,8 @@
|
||||
width: var(--unit-fuel-width);
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-fuel-level {
|
||||
[data-object|="unit"] .unit-fuel-level,
|
||||
[data-object|="unit"] .unit-health-level {
|
||||
background-color: var(--secondary-light-grey);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@@ -178,6 +195,7 @@
|
||||
|
||||
/*** Common ***/
|
||||
[data-object|="unit"]:hover .unit-ammo,
|
||||
[data-object|="unit"]:hover .unit-health ,
|
||||
[data-object|="unit"]:hover .unit-fuel {
|
||||
display: flex;
|
||||
}
|
||||
@@ -188,13 +206,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-has-low-fuel] .unit-fuel {
|
||||
[data-object|="unit"][data-has-low-fuel] .unit-fuel, [data-object|="unit"][data-has-low-health] .unit-health {
|
||||
animation: pulse 1.5s linear infinite;
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-is-in-hotgroup] .unit-hotgroup,
|
||||
[data-object|="unit"][data-is-selected] .unit-ammo,
|
||||
[data-object|="unit"][data-is-selected] .unit-fuel,
|
||||
[data-object|="unit"][data-is-selected] .unit-health,
|
||||
[data-object|="unit"][data-is-selected] .unit-selected-spotlight {
|
||||
display: flex;
|
||||
}
|
||||
@@ -211,6 +230,7 @@
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-coalition="blue"] .unit-fuel-level,
|
||||
[data-object|="unit"][data-coalition="blue"] .unit-health-level,
|
||||
[data-object|="unit"][data-coalition="blue"][data-has-fox-1] .unit-ammo>div:nth-child(1),
|
||||
[data-object|="unit"][data-coalition="blue"][data-has-fox-2] .unit-ammo>div:nth-child(2),
|
||||
[data-object|="unit"][data-coalition="blue"][data-has-fox-3] .unit-ammo>div:nth-child(3),
|
||||
@@ -227,6 +247,7 @@
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-coalition="red"] .unit-fuel-level,
|
||||
[data-object|="unit"][data-coalition="red"] .unit-health-level,
|
||||
[data-object|="unit"][data-coalition="red"][data-has-fox-1] .unit-ammo>div:nth-child(1),
|
||||
[data-object|="unit"][data-coalition="red"][data-has-fox-2] .unit-ammo>div:nth-child(2),
|
||||
[data-object|="unit"][data-coalition="red"][data-has-fox-3] .unit-ammo>div:nth-child(3),
|
||||
@@ -260,7 +281,8 @@
|
||||
background-image: url("/resources/theme/images/states/idle.svg");
|
||||
}
|
||||
|
||||
[data-object*="groundunit"][data-state="idle"] .unit-state {
|
||||
[data-object*="groundunit"][data-state="idle"] .unit-state,
|
||||
[data-object*="navyunit"][data-state="idle"] .unit-state {
|
||||
background-image: url(""); /* To avoid clutter, dont show the idle state for non flying units */
|
||||
}
|
||||
|
||||
@@ -307,6 +329,19 @@
|
||||
background-image: url("/resources/theme/images/states/awacs.svg");
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-health::before {
|
||||
background-image: url("/resources/theme/images/icons/health.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
content: " ";
|
||||
height: 6px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
translate: -10px -2px;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
|
||||
/*** Dead unit ***/
|
||||
[data-object|="unit"][data-is-dead] .unit-selected-spotlight,
|
||||
@@ -316,6 +351,7 @@
|
||||
[data-object|="unit"][data-is-dead] .unit-hotgroup-id,
|
||||
[data-object|="unit"][data-is-dead] .unit-state,
|
||||
[data-object|="unit"][data-is-dead] .unit-fuel,
|
||||
[data-object|="unit"][data-is-dead] .unit-health,
|
||||
[data-object|="unit"][data-is-dead] .unit-ammo,
|
||||
[data-object|="unit"][data-is-dead]:hover .unit-fuel,
|
||||
[data-object|="unit"][data-is-dead]:hover .unit-ammo {
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
@import url("other/contextmenus.css");
|
||||
@import url("other/popup.css");
|
||||
@import url("other/toolbar.css");
|
||||
|
||||
|
||||
@import url("markers/airbase.css");
|
||||
@import url("markers/bullseye.css");
|
||||
|
||||
@@ -124,6 +124,28 @@
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.ol-tag {
|
||||
background-color: #FFFFFF11;
|
||||
color: #FFFFFFDD;
|
||||
border: 1px solid #FFFFFF55;
|
||||
font-weight: normal;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
/*
|
||||
.ol-tag-CA {
|
||||
background-color: #FF000022;
|
||||
}
|
||||
|
||||
.ol-tag-Radar {
|
||||
background-color: #00FF0022;
|
||||
}
|
||||
|
||||
.ol-tag-IR {
|
||||
background-color: #0000FF22;
|
||||
}
|
||||
*/
|
||||
|
||||
.unit-loadout-list {
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -225,13 +247,14 @@
|
||||
column-gap: 5px;
|
||||
}
|
||||
|
||||
.unit-label-count-container>div:nth-child(1) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
.unit-label-count-container button {
|
||||
display: flex !important;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.unit-label-count-container>div:nth-child(2) {
|
||||
font-size: large;
|
||||
.unit-label-count-container button>*:nth-child(1) {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.unit-loadout-preview {
|
||||
|
||||
74
client/public/stylesheets/other/toolbar.css
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
#primary-toolbar {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
#command-mode-toolbar {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#app-icon>.ol-select-options {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
#toolbar-summary {
|
||||
background-image: url("/images/icon-round.png");
|
||||
background-position: 20px 22px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 45px 45px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
text-indent: 60px;
|
||||
}
|
||||
|
||||
#toolbar-summary {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#toolbar-container>*:nth-child(2)>svg {
|
||||
display: none;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
#toolbar-container>*:nth-child(3)>svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 1145px) {
|
||||
#toolbar-container {
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
#toolbar-container>*:nth-child(1):not(:hover) {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
#toolbar-container>*:nth-child(1):not(:hover)>*:not(:first-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#toolbar-container>*:not(:first-child):not(:hover) {
|
||||
height: 52px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
|
||||
#toolbar-container>*:not(:first-child):not(:hover)>svg {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
filter: invert();
|
||||
}
|
||||
|
||||
#toolbar-container>*:not(:first-child):not(:hover)>*:not(:first-child) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,58 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
}
|
||||
|
||||
#unit-control-panel {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
column-gap: 10px;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
#unit-control-panel>div:nth-child(2),
|
||||
#unit-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
#unit-controls {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#unit-control-panel>div:nth-child(2) {
|
||||
width: 330px;
|
||||
}
|
||||
|
||||
#unit-control-panel>*:nth-child(1) {
|
||||
display: none;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 1145px) {
|
||||
#unit-control-panel>*:nth-child(1) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#unit-control-panel>*:nth-child(1) svg {
|
||||
display: flex;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
filter: invert(100%);
|
||||
}
|
||||
|
||||
#unit-control-panel:hover>*:nth-child(1) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#unit-control-panel:not(:hover) {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
#unit-control-panel:not(:hover)>*:nth-child(2),
|
||||
#unit-control-panel:not(:hover)>*:nth-child(3) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#unit-control-panel h3 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@@ -223,18 +270,32 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
}
|
||||
|
||||
#advanced-settings-div {
|
||||
position: relative;
|
||||
column-gap: 5px;
|
||||
display: flex;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
#advanced-settings-div>*:nth-child(2) {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#advanced-settings-div button {
|
||||
#advanced-settings-div>button {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#delete-options button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
#delete-options button svg {
|
||||
margin-right: 10px;
|
||||
width: 18px;
|
||||
max-height: 18px;
|
||||
}
|
||||
|
||||
/* Element visibility control */
|
||||
#unit-control-panel:not([data-show-categories-tooltip]) #categories-tooltip,
|
||||
#unit-control-panel:not([data-show-speed-slider]) #speed-slider,
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
#unit-info-panel>* {
|
||||
position: relative;
|
||||
min-height: 100px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
@media (min-width: 1525px) {
|
||||
#unit-info-panel>.panel-section {
|
||||
border-right: 1px solid #555;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
#unit-info-panel>.panel-section:first-child {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
#unit-info-panel>.panel-section:last-child {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
#unit-info-panel>.panel-section:last-of-type {
|
||||
border-right-width: 0;
|
||||
}
|
||||
#unit-info-panel>*:nth-child(1) {
|
||||
display: flex;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 6px;
|
||||
filter: invert(100%);
|
||||
}
|
||||
|
||||
@media (max-width: 1525px) {
|
||||
#unit-info-panel>.panel-section {
|
||||
border-bottom: 1px solid #555;
|
||||
padding: 30px 0px;
|
||||
}
|
||||
|
||||
#unit-info-panel>.panel-section:first-child {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
#unit-info-panel>.panel-section:last-child {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
#unit-info-panel>.panel-section:last-of-type {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
#unit-info-panel:hover>*:nth-child(1) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#unit-info-panel:not(:hover) {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
padding: 10px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#unit-info-panel:not(:hover)>*:not(:first-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#unit-info-panel>.panel-section {
|
||||
border-right: 1px solid #555;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
#unit-info-panel>.panel-section:first-of-type {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
#unit-info-panel>.panel-section:last-of-type{
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
#unit-info-panel>.panel-section:last-of-type {
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
#general {
|
||||
display: flex;
|
||||
@@ -49,6 +49,7 @@
|
||||
justify-content: space-between;
|
||||
row-gap: 4px;
|
||||
position: relative;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
#unit-label {
|
||||
@@ -63,6 +64,10 @@
|
||||
#unit-name {
|
||||
margin-bottom: 4px;
|
||||
padding: 0px 0;
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#current-task {
|
||||
@@ -87,6 +92,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
#loadout-silhouette {
|
||||
@@ -101,9 +107,9 @@
|
||||
column-gap: 8px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
max-height: 108px;
|
||||
padding-right:40px;
|
||||
height: 100px;
|
||||
row-gap: 6px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#loadout-items>* {
|
||||
|
||||
@@ -88,7 +88,7 @@ form {
|
||||
}
|
||||
|
||||
.ol-scrollable {
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
scrollbar-color: white transparent;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
@@ -339,10 +339,32 @@ h4 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
button.ol-button-white {
|
||||
border: 1px solid white;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button.ol-button-white>svg:first-child {
|
||||
stroke: white;
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.ol-select-warning {
|
||||
border: 1px solid var(--primary-red);
|
||||
color: var(--primary-red) !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.ol-select-warning::after {
|
||||
stroke: var(--primary-red);
|
||||
fill: var(--primary-red);
|
||||
}
|
||||
|
||||
button.ol-button-warning {
|
||||
border: 1px solid var(--primary-red);
|
||||
color: var(--primary-red);
|
||||
font-weight: bold;
|
||||
color: var(--primary-red) !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
button.ol-button-warning>svg:first-child {
|
||||
@@ -714,11 +736,8 @@ nav.ol-panel> :last-child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 5px;
|
||||
position: absolute;
|
||||
height: fit-content;
|
||||
width: fit-content;
|
||||
left: calc(100% + 10px);
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
#rapid-controls button {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
|
||||
|
After Width: | Height: | Size: 500 B |
1
client/public/themes/olympus/images/icons/fire-solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M159.3 5.4c7.8-7.3 19.9-7.2 27.7 .1c27.6 25.9 53.5 53.8 77.7 84c11-14.4 23.5-30.1 37-42.9c7.9-7.4 20.1-7.4 28 .1c34.6 33 63.9 76.6 84.5 118c20.3 40.8 33.8 82.5 33.8 111.9C448 404.2 348.2 512 224 512C98.4 512 0 404.1 0 276.5c0-38.4 17.8-85.3 45.4-131.7C73.3 97.7 112.7 48.6 159.3 5.4zM225.7 416c25.3 0 47.7-7 68.8-21c42.1-29.4 53.4-88.2 28.1-134.4c-4.5-9-16-9.6-22.5-2l-25.2 29.3c-6.6 7.6-18.5 7.4-24.7-.5c-16.5-21-46-58.5-62.8-79.8c-6.3-8-18.3-8.1-24.7-.1c-33.8 42.5-50.8 69.3-50.8 99.4C112 375.4 162.6 416 225.7 416z"/></svg>
|
||||
|
After Width: | Height: | Size: 765 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M192 64C86 64 0 150 0 256S86 448 192 448H448c106 0 192-86 192-192s-86-192-192-192H192zM496 168a40 40 0 1 1 0 80 40 40 0 1 1 0-80zM392 304a40 40 0 1 1 80 0 40 40 0 1 1 -80 0zM168 200c0-13.3 10.7-24 24-24s24 10.7 24 24v32h32c13.3 0 24 10.7 24 24s-10.7 24-24 24H216v32c0 13.3-10.7 24-24 24s-24-10.7-24-24V280H136c-13.3 0-24-10.7-24-24s10.7-24 24-24h32V200z"/></svg>
|
||||
|
After Width: | Height: | Size: 601 B |
4
client/public/themes/olympus/images/icons/health.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="3" y1="4.37115e-08" x2="3" y2="6.00083" stroke="white" stroke-width="2"/>
|
||||
<line x1="6" y1="3" y2="3" stroke="white" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 244 B |
41
client/public/themes/olympus/images/icons/napalm.svg
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 576 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="napalm.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="251.94759"
|
||||
inkscape:cy="244.11502"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 64,448 v 0 h 448 v 0 h 32 c 17.7,0 32,14.3 32,32 0,17.7 -14.3,32 -32,32 H 32 C 14.3,512 0,497.7 0,480 0,462.3 14.3,448 32,448 Z"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="ccccssssssc" />
|
||||
<path
|
||||
d="m 233.26314,56.434762 c 5.55746,-5.201213 14.17865,-5.129964 19.73611,0.07125 19.66487,18.453622 38.11849,38.332232 55.36087,59.849578 7.83744,-10.25993 16.74363,-21.446096 26.36232,-30.566036 5.62871,-5.27246 14.32115,-5.27246 19.94986,0.0713 24.65233,23.512336 45.52843,54.577126 60.20583,84.074416 14.46365,29.0698 24.08233,58.78084 24.08233,79.7282 0,90.91437 -71.107,167.72133 -159.59889,167.72133 -89.48938,0 -159.59889,-76.87821 -159.59889,-167.79258 0,-27.35981 12.68241,-60.77583 32.34727,-93.8356 19.87862,-33.55852 47.95092,-68.542026 81.15319,-99.321808 z m 47.30967,292.550468 c 18.02613,0 33.98602,-4.98747 49.01966,-14.9624 29.99604,-20.94735 38.04724,-62.84206 20.02111,-95.75933 -3.20623,-6.41246 -11.39992,-6.83996 -16.03114,-1.42499 l -17.95487,20.8761 c -4.70247,5.41496 -13.18116,5.27247 -17.59863,-0.35625 -11.75617,-14.96239 -32.77477,-41.68096 -44.74469,-56.8571 -4.48872,-5.69996 -13.03866,-5.77121 -17.59863,-0.0713 -24.08233,30.28104 -36.19474,49.37591 -36.19474,70.82201 0.0712,48.80591 36.1235,77.73321 81.08193,77.73321 z"
|
||||
id="path1209"
|
||||
style="stroke-width:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M246.9 14.1C234 15.2 224 26 224 39c0 13.8 11.2 25 25 25H400c8.8 0 16-7.2 16-16V17.4C416 8 408 .7 398.7 1.4L246.9 14.1zM240 112c0 44.2 35.8 80 80 80s80-35.8 80-80c0-5.5-.6-10.8-1.6-16H241.6c-1 5.2-1.6 10.5-1.6 16zM72 224c-22.1 0-40 17.9-40 40s17.9 40 40 40H224v89.4L386.8 230.5c-13.3-4.3-27.3-6.5-41.6-6.5H240 72zm345.7 20.9L246.6 416H416V369.7l53.6 90.6c11.2 19 35.8 25.3 54.8 14.1s25.3-35.8 14.1-54.8L462.3 290.8c-11.2-18.9-26.6-34.5-44.6-45.9zM224 448v32c0 17.7 14.3 32 32 32H384c17.7 0 32-14.3 32-32V448H224z"/></svg>
|
||||
|
After Width: | Height: | Size: 759 B |
49
client/public/themes/olympus/images/icons/secondaries.svg
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 576 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="secondaries.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="228.44988"
|
||||
inkscape:cy="281.53728"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 64,448 v 0 h 448 v 0 h 32 c 17.7,0 32,14.3 32,32 0,17.7 -14.3,32 -32,32 H 32 C 14.3,512 0,497.7 0,480 0,462.3 14.3,448 32,448 Z"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="ccccssssssc" />
|
||||
<path
|
||||
d="m 278.72676,199.54168 c 3.72808,-5.9538 11.40681,-8.06823 17.63883,-4.72965 6.23202,3.33858 8.79159,10.85038 5.89816,17.24933 l -76.28657,169.59989 c 1.22415,1.27979 2.39265,2.61522 3.50551,3.95065 l 54.08501,-30.43672 c 5.84251,-3.28294 13.13175,-1.72494 17.19369,3.56115 4.06194,5.28608 3.50551,12.79789 -1.22415,17.52755 l -48.40942,48.40941 h -39.78475 c -7.34487,-20.75484 -27.09814,-35.61152 -50.35692,-35.61152 -23.25878,0 -43.06769,14.85668 -50.35692,35.61152 H 66.281757 l -42.01048,-29.04565 c -5.39737,-3.72808 -7.28923,-10.90603 -4.39579,-16.85983 2.89343,-5.9538 9.68188,-8.84724 15.96954,-6.89973 l 54.085,16.91547 c 1.66929,-2.17007 3.39422,-4.28451 5.23044,-6.28766 l -34.66559,-57.75744 c -3.39422,-5.61994 -2.17008,-12.85353 2.8378,-17.0824 5.00787,-4.22887 12.35274,-4.17323 17.30497,0.0556 l 56.978443,48.9102 c 0.83464,-0.22257 1.66929,-0.44514 2.50393,-0.61207 l 7.56745,-79.40257 c 0.66772,-6.84409 6.39895,-12.07453 13.29868,-12.07453 6.89973,0 12.63096,5.23044 13.29868,12.07453 l 7.51181,78.95743 z m -117.74061,-6.28766 c 7.40052,0 13.35432,5.9538 13.35432,13.35432 v 26.70865 c 0,7.40052 -5.9538,13.35432 -13.35432,13.35432 -7.40052,0 -13.35432,-5.9538 -13.35432,-13.35432 v -26.70865 c 0,-7.40052 5.9538,-13.35432 13.35432,-13.35432 z"
|
||||
id="path850"
|
||||
style="stroke-width:0.999999" />
|
||||
<path
|
||||
d="m 505.67595,141.18773 c 2.83997,-4.53547 8.68946,-6.1462 13.43688,-3.60294 4.74742,2.54326 6.69725,8.26558 4.49309,13.14017 L 465.49246,279.9225 c 0.93253,0.97491 1.82267,1.99221 2.67042,3.00952 l 41.20079,-23.18604 c 4.4507,-2.50087 10.00349,-1.31402 13.09779,2.71281 3.0943,4.02682 2.67042,9.74915 -0.93253,13.35211 l -36.87726,36.87724 h -30.30716 c -5.59516,-15.81059 -20.64278,-27.12809 -38.36082,-27.12809 -17.71803,0 -32.80803,11.3175 -38.36081,27.12809 h -33.78295 l -32.00268,-22.12635 c -4.1116,-2.83997 -5.55278,-8.30798 -3.34862,-12.84345 2.20416,-4.53548 7.37545,-6.73964 12.16526,-5.25607 l 41.20078,12.88584 c 1.27163,-1.65311 2.58565,-3.26385 3.98444,-4.7898 l -26.4075,-43.99838 c -2.58564,-4.28115 -1.65312,-9.79154 2.16177,-13.013 3.81489,-3.22146 9.41006,-3.17908 13.18256,0.0423 l 43.40494,37.25874 c 0.63582,-0.16955 1.27163,-0.3391 1.90744,-0.46626 l 5.76473,-60.48717 c 0.50865,-5.21368 4.87458,-9.19811 10.13064,-9.19811 5.25607,0 9.622,3.98443 10.13065,9.19811 l 5.72234,60.14807 z m -89.69226,-4.7898 c 5.63756,0 10.17304,4.53548 10.17304,10.17303 v 20.34608 c 0,5.63755 -4.53548,10.17303 -10.17304,10.17303 -5.63755,0 -10.17303,-4.53548 -10.17303,-10.17303 v -20.34608 c 0,-5.63755 4.53548,-10.17303 10.17303,-10.17303 z"
|
||||
id="path850-7"
|
||||
style="stroke-width:1" />
|
||||
<path
|
||||
d="m 265.48894,18.393218 c 2.4073,-3.844498 7.36562,-5.209832 11.38977,-3.054038 4.02416,2.155794 5.67693,7.006327 3.80858,11.138268 l -49.2599,109.514342 c 0.79046,0.82639 1.54498,1.6887 2.26358,2.55102 l 34.92387,-19.65365 c 3.77263,-2.11987 8.47946,-1.11384 11.10234,2.29951 2.62288,3.41333 2.26358,8.26387 -0.79046,11.31792 L 247.6677,163.7656 h -25.68988 c -4.74274,-13.40185 -17.49786,-22.99514 -32.51656,-22.99514 -15.0187,0 -27.80974,9.59329 -32.51656,22.99514 h -28.63613 l -27.12708,-18.75541 c -3.485195,-2.4073 -4.70681,-7.04226 -2.838453,-10.88676 1.868353,-3.8445 6.251803,-5.71285 10.311883,-4.45531 l 34.92386,10.92269 c 1.07789,-1.40126 2.19172,-2.7666 3.37741,-4.06008 L 124.57186,99.235499 c -2.19172,-3.628918 -1.40127,-8.299804 1.83243,-11.030478 3.23369,-2.730673 7.97643,-2.694745 11.1742,0.0359 l 36.79222,31.582379 c 0.53894,-0.14372 1.07789,-0.28743 1.61684,-0.39522 l 4.88646,-51.271971 c 0.43117,-4.419378 4.13195,-7.796787 8.58725,-7.796787 4.45531,0 8.15609,3.377409 8.58725,7.796787 l 4.85054,50.984531 z m -76.02768,-4.060079 c 4.77868,0 8.62318,3.844499 8.62318,8.623175 v 17.246357 c 0,4.778677 -3.8445,8.623176 -8.62318,8.623176 -4.77868,0 -8.62317,-3.844499 -8.62317,-8.623176 V 22.956314 c 0,-4.778676 3.84449,-8.623175 8.62317,-8.623175 z"
|
||||
id="path850-6"
|
||||
style="stroke-width:0.999997" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
1
client/public/themes/olympus/images/icons/smog-solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M32 144c0 79.5 64.5 144 144 144H299.3c22.6 19.9 52.2 32 84.7 32s62.1-12.1 84.7-32H496c61.9 0 112-50.1 112-112s-50.1-112-112-112c-10.7 0-21 1.5-30.8 4.3C443.8 27.7 401.1 0 352 0c-32.6 0-62.4 12.2-85.1 32.3C242.1 12.1 210.5 0 176 0C96.5 0 32 64.5 32 144zM616 368H280c-13.3 0-24 10.7-24 24s10.7 24 24 24H616c13.3 0 24-10.7 24-24s-10.7-24-24-24zm-64 96H440c-13.3 0-24 10.7-24 24s10.7 24 24 24H552c13.3 0 24-10.7 24-24s-10.7-24-24-24zm-192 0H24c-13.3 0-24 10.7-24 24s10.7 24 24 24H360c13.3 0 24-10.7 24-24s-10.7-24-24-24zM224 392c0-13.3-10.7-24-24-24H96c-13.3 0-24 10.7-24 24s10.7 24 24 24H200c13.3 0 24-10.7 24-24z"/></svg>
|
||||
|
After Width: | Height: | Size: 858 B |
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 576 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="white-phosphorous.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.625"
|
||||
inkscape:cx="237.84615"
|
||||
inkscape:cy="323.38462"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 499.6,11.3 c 6.7,-10.7 20.5,-14.5 31.7,-8.5 11.2,6 15.8,19.5 10.6,31 L 404.8,338.6 c 2.2,2.3 4.3,4.7 6.3,7.1 L 508.3,291 c 10.5,-5.9 23.6,-3.1 30.9,6.4 7.3,9.5 6.3,23 -2.2,31.5 l -87,87 h -71.5 c -13.2,-37.3 -48.7,-64 -90.5,-64 -41.8,0 -77.4,26.7 -90.5,64 H 117.8 L 42.3,363.7 C 32.6,357 29.2,344.1 34.4,333.4 39.6,322.7 51.8,317.5 63.1,321 l 97.2,30.4 c 3,-3.9 6.1,-7.7 9.4,-11.3 L 107.4,236.3 c -6.1,-10.1 -3.9,-23.1 5.1,-30.7 9,-7.6 22.2,-7.5 31.1,0.1 L 246,293.6 c 1.5,-0.4 3,-0.8 4.5,-1.1 l 13.6,-142.7 c 1.2,-12.3 11.5,-21.7 23.9,-21.7 12.4,0 22.7,9.4 23.9,21.7 l 13.5,141.9 z M 288,0 c 13.3,0 24,10.7 24,24 v 48 c 0,13.3 -10.7,24 -24,24 -13.3,0 -24,-10.7 -24,-24 V 24 C 264,10.7 274.7,0 288,0 Z"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="csccccssccscccsccccsccccscccsssssss"
|
||||
style="fill:#f9ffff;fill-opacity:1;opacity:0.41025641" />
|
||||
<path
|
||||
d="m 64.847242,449.52266 v 0 H 512.84724 v 0 h 32 c 17.7,0 32,14.3 32,32 0,17.7 -14.3,32 -32,32 H 32.847242 c -17.7,0 -31.99999998,-14.3 -31.99999998,-32 0,-17.7 14.29999998,-32 31.99999998,-32 z"
|
||||
id="path965" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="50" height="50" version="1.1" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m45.773 41.342-19.825-33.703c-0.4253-0.72303-1.4709-0.72305-1.8962 0l-19.825 33.703c-0.43135 0.7333 0.09738 1.6577 0.94813 1.6577h39.65c0.8507 0 1.3794-0.9244 0.9481-1.6577z" fill="#3BB9FF" stroke-width="2"/>
|
||||
<text x="21.590775" y="28.910091" fill="#082e44" font-family="sans-serif" font-size="8.838px" font-weight="bold" stroke="#082e44" stroke-width=".514" style="line-height:1.25" xml:space="preserve"><tspan x="21.590775" y="28.910091" font-family="sans-serif" font-size="8.838px" font-weight="bold" stroke="#082e44" stroke-width=".514">A</tspan></text>
|
||||
<path d="M6.74842 41L25 9.97231L43.2516 41H6.74842Z" fill="none" stroke="#082E44" stroke-width="2"/>
|
||||
<text x="16.189293" y="36.063168" fill="#082e44" font-family="sans-serif" font-size="8.838px" font-weight="bold" stroke="#082e44" stroke-width=".514" style="line-height:1.25" xml:space="preserve"><tspan x="16.189293" y="36.063168" font-family="sans-serif" font-size="8.838px" font-weight="bold" stroke="#082e44" stroke-width=".514">A</tspan></text>
|
||||
<text x="27.098772" y="36.063168" fill="#082e44" font-family="sans-serif" font-size="8.838px" font-weight="bold" stroke="#082e44" stroke-width=".514" style="line-height:1.25" xml:space="preserve"><tspan x="27.098772" y="36.063168" font-family="sans-serif" font-size="8.838px" font-weight="bold" stroke="#082e44" stroke-width=".514">A</tspan></text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="50" height="50" version="1.1" viewBox="0 0 50 50" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect transform="rotate(45 25 2.3724)" x="25" y="2.3724" width="32" height="32" rx="1.1" fill="white" stroke-width="2"/><rect transform="rotate(45 25 5.2008)" x="25" y="5.2008" width="28" height="28" fill="none" stroke="#082e44" stroke-width="2"/><path d="m25 14.598c0.71918 0 1.3002 0.58104 1.3002 1.3002v0.42257c3.8072 0.56478 6.814 3.5756 7.3787 7.3787h0.42257c0.71918 0 1.3002 0.58104 1.3002 1.3002s-0.58104 1.3002-1.3002 1.3002h-0.42257c-0.56478 3.8072-3.5756 6.814-7.3787 7.3787v0.42257c0 0.71918-0.58104 1.3002-1.3002 1.3002s-1.3002-0.58104-1.3002-1.3002v-0.42257c-3.8072-0.56478-6.814-3.5715-7.3787-7.3787h-0.42257c-0.71918 0-1.3002-0.58104-1.3002-1.3002s0.58104-1.3002 1.3002-1.3002h0.42257c0.56478-3.8072 3.5715-6.814 7.3787-7.3787v-0.42257c0-0.71918 0.58104-1.3002 1.3002-1.3002zm-6.0379 11.702c0.5079 2.3688 2.3729 4.2298 4.7377 4.7377v-0.83702c0-0.71918 0.58104-1.3002 1.3002-1.3002s1.3002 0.58104 1.3002 1.3002v0.83702c2.3688-0.5079 4.2298-2.3729 4.7377-4.7377h-0.83702c-0.71918 0-1.3002-0.58104-1.3002-1.3002s0.58104-1.3002 1.3002-1.3002h0.83702c-0.5079-2.3688-2.3688-4.2298-4.7377-4.7377v0.83702c0 0.71918-0.58104 1.3002-1.3002 1.3002s-1.3002-0.58104-1.3002-1.3002v-0.83702c-2.3688 0.5079-4.2298 2.3688-4.7377 4.7377h0.83702c0.71918 0 1.3002 0.58104 1.3002 1.3002s-0.58104 1.3002-1.3002 1.3002zm6.0379-2.6004a1.3002 1.3002 0 1 1 0 2.6004 1.3002 1.3002 0 1 1 0-2.6004z" fill="#082e44" stroke="#082e44" stroke-width=".040632"/></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="50" height="50" version="1.1" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect transform="rotate(45 25 2.3724)" x="25" y="2.3724" width="32" height="32" rx="1.1" fill="white" stroke-width="2"/>
|
||||
<rect transform="rotate(45 25 5.2008)" x="25" y="5.2008" width="28" height="28" fill="none" stroke="#082E44" stroke-width="2"/>
|
||||
<path d="m24.581 23.186c0.72013 0 1.4119 0.2013 2.0073 0.56419v8.5083h-5.4435v-4.9416l-1.5196 2.5686c-0.31753 0.53868-1.015 0.71729-1.5537 0.39976-0.53868-0.31754-0.71729-1.015-0.39975-1.5537l2.1604-3.6517c0.69461-1.1737 1.9562-1.8939 3.3199-1.8939zm-2.9826-3.1754a2.2681 2.2681 0 1 1 4.5362 0 2.2681 2.2681 0 1 1-4.5362 0zm8.1652-2.2681c0.24949 0 0.45362 0.20413 0.45362 0.45362v3.2973c0.27218 0.15594 0.45362 0.45079 0.45362 0.78533v3.0988l0.45362-0.15026v-1.5877c0-0.24949 0.20413-0.45362 0.45362-0.45362h0.45362c0.24949 0 0.45362 0.20413 0.45362 0.45362v2.3957c0 0.19562-0.12474 0.36857-0.30903 0.43094l-1.5055 0.49898v0.75698h1.3609c0.24949 0 0.45362 0.20413 0.45362 0.45362v0.45362c0 0.24949-0.20413 0.45362-0.45362 0.45362h-1.2475l0.65208 2.6112c0.07088 0.28635-0.1446 0.56419-0.43945 0.56419h-1.6869c-0.24949 0-0.45362-0.20413-0.45362-0.45362v-2.7217h-0.45362c-0.50182 0-0.90724-0.40542-0.90724-0.90724v-4.0826c0-0.50182 0.40542-0.90724 0.90724-0.90724v-0.90725c0-0.33454 0.18145-0.6294 0.45362-0.78533v-2.8436c-0.24949 0-0.45362-0.20413-0.45362-0.45362 0-0.2495 0.20413-0.45362 0.45362-0.45362h0.45362z" fill="none" stroke="#082e44"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="50" height="50" version="1.1" viewBox="0 0 50 50" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect transform="rotate(45 25 2.3724)" x="25" y="2.3724" width="32" height="32" rx="1.1" fill="white" stroke-width="2"/><rect transform="rotate(45 25 5.2008)" x="25" y="5.2008" width="28" height="28" fill="none" stroke="#082E44" stroke-width="2"/><path d="m22.431 22.053-0.55497 1.5863h6.2472l-0.55497-1.5863c-0.09568-0.27217-0.35297-0.45504-0.64216-0.45504h-3.8529c-0.28918 0-0.54647 0.18287-0.64216 0.45504zm-2.0328 1.6883 0.74847-2.137c0.28706-0.81864 1.0589-1.3672 1.9265-1.3672h3.8529c0.86754 0 1.6394 0.5486 1.9265 1.3672l0.74847 2.137c0.49331 0.20412 0.84203 0.69106 0.84203 1.2588v4.0826c0 0.37636-0.30407 0.68042-0.68043 0.68042h-0.68042c-0.37636 0-0.68043-0.30407-0.68043-0.68042v-1.0206h-6.8043v1.0206c0 0.37636-0.30407 0.68042-0.68043 0.68042h-0.68042c-0.37636 0-0.68043-0.30407-0.68043-0.68042v-4.0826c0-0.56773 0.34872-1.0547 0.84203-1.2588zm1.8797 1.9392a0.68043 0.68043 0 1 0-1.3609 0 0.68043 0.68043 0 1 0 1.3609 0zm6.1239 0.68043a0.68043 0.68043 0 1 0 0-1.3609 0.68043 0.68043 0 1 0 0 1.3609z" fill="none" stroke="#082e44"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="50" height="50" version="1.1" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect transform="rotate(45 25 2.3724)" x="25" y="2.3724" width="32" height="32" rx="1.1" fill="white" stroke-width="2"/>
|
||||
<rect transform="rotate(45 25 5.2008)" x="25" y="5.2008" width="28" height="28" fill="none" stroke="#082E44" stroke-width="2"/>
|
||||
<path d="m19.209 19.55c-0.56415 0-1.0219 0.45771-1.0219 1.0219v6.8124c0 0.56415 0.45771 1.0219 1.0219 1.0219h0.34062c0 1.1283 0.91541 2.0437 2.0437 2.0437 1.1283 0 2.0437-0.91541 2.0437-2.0437h2.725c0 1.1283 0.91542 2.0437 2.0437 2.0437 1.1283 0 2.0437-0.91541 2.0437-2.0437h0.68124c0.37681 0 0.68124-0.30443 0.68124-0.68124s-0.30443-0.68124-0.68124-0.68124v-2.4418c0-0.36191-0.14264-0.70892-0.3981-0.96438l-1.6456-1.6456c-0.25546-0.25546-0.60247-0.3981-0.96438-0.3981h-1.0793v-1.0219c0-0.56415-0.45771-1.0219-1.0219-1.0219zm7.8342 3.4062h1.0793l1.6456 1.6456v0.3981h-2.725zm-6.4718 5.4499a1.0219 1.0219 0 1 1 2.0437 0 1.0219 1.0219 0 1 1-2.0437 0zm7.8342-1.0219a1.0219 1.0219 0 1 1 0 2.0437 1.0219 1.0219 0 1 1 0-2.0437z" fill="none" stroke="#082e44"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 447 B After Width: | Height: | Size: 447 B |
@@ -67,6 +67,12 @@
|
||||
--unit-height: 50px;
|
||||
--unit-width: 50px;
|
||||
|
||||
--unit-health-border-width: 2px;
|
||||
--unit-health-height: 6px;
|
||||
--unit-health-width: 36px;
|
||||
--unit-health-x: 0px;
|
||||
--unit-health-y: 26px;
|
||||
|
||||
/*** Air units ***/
|
||||
--unit-ammo-gap: calc(2px + var(--unit-stroke-width));
|
||||
--unit-ammo-border-radius: 50%;
|
||||
@@ -81,4 +87,4 @@
|
||||
--unit-fuel-x: 0px;
|
||||
--unit-fuel-y: 22px;
|
||||
--unit-vvi-width: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,6 +259,7 @@ export enum DataIndexes {
|
||||
operateAs,
|
||||
shotsScatter,
|
||||
shotsIntensity,
|
||||
health,
|
||||
endOfData = 255
|
||||
};
|
||||
|
||||
@@ -269,4 +270,6 @@ export const MGRS_PRECISION_10M = 5;
|
||||
export const MGRS_PRECISION_1M = 6;
|
||||
|
||||
export const DELETE_CYCLE_TIME = 0.05;
|
||||
export const DELETE_SLOW_THRESHOLD = 50;
|
||||
export const DELETE_SLOW_THRESHOLD = 50;
|
||||
|
||||
export const GROUPING_ZOOM_TRANSITION = 13;
|
||||
@@ -58,7 +58,7 @@ export class MapContextMenu extends ContextMenu {
|
||||
|
||||
document.addEventListener("contextMenuExplosion", (e: any) => {
|
||||
this.hide();
|
||||
getApp().getServerManager().spawnExplosion(e.detail.strength, this.getLatLng());
|
||||
getApp().getServerManager().spawnExplosion(e.detail.strength ?? 0, e.detail.explosionType, this.getLatLng());
|
||||
});
|
||||
|
||||
document.addEventListener("editCoalitionArea", (e: any) => {
|
||||
|
||||
@@ -36,18 +36,37 @@ export class Dropdown {
|
||||
return this.#container;
|
||||
}
|
||||
|
||||
setOptions(optionsList: string[], sort:""|"string"|"number" = "string") {
|
||||
|
||||
if ( sort === "number" ) {
|
||||
this.#optionsList = optionsList.sort( (optionA:string, optionB:string) => {
|
||||
const a = parseInt( optionA );
|
||||
const b = parseInt( optionB );
|
||||
if ( a > b )
|
||||
setOptions(optionsList: string[], sort: "" | "string" | "number" | "string+number" = "string") {
|
||||
if (sort === "number") {
|
||||
this.#optionsList = optionsList.sort((optionA: string, optionB: string) => {
|
||||
const a = parseInt(optionA);
|
||||
const b = parseInt(optionB);
|
||||
if (a > b)
|
||||
return 1;
|
||||
else
|
||||
return ( b > a ) ? -1 : 0;
|
||||
return (b > a) ? -1 : 0;
|
||||
});
|
||||
} else if ( sort === "string" ) {
|
||||
} else if (sort === "string+number") {
|
||||
this.#optionsList = optionsList.sort((optionA: string, optionB: string) => {
|
||||
var regex = /\d+/g;
|
||||
var matchesA = optionA.match(regex);
|
||||
var matchesB = optionB.match(regex);
|
||||
if ((matchesA != null && matchesA?.length > 0) && (matchesB != null && matchesB?.length > 0) && optionA[0] == optionB[0]) {
|
||||
const a = parseInt(matchesA[0] ?? 0);
|
||||
const b = parseInt(matchesB[0] ?? 0);
|
||||
if (a > b)
|
||||
return 1;
|
||||
else
|
||||
return (b > a) ? -1 : 0;
|
||||
} else {
|
||||
if (optionA > optionB)
|
||||
return 1;
|
||||
else
|
||||
return (optionB > optionA) ? -1 : 0;
|
||||
}
|
||||
|
||||
});
|
||||
} else if (sort === "string") {
|
||||
this.#optionsList = optionsList.sort();
|
||||
}
|
||||
|
||||
@@ -169,17 +188,17 @@ export class Dropdown {
|
||||
}
|
||||
|
||||
#toggle() {
|
||||
this.#container.classList.contains("is-open")? this.close(): this.open();
|
||||
this.#container.classList.contains("is-open") ? this.close() : this.open();
|
||||
}
|
||||
|
||||
#createElement(defaultText: string | undefined) {
|
||||
var div = document.createElement("div");
|
||||
div.classList.add("ol-select");
|
||||
|
||||
|
||||
var value = document.createElement("div");
|
||||
value.classList.add("ol-select-value");
|
||||
value.innerText = defaultText? defaultText: "";
|
||||
|
||||
value.innerText = defaultText ? defaultText : "";
|
||||
|
||||
var options = document.createElement("div");
|
||||
options.classList.add("ol-select-options");
|
||||
|
||||
|
||||
@@ -154,9 +154,28 @@ export class UnitSpawnMenu {
|
||||
this.#unitLiveryDropdown.reset();
|
||||
|
||||
if (this.#orderByRole)
|
||||
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByRole(this.spawnOptions.roleType).map((blueprint) => { return blueprint.label }));
|
||||
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByRole(this.spawnOptions.roleType).map((blueprint) => { return blueprint.label }), "string+number");
|
||||
else
|
||||
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByType(this.spawnOptions.roleType).map((blueprint) => { return blueprint.label }));
|
||||
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByType(this.spawnOptions.roleType).map((blueprint) => { return blueprint.label }), "string+number");
|
||||
|
||||
/* Add the tags to the options */
|
||||
var elements: HTMLElement[] = [];
|
||||
for (let idx = 0; idx < this.#unitLabelDropdown.getOptionElements().length; idx++) {
|
||||
let element = this.#unitLabelDropdown.getOptionElements()[idx] as HTMLElement;
|
||||
let entry = this.#unitDatabase.getByLabel(element.textContent ?? "");
|
||||
if (entry) {
|
||||
element.querySelectorAll("button")[0]?.append(...(entry.tags?.split(",").map((tag: string) => {
|
||||
tag = tag.trim();
|
||||
let el = document.createElement("div");
|
||||
el.classList.add("pill", `ol-tag`, `ol-tag-${tag.replace(/[\W_]+/g,"-")}`);
|
||||
el.textContent = tag;
|
||||
element.appendChild(el);
|
||||
return el;
|
||||
}) ?? []));
|
||||
elements.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
this.#container.dispatchEvent(new Event("resize"));
|
||||
|
||||
this.spawnOptions.name = "";
|
||||
|
||||
@@ -86,6 +86,7 @@ export interface UnitSpawnTable {
|
||||
export interface ObjectIconOptions {
|
||||
showState: boolean,
|
||||
showVvi: boolean,
|
||||
showHealth: boolean,
|
||||
showHotgroup: boolean,
|
||||
showUnitIcon: boolean,
|
||||
showShortLabel: boolean,
|
||||
@@ -182,6 +183,7 @@ export interface UnitData {
|
||||
operateAs: string;
|
||||
shotsScatter: number;
|
||||
shotsIntensity: number;
|
||||
health: number;
|
||||
}
|
||||
|
||||
export interface LoadoutItemBlueprint {
|
||||
@@ -219,6 +221,7 @@ export interface UnitBlueprint {
|
||||
shotsBaseScatter?: number;
|
||||
description?: string;
|
||||
abilities?: string;
|
||||
tags?: string;
|
||||
acquisitionRange?: number;
|
||||
engagementRange?: number;
|
||||
targetingRange?: number;
|
||||
@@ -228,6 +231,8 @@ export interface UnitBlueprint {
|
||||
canRearm?: boolean;
|
||||
canAAA?: boolean;
|
||||
indirectFire?: boolean;
|
||||
markerFile?: string;
|
||||
unitWhenGrouped?: string;
|
||||
}
|
||||
|
||||
export interface UnitSpawnOptions {
|
||||
|
||||
@@ -68,6 +68,7 @@ export class Map extends L.Map {
|
||||
#temporaryMarkers: TemporaryUnitMarker[] = [];
|
||||
#selecting: boolean = false;
|
||||
#isZooming: boolean = false;
|
||||
#previousZoom: number = 0;
|
||||
|
||||
#destinationGroupRotation: number = 0;
|
||||
#computeDestinationRotation: boolean = false;
|
||||
@@ -102,8 +103,6 @@ export class Map extends L.Map {
|
||||
constructor(ID: string){
|
||||
/* Init the leaflet map */
|
||||
super(ID, {
|
||||
zoomSnap: 0,
|
||||
zoomDelta: 0.25,
|
||||
preferCanvas: true,
|
||||
doubleClickZoom: false,
|
||||
zoomControl: false,
|
||||
@@ -503,6 +502,10 @@ export class Map extends L.Map {
|
||||
return this.#visibilityOptions;
|
||||
}
|
||||
|
||||
getPreviousZoom() {
|
||||
return this.#previousZoom;
|
||||
}
|
||||
|
||||
/* Event handlers */
|
||||
#onClick(e: any) {
|
||||
if (!this.#preventLeftClick) {
|
||||
@@ -703,6 +706,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
#onZoomStart(e: any) {
|
||||
this.#previousZoom = this.getZoom();
|
||||
if (this.#centerUnit != null)
|
||||
this.#panToUnit(this.#centerUnit);
|
||||
this.#isZooming = true;
|
||||
|
||||
@@ -36,6 +36,7 @@ export class TemporaryUnitMarker extends CustomMarker {
|
||||
|
||||
createIcon() {
|
||||
const category = getMarkerCategoryByName(this.#name);
|
||||
const databaseEntry = getUnitDatabaseByCategory(category)?.getByName(this.#name);
|
||||
|
||||
/* Set the icon */
|
||||
var icon = new DivIcon({
|
||||
@@ -54,7 +55,8 @@ export class TemporaryUnitMarker extends CustomMarker {
|
||||
var unitIcon = document.createElement("div");
|
||||
unitIcon.classList.add("unit-icon");
|
||||
var img = document.createElement("img");
|
||||
img.src = `/resources/theme/images/units/${category}.svg`;
|
||||
|
||||
img.src = `/resources/theme/images/units/${databaseEntry?.markerFile ?? category}.svg`;
|
||||
img.onload = () => SVGInjector(img);
|
||||
unitIcon.appendChild(img);
|
||||
unitIcon.toggleAttribute("data-rotate-to-heading", false);
|
||||
@@ -64,7 +66,7 @@ export class TemporaryUnitMarker extends CustomMarker {
|
||||
if (category == "aircraft" || category == "helicopter") {
|
||||
var shortLabel = document.createElement("div");
|
||||
shortLabel.classList.add("unit-short-label");
|
||||
shortLabel.innerText = getUnitDatabaseByCategory(category)?.getByName(this.#name)?.shortLabel || "";
|
||||
shortLabel.innerText = databaseEntry?.shortLabel || "";
|
||||
el.append(shortLabel);
|
||||
}
|
||||
|
||||
|
||||
56
client/src/map/rangecircle.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
// @ts-nocheck
|
||||
// This is a horrible hack. But it is needed at the moment to ovveride a default behaviour of Leaflet. TODO please fix me the proper way.
|
||||
|
||||
import { Circle, Point, Polyline } from 'leaflet';
|
||||
|
||||
/**
|
||||
* This custom Circle object implements a faster render method for very big circles. When zoomed in, the default ctx.arc method
|
||||
* is very slow since the circle is huge. Also, when zoomed in most of the circle points will be outside the screen and not needed. This
|
||||
* simpler, faster renderer approximates the circle with line segements and only draws those currently visibile.
|
||||
* A more refined version using arcs could be implemented but this works good enough.
|
||||
*/
|
||||
export class RangeCircle extends Circle {
|
||||
_updatePath() {
|
||||
if (!this._renderer._drawing || this._empty()) { return; }
|
||||
var p = this._point,
|
||||
ctx = this._renderer._ctx,
|
||||
r = Math.max(Math.round(this._radius), 1),
|
||||
s = (Math.max(Math.round(this._radiusY), 1) || r) / r;
|
||||
|
||||
if (s !== 1) {
|
||||
ctx.save();
|
||||
ctx.scale(1, s);
|
||||
}
|
||||
|
||||
let pathBegun = false;
|
||||
let dtheta = Math.PI * 2 / 120;
|
||||
for (let theta = 0; theta <= Math.PI * 2; theta += dtheta) {
|
||||
let p1 = new Point(p.x + r * Math.cos(theta), p.y / s + r * Math.sin(theta));
|
||||
let p2 = new Point(p.x + r * Math.cos(theta + dtheta), p.y / s + r * Math.sin(theta + dtheta));
|
||||
let l1 = this._map.layerPointToLatLng(p1);
|
||||
let l2 = this._map.layerPointToLatLng(p2);
|
||||
let line = new Polyline([l1, l2]);
|
||||
if (this._map.getBounds().intersects(line.getBounds())) {
|
||||
if (!pathBegun) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(p1.x, p1.y);
|
||||
pathBegun = true;
|
||||
}
|
||||
ctx.lineTo(p2.x, p2.y);
|
||||
}
|
||||
else {
|
||||
if (pathBegun) {
|
||||
this._renderer._fillStroke(ctx, this);
|
||||
pathBegun = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pathBegun)
|
||||
this._renderer._fillStroke(ctx, this);
|
||||
|
||||
if (s !== 1)
|
||||
ctx.restore();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -192,6 +192,10 @@ export class OlympusApp {
|
||||
this.#unitsManager = new UnitsManager();
|
||||
this.#weaponsManager = new WeaponsManager();
|
||||
|
||||
// Toolbars
|
||||
this.getToolbarsManager().add("primaryToolbar", new PrimaryToolbar("primary-toolbar"))
|
||||
.add("commandModeToolbar", new CommandModeToolbar("command-mode-toolbar"));
|
||||
|
||||
// Panels
|
||||
this.getPanelsManager()
|
||||
.add("connectionStatus", new ConnectionStatusPanel("connection-status-panel"))
|
||||
@@ -206,11 +210,7 @@ export class OlympusApp {
|
||||
// Popups
|
||||
this.getPopupsManager()
|
||||
.add("infoPopup", new Popup("info-popup"));
|
||||
|
||||
// Toolbars
|
||||
this.getToolbarsManager().add("primaryToolbar", new PrimaryToolbar("primary-toolbar"))
|
||||
.add("commandModeToolbar", new CommandModeToolbar("command-mode-toolbar"));
|
||||
|
||||
|
||||
this.#pluginsManager = new PluginsManager();
|
||||
|
||||
/* Load the config file from the app server*/
|
||||
|
||||
@@ -339,18 +339,14 @@ export function getMarkerCategoryByName(name: string) {
|
||||
else if (helicopterDatabase.getByName(name) != null)
|
||||
return "helicopter";
|
||||
else if (groundUnitDatabase.getByName(name) != null){
|
||||
var type = groundUnitDatabase.getByName(name)?.type;
|
||||
if (type === "SAM")
|
||||
var type = groundUnitDatabase.getByName(name)?.type ?? "";
|
||||
if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type))
|
||||
return "groundunit-sam";
|
||||
else if (type === "SAM Search radar" || type === "SAM Track radar" || type === "SAM Search/Track radar")
|
||||
return "groundunit-sam-radar";
|
||||
else if (type === "SAM Launcher")
|
||||
return "groundunit-sam-launcher";
|
||||
else if (type === "Radar")
|
||||
return "groundunit-ewr";
|
||||
else
|
||||
return "groundunit-other";
|
||||
}
|
||||
else if (navyUnitDatabase.getByName(name) != null)
|
||||
return "navyunit";
|
||||
else
|
||||
return "groundunit-other"; // TODO add other unit types
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Switch } from "../controls/switch";
|
||||
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, shotsIntensityDescriptions, shotsScatterDescriptions, speedIncrements } from "../constants/constants";
|
||||
import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils";
|
||||
import { GeneralSettings, Radio, TACAN } from "../interfaces";
|
||||
import { PrimaryToolbar } from "../toolbars/primarytoolbar";
|
||||
|
||||
export class UnitControlPanel extends Panel {
|
||||
#altitudeSlider: Slider;
|
||||
@@ -27,6 +28,7 @@ export class UnitControlPanel extends Panel {
|
||||
#advancedSettingsDialog: HTMLElement;
|
||||
#units: Unit[] = [];
|
||||
#selectedUnitsTypes: string[] = [];
|
||||
#deleteDropdown: Dropdown;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -112,6 +114,7 @@ export class UnitControlPanel extends Panel {
|
||||
this.#radioDecimalsDropdown = new Dropdown("radio-decimals", () => {});
|
||||
this.#radioDecimalsDropdown.setOptions([".000", ".250", ".500", ".750"]);
|
||||
this.#radioCallsignDropdown = new Dropdown("radio-callsign", () => {});
|
||||
this.#deleteDropdown = new Dropdown("delete-options", () => { });
|
||||
|
||||
/* Events and timer */
|
||||
window.setInterval(() => {this.update();}, 25);
|
||||
@@ -136,7 +139,13 @@ export class UnitControlPanel extends Panel {
|
||||
this.#updateRapidControls();
|
||||
});
|
||||
|
||||
window.addEventListener("resize", (e: any) => this.#calculateMaxHeight());
|
||||
|
||||
const element = document.getElementById("toolbar-container");
|
||||
if (element)
|
||||
new ResizeObserver(() => this.#calculateTop()).observe(element);
|
||||
|
||||
this.#calculateMaxHeight()
|
||||
this.hide();
|
||||
}
|
||||
|
||||
@@ -154,6 +163,7 @@ export class UnitControlPanel extends Panel {
|
||||
this.#followRoadsSwitch.resetExpectedValue();
|
||||
this.#altitudeSlider.resetExpectedValue();
|
||||
this.#speedSlider.resetExpectedValue();
|
||||
this.#calculateMaxHeight();
|
||||
}
|
||||
|
||||
addButtons() {
|
||||
@@ -470,4 +480,17 @@ export class UnitControlPanel extends Panel {
|
||||
button.addEventListener("click", callback);
|
||||
return button;
|
||||
}
|
||||
|
||||
#calculateTop() {
|
||||
const element = document.getElementById("toolbar-container");
|
||||
if (element)
|
||||
this.getElement().style.top = `${element.offsetTop + element.offsetHeight + 10}px`;
|
||||
}
|
||||
|
||||
#calculateMaxHeight() {
|
||||
const element = document.getElementById("unit-control-panel-content");
|
||||
this.#calculateTop();
|
||||
if (element)
|
||||
element.style.maxHeight = `${window.innerHeight - this.getElement().offsetTop - 10}px`;
|
||||
}
|
||||
}
|
||||
@@ -160,8 +160,8 @@ export class ServerManager {
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
|
||||
spawnExplosion(intensity: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "intensity": intensity, "location": latlng };
|
||||
spawnExplosion(intensity: number, explosionType: string, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "explosionType": explosionType, "intensity": intensity, "location": latlng };
|
||||
var data = { "explosion": command }
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
@@ -212,8 +212,8 @@ export class ServerManager {
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
|
||||
deleteUnit(ID: number, explosion: boolean, immediate: boolean, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "explosion": explosion, "immediate": immediate };
|
||||
deleteUnit(ID: number, explosion: boolean, explosionType: string, immediate: boolean, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "explosion": explosion, "explosionType": explosionType, "immediate": immediate };
|
||||
var data = { "deleteUnit": command }
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
|
||||
45
client/src/unit/group.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Unit } from "./unit";
|
||||
|
||||
export class Group {
|
||||
#members: Unit[] = [];
|
||||
#name: string;
|
||||
|
||||
constructor(name: string) {
|
||||
this.#name = name;
|
||||
|
||||
document.addEventListener("unitDeath", (e: any) => {
|
||||
if (this.#members.includes(e.detail))
|
||||
this.getLeader()?.onGroupChanged(e.detail);
|
||||
});
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
addMember(member: Unit) {
|
||||
if (!this.#members.includes(member)) {
|
||||
this.#members.push(member);
|
||||
member.setGroup(this);
|
||||
|
||||
this.getLeader()?.onGroupChanged(member);
|
||||
}
|
||||
}
|
||||
|
||||
removeMember(member: Unit) {
|
||||
if (this.#members.includes(member)) {
|
||||
delete this.#members[this.#members.indexOf(member)];
|
||||
member.setGroup(null);
|
||||
|
||||
this.getLeader()?.onGroupChanged(member);
|
||||
}
|
||||
}
|
||||
|
||||
getMembers() {
|
||||
return this.#members;
|
||||
}
|
||||
|
||||
getLeader() {
|
||||
return this.#members.find((unit: Unit) => { return (unit.getIsLeader() && unit.getAlive())})
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,14 @@ 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_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, GROUND_UNIT_AIR_DEFENCE_REGEX } from '../constants/constants';
|
||||
import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, GROUPING_ZOOM_TRANSITION, GROUND_UNIT_AIR_DEFENCE_REGEX } from '../constants/constants';
|
||||
import { DataExtractor } from '../server/dataextractor';
|
||||
import { groundUnitDatabase } from './databases/groundunitdatabase';
|
||||
import { navyUnitDatabase } from './databases/navyunitdatabase';
|
||||
import { Weapon } from '../weapon/weapon';
|
||||
import { Ammo, Contact, GeneralSettings, LoadoutBlueprint, ObjectIconOptions, Offset, Radio, TACAN, UnitData } from '../interfaces';
|
||||
import { RangeCircle } from "../map/rangecircle";
|
||||
import { Group } from './group';
|
||||
|
||||
var pathIcon = new Icon({
|
||||
iconUrl: '/resources/theme/images/markers/marker-icon.png',
|
||||
@@ -20,12 +22,11 @@ var pathIcon = new Icon({
|
||||
|
||||
/**
|
||||
* Unit class which controls unit behaviour
|
||||
*
|
||||
* Just about everything is a unit - even missiles!
|
||||
*/
|
||||
export class Unit extends CustomMarker {
|
||||
export abstract class Unit extends CustomMarker {
|
||||
ID: number;
|
||||
|
||||
/* Data controlled directly by the backend. No setters are provided to avoid misalignments */
|
||||
#alive: boolean = false;
|
||||
#human: boolean = false;
|
||||
#controlled: boolean = false;
|
||||
@@ -87,8 +88,10 @@ export class Unit extends CustomMarker {
|
||||
#operateAs: string = "blue";
|
||||
#shotsScatter: number = 2;
|
||||
#shotsIntensity: number = 2;
|
||||
#health: number = 100;
|
||||
|
||||
#selectable: boolean;
|
||||
/* Other members used to draw the unit, mostly ancillary stuff like targets, ranges and so on */
|
||||
#group: Group | null = null;
|
||||
#selected: boolean = false;
|
||||
#hidden: boolean = false;
|
||||
#highlighted: boolean = false;
|
||||
@@ -96,8 +99,8 @@ export class Unit extends CustomMarker {
|
||||
#pathMarkers: Marker[] = [];
|
||||
#pathPolyline: Polyline;
|
||||
#contactsPolylines: Polyline[] = [];
|
||||
#engagementCircle: Circle;
|
||||
#acquisitionCircle: Circle;
|
||||
#engagementCircle: RangeCircle;
|
||||
#acquisitionCircle: RangeCircle;
|
||||
#miniMapMarker: CircleMarker | null = null;
|
||||
#targetPositionMarker: TargetMarker;
|
||||
#targetPositionPolyline: Polyline;
|
||||
@@ -105,6 +108,7 @@ export class Unit extends CustomMarker {
|
||||
#hotgroup: number | null = null;
|
||||
#detectionMethods: number[] = [];
|
||||
|
||||
/* Getters for backend driven data */
|
||||
getAlive() { return this.#alive };
|
||||
getHuman() { return this.#human };
|
||||
getControlled() { return this.#controlled };
|
||||
@@ -121,8 +125,8 @@ export class Unit extends CustomMarker {
|
||||
getHorizontalVelocity() { return this.#horizontalVelocity };
|
||||
getVerticalVelocity() { return this.#verticalVelocity };
|
||||
getHeading() { return this.#heading };
|
||||
getIsActiveTanker() { return this.#isActiveTanker };
|
||||
getIsActiveAWACS() { return this.#isActiveAWACS };
|
||||
getIsActiveTanker() { return this.#isActiveTanker };
|
||||
getOnOff() { return this.#onOff };
|
||||
getFollowRoads() { return this.#followRoads };
|
||||
getFuel() { return this.#fuel };
|
||||
@@ -145,8 +149,9 @@ export class Unit extends CustomMarker {
|
||||
getActivePath() { return this.#activePath };
|
||||
getIsLeader() { return this.#isLeader };
|
||||
getOperateAs() { return this.#operateAs };
|
||||
getShotsScatter() { return this.#shotsScatter};
|
||||
getShotsIntensity() { return this.#shotsIntensity};
|
||||
getShotsScatter() { return this.#shotsScatter };
|
||||
getShotsIntensity() { return this.#shotsIntensity };
|
||||
getHealth() { return this.#health };
|
||||
|
||||
static getConstructor(type: string) {
|
||||
if (type === "GroundUnit") return GroundUnit;
|
||||
@@ -159,15 +164,15 @@ export class Unit extends CustomMarker {
|
||||
super(new LatLng(0, 0), { riseOnHover: true, keyboard: false });
|
||||
|
||||
this.ID = ID;
|
||||
this.#selectable = true;
|
||||
|
||||
this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 });
|
||||
this.#pathPolyline.addTo(getApp().getMap());
|
||||
this.#targetPositionMarker = new TargetMarker(new LatLng(0, 0));
|
||||
this.#targetPositionPolyline = new Polyline([], { color: '#FF0000', weight: 3, opacity: 0.5, smoothFactor: 1 });
|
||||
this.#engagementCircle = new Circle(this.getPosition(), { radius: 0, weight: 4, opacity: 1, fillOpacity: 0, dashArray: "4 8", interactive: false, bubblingMouseEvents: false });
|
||||
this.#acquisitionCircle = new Circle(this.getPosition(), { radius: 0, weight: 2, opacity: 1, fillOpacity: 0, dashArray: "8 12", interactive: false, bubblingMouseEvents: false });
|
||||
this.#engagementCircle = new RangeCircle(this.getPosition(), { radius: 0, weight: 4, opacity: 1, fillOpacity: 0, dashArray: "4 8", interactive: false, bubblingMouseEvents: false });
|
||||
this.#acquisitionCircle = new RangeCircle(this.getPosition(), { radius: 0, weight: 2, opacity: 1, fillOpacity: 0, dashArray: "8 12", interactive: false, bubblingMouseEvents: false });
|
||||
|
||||
/* Leaflet events listeners */
|
||||
this.on('click', (e) => this.#onClick(e));
|
||||
this.on('dblclick', (e) => this.#onDoubleClick(e));
|
||||
this.on('contextmenu', (e) => this.#onContextMenu(e));
|
||||
@@ -181,7 +186,7 @@ export class Unit extends CustomMarker {
|
||||
this.setHighlighted(false);
|
||||
document.dispatchEvent(new CustomEvent("unitMouseout", { detail: this }));
|
||||
});
|
||||
getApp().getMap().on("zoomend", () => { this.#onZoom(); })
|
||||
getApp().getMap().on("zoomend", (e: any) => { this.#onZoom(e); })
|
||||
|
||||
/* Deselect units if they are hidden */
|
||||
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
|
||||
@@ -192,13 +197,14 @@ export class Unit extends CustomMarker {
|
||||
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
|
||||
});
|
||||
|
||||
/* Update the marker when the visibility options change */
|
||||
document.addEventListener("mapVisibilityOptionsChanged", (ev: CustomEventInit) => {
|
||||
this.#updateMarker();
|
||||
|
||||
/* Circles don't like to be updated when the map is zooming */
|
||||
if (!getApp().getMap().isZooming())
|
||||
if (!getApp().getMap().isZooming())
|
||||
this.#drawRanges();
|
||||
else
|
||||
else
|
||||
this.once("zoomend", () => { this.#drawRanges(); })
|
||||
|
||||
if (this.getSelected())
|
||||
@@ -206,10 +212,25 @@ export class Unit extends CustomMarker {
|
||||
});
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
// Overloaded by child classes
|
||||
return "";
|
||||
}
|
||||
/********************** Abstract methods *************************/
|
||||
/** Get the unit category string
|
||||
*
|
||||
* @returns string The unit category
|
||||
*/
|
||||
abstract getCategory(): string;
|
||||
|
||||
/** Get the icon options
|
||||
* Used to configure how the marker appears on the map
|
||||
*
|
||||
* @returns ObjectIconOptions
|
||||
*/
|
||||
abstract getIconOptions(): ObjectIconOptions;
|
||||
|
||||
/** Get the actions that this unit can perform
|
||||
*
|
||||
* @returns Object containing the available actions
|
||||
*/
|
||||
abstract getActions(): {[key: string]: { text: string, tooltip: string, type: string}};
|
||||
|
||||
/** Get the category but for display use - for the user. (i.e. has spaces in it)
|
||||
*
|
||||
@@ -220,9 +241,15 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
/********************** Unit data *************************/
|
||||
/** This function is called by the units manager to update all the data coming from the backend. It reads the binary raw data using a DataExtractor
|
||||
*
|
||||
* @param dataExtractor The DataExtractor object pointing to the binary buffer which contains the raw data coming from the backend
|
||||
*/
|
||||
setData(dataExtractor: DataExtractor) {
|
||||
/* This variable controls if the marker must be updated. This is not always true since not all variables have an effect on the marker */
|
||||
var updateMarker = !getApp().getMap().hasLayer(this);
|
||||
|
||||
|
||||
var oldIsLeader = this.#isLeader;
|
||||
var datumIndex = 0;
|
||||
while (datumIndex != DataIndexes.endOfData) {
|
||||
datumIndex = dataExtractor.extractUInt8();
|
||||
@@ -231,7 +258,7 @@ export class Unit extends CustomMarker {
|
||||
case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break;
|
||||
case DataIndexes.human: this.#human = dataExtractor.extractBool(); break;
|
||||
case DataIndexes.controlled: this.#controlled = dataExtractor.extractBool(); updateMarker = true; break;
|
||||
case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); updateMarker = true; this.#clearRanges(); break;
|
||||
case DataIndexes.coalition: let newCoalition = enumToCoalition(dataExtractor.extractUInt8()); updateMarker = true; if (newCoalition != this.#coalition) this.#clearRanges(); this.#coalition = newCoalition; break; // If the coalition has changed, redraw the range circles to update the colour
|
||||
case DataIndexes.country: this.#country = dataExtractor.extractUInt8(); break;
|
||||
case DataIndexes.name: this.#name = dataExtractor.extractString(); break;
|
||||
case DataIndexes.unitName: this.#unitName = dataExtractor.extractString(); break;
|
||||
@@ -266,32 +293,37 @@ export class Unit extends CustomMarker {
|
||||
case DataIndexes.ammo: this.#ammo = dataExtractor.extractAmmo(); break;
|
||||
case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); document.dispatchEvent(new CustomEvent("contactsUpdated", { detail: this })); break;
|
||||
case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break;
|
||||
case DataIndexes.isLeader: this.#isLeader = dataExtractor.extractBool(); updateMarker = true; break;
|
||||
case DataIndexes.isLeader: this.#isLeader = dataExtractor.extractBool(); break;
|
||||
case DataIndexes.operateAs: this.#operateAs = enumToCoalition(dataExtractor.extractUInt8()); break;
|
||||
case DataIndexes.shotsScatter: this.#shotsScatter = dataExtractor.extractUInt8(); break;
|
||||
case DataIndexes.shotsIntensity: this.#shotsIntensity = dataExtractor.extractUInt8(); break;
|
||||
case DataIndexes.health: this.#health = dataExtractor.extractUInt8(); updateMarker = true; break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dead units can't be selected */
|
||||
/* Dead and hidden units can't be selected */
|
||||
this.setSelected(this.getSelected() && this.#alive && !this.getHidden())
|
||||
|
||||
/* Update the marker if required */
|
||||
if (updateMarker)
|
||||
this.#updateMarker();
|
||||
|
||||
/* Redraw the marker if isLeader has changed. TODO I don't love this approach, observables may be more elegant */
|
||||
if (oldIsLeader !== this.#isLeader) {
|
||||
this.#redrawMarker();
|
||||
|
||||
/* Reapply selection */
|
||||
if (this.getSelected()) {
|
||||
this.setSelected(false);
|
||||
this.setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
/* If the unit is selected or if the view is centered on this unit, sent the update signal so that other elements like the UnitControlPanel can be updated. */
|
||||
if (this.getSelected() || getApp().getMap().getCenterUnit() === this)
|
||||
document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
|
||||
}
|
||||
|
||||
drawLines() {
|
||||
/* Leaflet does not like it when you change coordinates when the map is zooming */
|
||||
if (!getApp().getMap().isZooming()) {
|
||||
this.#drawPath();
|
||||
this.#drawContacts();
|
||||
this.#drawTarget();
|
||||
}
|
||||
}
|
||||
|
||||
/** Get unit data collated into an object
|
||||
*
|
||||
* @returns object populated by unit information which can also be retrieved using getters
|
||||
@@ -342,7 +374,8 @@ export class Unit extends CustomMarker {
|
||||
isLeader: this.#isLeader,
|
||||
operateAs: this.#operateAs,
|
||||
shotsScatter: this.#shotsScatter,
|
||||
shotsIntensity: this.#shotsIntensity
|
||||
shotsIntensity: this.#shotsIntensity,
|
||||
health: this.#health
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,27 +395,6 @@ export class Unit extends CustomMarker {
|
||||
return getUnitDatabaseByCategory(this.getMarkerCategory());
|
||||
}
|
||||
|
||||
/** Get the icon options
|
||||
* Used to configure how the marker appears on the map
|
||||
*
|
||||
* @returns ObjectIconOptions
|
||||
*/
|
||||
getIconOptions(): ObjectIconOptions {
|
||||
// Default values, overloaded by child classes if needed
|
||||
return {
|
||||
showState: false,
|
||||
showVvi: false,
|
||||
showHotgroup: false,
|
||||
showUnitIcon: true,
|
||||
showShortLabel: false,
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: true,
|
||||
showCallsign: true,
|
||||
rotateToHeading: false
|
||||
}
|
||||
}
|
||||
|
||||
/** Set the unit as alive or dead
|
||||
*
|
||||
* @param newAlive (boolean) true = alive, false = dead
|
||||
@@ -398,16 +410,11 @@ export class Unit extends CustomMarker {
|
||||
* @param selected (boolean)
|
||||
*/
|
||||
setSelected(selected: boolean) {
|
||||
/* Only alive units can be selected. Some units are not selectable (weapons) */
|
||||
if ((this.#alive || !selected) && this.getSelectable() && this.getSelected() != selected && this.belongsToCommandedCoalition()) {
|
||||
/* Only alive units can be selected that belong to the commanded coalition can be selected */
|
||||
if ((this.#alive || !selected) && this.belongsToCommandedCoalition() && this.getSelected() != selected) {
|
||||
this.#selected = selected;
|
||||
|
||||
/* Circles don't like to be updated when the map is zooming */
|
||||
if (!getApp().getMap().isZooming())
|
||||
this.#drawRanges();
|
||||
else
|
||||
this.once("zoomend", () => { this.#drawRanges(); })
|
||||
|
||||
/* If selected, update the marker to show the selected effects, else clear all the drawings that are only shown for selected units. */
|
||||
if (selected) {
|
||||
this.#updateMarker();
|
||||
}
|
||||
@@ -417,21 +424,27 @@ export class Unit extends CustomMarker {
|
||||
this.#clearTarget();
|
||||
}
|
||||
|
||||
this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected);
|
||||
if (this.getCategory() === "GroundUnit" && getApp().getMap().getZoom() < 13) {
|
||||
if (this.#isLeader)
|
||||
/* When the group leader is selected, if grouping is active, all the other group members are also selected */
|
||||
if (this.getCategory() === "GroundUnit" && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION) {
|
||||
if (this.#isLeader) {
|
||||
/* Redraw the marker in case the leader unit was replaced by a group marker, like for SAM Sites */
|
||||
this.#redrawMarker();
|
||||
this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected));
|
||||
else
|
||||
}
|
||||
else {
|
||||
this.#updateMarker();
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger events after all (de-)selecting has been done
|
||||
/* Activate the selection effects on the marker */
|
||||
this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected);
|
||||
|
||||
/* 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 }));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,22 +456,6 @@ export class Unit extends CustomMarker {
|
||||
return this.#selected;
|
||||
}
|
||||
|
||||
/** Set whether this unit is selectable
|
||||
*
|
||||
* @param selectable (boolean)
|
||||
*/
|
||||
setSelectable(selectable: boolean) {
|
||||
this.#selectable = selectable;
|
||||
}
|
||||
|
||||
/** Get whether this unit is selectable
|
||||
*
|
||||
* @returns boolean
|
||||
*/
|
||||
getSelectable() {
|
||||
return this.#selectable;
|
||||
}
|
||||
|
||||
/** Set the number of the hotgroup to which the unit belongs
|
||||
*
|
||||
* @param hotgroup (number)
|
||||
@@ -481,9 +478,9 @@ export class Unit extends CustomMarker {
|
||||
* @param highlighted (boolean)
|
||||
*/
|
||||
setHighlighted(highlighted: boolean) {
|
||||
if (this.getSelectable() && this.#highlighted != highlighted) {
|
||||
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted);
|
||||
if (this.#highlighted != highlighted) {
|
||||
this.#highlighted = highlighted;
|
||||
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted);
|
||||
this.getGroupMembers().forEach((unit: Unit) => unit.setHighlighted(highlighted));
|
||||
}
|
||||
}
|
||||
@@ -501,7 +498,19 @@ export class Unit extends CustomMarker {
|
||||
* @returns Unit[]
|
||||
*/
|
||||
getGroupMembers() {
|
||||
return Object.values(getApp().getUnitsManager().getUnits()).filter((unit: Unit) => { return unit != this && unit.getGroupName() === this.getGroupName(); });
|
||||
if (this.#group !== null)
|
||||
return this.#group.getMembers().filter((unit: Unit) => { return unit != this; })
|
||||
return [];
|
||||
}
|
||||
|
||||
/** Return the leader of the group
|
||||
*
|
||||
* @returns Unit The leader of the group
|
||||
*/
|
||||
getGroupLeader() {
|
||||
if (this.#group !== null)
|
||||
return this.#group.getLeader();
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns whether the user is allowed to command this unit, based on coalition
|
||||
@@ -520,6 +529,31 @@ export class Unit extends CustomMarker {
|
||||
return this.getDatabase()?.getSpawnPointsByName(this.getName());
|
||||
}
|
||||
|
||||
getDatabaseEntry() {
|
||||
return this.getDatabase()?.getByName(this.#name);
|
||||
}
|
||||
|
||||
getGroup() {
|
||||
return this.#group;
|
||||
}
|
||||
|
||||
setGroup(group: Group | null) {
|
||||
this.#group = group;
|
||||
}
|
||||
|
||||
drawLines() {
|
||||
/* Leaflet does not like it when you change coordinates when the map is zooming */
|
||||
if (!getApp().getMap().isZooming()) {
|
||||
this.#drawPath();
|
||||
this.#drawContacts();
|
||||
this.#drawTarget();
|
||||
}
|
||||
}
|
||||
|
||||
checkZoomRedraw() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/********************** Icon *************************/
|
||||
createIcon(): void {
|
||||
/* Set the icon */
|
||||
@@ -530,6 +564,7 @@ export class Unit extends CustomMarker {
|
||||
});
|
||||
this.setIcon(icon);
|
||||
|
||||
/* Create the base element */
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("unit");
|
||||
el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`);
|
||||
@@ -537,8 +572,8 @@ export class Unit extends CustomMarker {
|
||||
|
||||
var iconOptions = this.getIconOptions();
|
||||
|
||||
// Generate and append elements depending on active options
|
||||
// Velocity vector
|
||||
/* Generate and append elements depending on active options */
|
||||
/* Velocity vector */
|
||||
if (iconOptions.showVvi) {
|
||||
var vvi = document.createElement("div");
|
||||
vvi.classList.add("unit-vvi");
|
||||
@@ -546,7 +581,7 @@ export class Unit extends CustomMarker {
|
||||
el.append(vvi);
|
||||
}
|
||||
|
||||
// Hotgroup indicator
|
||||
/* Hotgroup indicator */
|
||||
if (iconOptions.showHotgroup) {
|
||||
var hotgroup = document.createElement("div");
|
||||
hotgroup.classList.add("unit-hotgroup");
|
||||
@@ -556,42 +591,42 @@ export class Unit extends CustomMarker {
|
||||
el.append(hotgroup);
|
||||
}
|
||||
|
||||
// Main icon
|
||||
/* Main icon */
|
||||
if (iconOptions.showUnitIcon) {
|
||||
var unitIcon = document.createElement("div");
|
||||
unitIcon.classList.add("unit-icon");
|
||||
var img = document.createElement("img");
|
||||
var imgSrc;
|
||||
|
||||
/* If a unit does not belong to the commanded coalition or it is not visually detected, show it with the generic aircraft square */
|
||||
var marker;
|
||||
if (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)))
|
||||
imgSrc = this.getMarkerCategory();
|
||||
marker = this.getDatabaseEntry()?.markerFile ?? this.getMarkerCategory();
|
||||
else
|
||||
imgSrc = "aircraft";
|
||||
|
||||
img.src = `/resources/theme/images/units/${imgSrc}.svg`;
|
||||
marker = "aircraft";
|
||||
img.src = `/resources/theme/images/units/${marker}.svg`;
|
||||
img.onload = () => SVGInjector(img);
|
||||
unitIcon.appendChild(img);
|
||||
|
||||
unitIcon.toggleAttribute("data-rotate-to-heading", iconOptions.rotateToHeading);
|
||||
el.append(unitIcon);
|
||||
}
|
||||
|
||||
// State icon
|
||||
/* State icon */
|
||||
if (iconOptions.showState) {
|
||||
var state = document.createElement("div");
|
||||
state.classList.add("unit-state");
|
||||
el.appendChild(state);
|
||||
}
|
||||
|
||||
// Short label
|
||||
/* Short label */
|
||||
if (iconOptions.showShortLabel) {
|
||||
var shortLabel = document.createElement("div");
|
||||
shortLabel.classList.add("unit-short-label");
|
||||
shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.#name)?.shortLabel || "";
|
||||
shortLabel.innerText = this.getDatabaseEntry()?.shortLabel || "";
|
||||
el.append(shortLabel);
|
||||
}
|
||||
|
||||
// Fuel indicator
|
||||
/* Fuel indicator */
|
||||
if (iconOptions.showFuel) {
|
||||
var fuelIndicator = document.createElement("div");
|
||||
fuelIndicator.classList.add("unit-fuel");
|
||||
@@ -601,7 +636,17 @@ export class Unit extends CustomMarker {
|
||||
el.append(fuelIndicator);
|
||||
}
|
||||
|
||||
// Ammo indicator
|
||||
/* Health indicator */
|
||||
if (iconOptions.showHealth) {
|
||||
var healthIndicator = document.createElement("div");
|
||||
healthIndicator.classList.add("unit-health");
|
||||
var healthLevel = document.createElement("div");
|
||||
healthLevel.classList.add("unit-health-level");
|
||||
healthIndicator.appendChild(healthLevel);
|
||||
el.append(healthIndicator);
|
||||
}
|
||||
|
||||
/* Ammo indicator */
|
||||
if (iconOptions.showAmmo) {
|
||||
var ammoIndicator = document.createElement("div");
|
||||
ammoIndicator.classList.add("unit-ammo");
|
||||
@@ -610,7 +655,7 @@ export class Unit extends CustomMarker {
|
||||
el.append(ammoIndicator);
|
||||
}
|
||||
|
||||
// Unit summary
|
||||
/* Unit summary */
|
||||
if (iconOptions.showSummary) {
|
||||
var summary = document.createElement("div");
|
||||
summary.classList.add("unit-summary");
|
||||
@@ -628,25 +673,29 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
this.getElement()?.appendChild(el);
|
||||
|
||||
/* Circles don't like to be updated when the map is zooming */
|
||||
if (!getApp().getMap().isZooming())
|
||||
this.#drawRanges();
|
||||
else
|
||||
this.once("zoomend", () => { this.#drawRanges(); })
|
||||
}
|
||||
|
||||
/********************** Visibility *************************/
|
||||
updateVisibility() {
|
||||
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)) ||
|
||||
const hiddenTypes = getApp().getMap().getHiddenTypes();
|
||||
var hidden = (
|
||||
/* Hide the unit if it is a human and humans are hidden */
|
||||
(this.#human && hiddenTypes.includes("human")) ||
|
||||
/* Hide the unit if it is DCS controlled and DCS controlled units are hidden */
|
||||
(this.#controlled == false && hiddenTypes.includes("dcs")) ||
|
||||
/* Hide the unit if this specific category is hidden */
|
||||
(hiddenTypes.includes(this.getMarkerCategory())) ||
|
||||
/* Hide the unit if this coalition is hidden */
|
||||
(hiddenTypes.includes(this.#coalition)) ||
|
||||
/* Hide the unit if it does not belong to the commanded coalition and it is not detected by a method that can pinpoint its location (RWR does not count) */
|
||||
(!this.belongsToCommandedCoalition() && (this.#detectionMethods.length == 0 || (this.#detectionMethods.length == 1 && this.#detectionMethods[0] === RWR))) ||
|
||||
(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());
|
||||
/* Hide the unit if grouping is activated, the unit is not the group leader, it is not selected, and the zoom is higher than the grouping threshold */
|
||||
(getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION &&
|
||||
(this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) &&
|
||||
!(this.getSelected()
|
||||
);
|
||||
|
||||
/* Force dead units to be hidden */
|
||||
this.setHidden(hidden || !this.#alive);
|
||||
}
|
||||
|
||||
@@ -666,11 +715,12 @@ export class Unit extends CustomMarker {
|
||||
getApp().getMap().removeLayer(this);
|
||||
}
|
||||
|
||||
/* Draw the range circles if the unit is not hidden */
|
||||
if (!this.getHidden()) {
|
||||
/* Circles don't like to be updated when the map is zooming */
|
||||
if (!getApp().getMap().isZooming())
|
||||
if (!getApp().getMap().isZooming())
|
||||
this.#drawRanges();
|
||||
else
|
||||
else
|
||||
this.once("zoomend", () => { this.#drawRanges(); })
|
||||
} else {
|
||||
this.#clearRanges();
|
||||
@@ -705,7 +755,7 @@ export class Unit extends CustomMarker {
|
||||
if (typeof (roles) === "string")
|
||||
roles = [roles];
|
||||
|
||||
var loadouts = this.getDatabase()?.getByName(this.#name)?.loadouts;
|
||||
var loadouts = this.getDatabaseEntry()?.loadouts;
|
||||
if (loadouts) {
|
||||
return loadouts.some((loadout: LoadoutBlueprint) => {
|
||||
return (roles as string[]).some((role: string) => { return loadout.roles.includes(role) });
|
||||
@@ -719,11 +769,11 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
canTargetPoint() {
|
||||
return this.getDatabase()?.getByName(this.#name)?.canTargetPoint === true;
|
||||
return this.getDatabaseEntry()?.canTargetPoint === true;
|
||||
}
|
||||
|
||||
canRearm() {
|
||||
return this.getDatabase()?.getByName(this.#name)?.canRearm === true;
|
||||
return this.getDatabaseEntry()?.canRearm === true;
|
||||
}
|
||||
|
||||
canLandAtPoint() {
|
||||
@@ -731,11 +781,11 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
canAAA() {
|
||||
return this.getDatabase()?.getByName(this.#name)?.canAAA === true;
|
||||
return this.getDatabaseEntry()?.canAAA === true;
|
||||
}
|
||||
|
||||
indirectFire() {
|
||||
return this.getDatabase()?.getByName(this.#name)?.indirectFire === true;
|
||||
return this.getDatabaseEntry()?.indirectFire === true;
|
||||
}
|
||||
|
||||
isTanker() {
|
||||
@@ -845,8 +895,8 @@ export class Unit extends CustomMarker {
|
||||
getApp().getServerManager().setOperateAs(this.ID, coalitionToEnum(operateAs));
|
||||
}
|
||||
|
||||
delete(explosion: boolean, immediate: boolean) {
|
||||
getApp().getServerManager().deleteUnit(this.ID, explosion, immediate);
|
||||
delete(explosion: boolean, explosionType: string, immediate: boolean) {
|
||||
getApp().getServerManager().deleteUnit(this.ID, explosion, explosionType, immediate);
|
||||
}
|
||||
|
||||
refuel() {
|
||||
@@ -924,11 +974,6 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
getActions(): { [key: string]: { text: string, tooltip: string, type: string } } {
|
||||
/* To be implemented by child classes */ // TODO make Unit an abstract class
|
||||
return {};
|
||||
}
|
||||
|
||||
executeAction(e: any, action: string) {
|
||||
if (action === "center-map")
|
||||
getApp().getMap().centerOnUnit(this.ID);
|
||||
@@ -952,20 +997,21 @@ export class Unit extends CustomMarker {
|
||||
return this;
|
||||
}
|
||||
|
||||
onGroupChanged(member: Unit) {
|
||||
this.#redrawMarker();
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
#onClick(e: any) {
|
||||
|
||||
// Exit if we were waiting for a doubleclick
|
||||
/* Exit if we were waiting for a doubleclick */
|
||||
if (this.#waitingForDoubleClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We'll wait for a doubleclick
|
||||
/* We'll wait for a doubleclick */
|
||||
this.#waitingForDoubleClick = true;
|
||||
|
||||
this.#doubleClickTimer = window.setTimeout(() => {
|
||||
|
||||
// Still waiting so no doubleclick; do the click action
|
||||
/* Still waiting so no doubleclick; do the click action */
|
||||
if (this.#waitingForDoubleClick) {
|
||||
if (getApp().getMap().getState() === IDLE || getApp().getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) {
|
||||
if (!e.originalEvent.ctrlKey)
|
||||
@@ -975,17 +1021,17 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
}
|
||||
|
||||
// No longer waiting for a doubleclick
|
||||
/* No longer waiting for a doubleclick */
|
||||
this.#waitingForDoubleClick = false;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
#onDoubleClick(e: any) {
|
||||
// Let single clicks work again
|
||||
/* Let single clicks work again */
|
||||
this.#waitingForDoubleClick = false;
|
||||
clearTimeout(this.#doubleClickTimer);
|
||||
|
||||
// Select all matching units in the viewport
|
||||
/* Select all matching units in the viewport */
|
||||
const unitsManager = getApp().getUnitsManager();
|
||||
Object.values(unitsManager.getUnits()).forEach((unit: Unit) => {
|
||||
if (unit.getAlive() === true && unit.getName() === this.getName() && unit.isInViewport())
|
||||
@@ -1040,14 +1086,14 @@ export class Unit extends CustomMarker {
|
||||
var options: { [key: string]: { text: string, tooltip: string } } = {};
|
||||
|
||||
options = {
|
||||
'trail': { text: "Trail", tooltip: "Follow unit in trail formation" },
|
||||
'echelon-lh': { text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation" },
|
||||
'echelon-rh': { text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation" },
|
||||
'line-abreast-lh': { text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation" },
|
||||
'line-abreast-rh': { text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation" },
|
||||
'front': { text: "Front", tooltip: "Fly in front of unit" },
|
||||
'diamond': { text: "Diamond", tooltip: "Follow unit in diamond formation" },
|
||||
'custom': { text: "Custom", tooltip: "Set a custom formation position" },
|
||||
'trail': { text: "Trail", tooltip: "Follow unit in trail formation" },
|
||||
'echelon-lh': { text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation" },
|
||||
'echelon-rh': { text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation" },
|
||||
'line-abreast-lh': { text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation" },
|
||||
'line-abreast-rh': { text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation" },
|
||||
'front': { text: "Front", tooltip: "Fly in front of unit" },
|
||||
'diamond': { text: "Diamond", tooltip: "Follow unit in diamond formation" },
|
||||
'custom': { text: "Custom", tooltip: "Set a custom formation position" },
|
||||
}
|
||||
|
||||
getApp().getMap().getUnitContextMenu().setOptions(options, (option: string) => {
|
||||
@@ -1138,6 +1184,10 @@ export class Unit extends CustomMarker {
|
||||
element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.#fuel}%`);
|
||||
element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.#fuel < 20);
|
||||
|
||||
/* Set health data */
|
||||
element.querySelector(".unit-health-level")?.setAttribute("style", `width: ${this.#health}%`);
|
||||
element.querySelector(".unit")?.toggleAttribute("data-has-low-health", this.#health < 20);
|
||||
|
||||
/* Set dead/alive flag */
|
||||
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive);
|
||||
|
||||
@@ -1148,7 +1198,7 @@ export class Unit extends CustomMarker {
|
||||
else if (!this.#controlled) { // Unit is under DCS control (not Olympus)
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "dcs");
|
||||
}
|
||||
else if ((this.getCategory() == "Aircraft" || this.getCategory() == "Helicopter") && !this.#hasTask){
|
||||
else if ((this.getCategory() == "Aircraft" || this.getCategory() == "Helicopter") && !this.#hasTask) {
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "no-task");
|
||||
}
|
||||
else { // Unit is under Olympus control
|
||||
@@ -1221,6 +1271,14 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
}
|
||||
|
||||
#redrawMarker() {
|
||||
this.removeFrom(getApp().getMap());
|
||||
this.#updateMarker();
|
||||
|
||||
/* Activate the selection effects on the marker */
|
||||
this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", this.getSelected());
|
||||
}
|
||||
|
||||
#drawPath() {
|
||||
if (this.#activePath != undefined && getApp().getMap().getVisibilityOptions()[SHOW_UNIT_PATHS]) {
|
||||
var points = [];
|
||||
@@ -1316,13 +1374,13 @@ export class Unit extends CustomMarker {
|
||||
|
||||
/* Get the acquisition and engagement ranges of the entire group, not for each unit */
|
||||
if (this.getIsLeader()) {
|
||||
var engagementRange = this.getDatabase()?.getByName(this.getName())?.engagementRange?? 0;
|
||||
var acquisitionRange = this.getDatabase()?.getByName(this.getName())?.acquisitionRange?? 0;
|
||||
var engagementRange = this.getDatabase()?.getByName(this.getName())?.engagementRange ?? 0;
|
||||
var acquisitionRange = this.getDatabase()?.getByName(this.getName())?.acquisitionRange ?? 0;
|
||||
|
||||
this.getGroupMembers().forEach((unit: Unit) => {
|
||||
if (unit.getAlive()) {
|
||||
let unitEngagementRange = unit.getDatabase()?.getByName(unit.getName())?.engagementRange?? 0;
|
||||
let unitAcquisitionRange = unit.getDatabase()?.getByName(unit.getName())?.acquisitionRange?? 0;
|
||||
let unitEngagementRange = unit.getDatabase()?.getByName(unit.getName())?.engagementRange ?? 0;
|
||||
let unitAcquisitionRange = unit.getDatabase()?.getByName(unit.getName())?.acquisitionRange ?? 0;
|
||||
|
||||
if (unitEngagementRange > engagementRange)
|
||||
engagementRange = unitEngagementRange;
|
||||
@@ -1332,13 +1390,14 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
})
|
||||
|
||||
if (acquisitionRange !== this.#acquisitionCircle.getRadius())
|
||||
this.#acquisitionCircle.setRadius(acquisitionRange);
|
||||
if (acquisitionRange !== this.#acquisitionCircle.getRadius()) {
|
||||
this.#acquisitionCircle.setRadius(acquisitionRange);
|
||||
}
|
||||
|
||||
if (engagementRange !== this.#engagementCircle.getRadius())
|
||||
this.#engagementCircle.setRadius(engagementRange);
|
||||
|
||||
this.#engagementCircle.options.fillOpacity = this.getSelected() && getApp().getMap().getVisibilityOptions()[FILL_SELECTED_RING]? 0.3: 0;
|
||||
this.#engagementCircle.options.fillOpacity = this.getSelected() && getApp().getMap().getVisibilityOptions()[FILL_SELECTED_RING] ? 0.3 : 0;
|
||||
|
||||
/* Acquisition circles */
|
||||
var shortAcquisitionRangeCheck = (acquisitionRange > nmToM(3) || !getApp().getMap().getVisibilityOptions()[HIDE_UNITS_SHORT_RANGE_RINGS]);
|
||||
@@ -1358,13 +1417,14 @@ export class Unit extends CustomMarker {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.#acquisitionCircle.setLatLng(this.getPosition());
|
||||
if (this.getPosition() != this.#acquisitionCircle.getLatLng())
|
||||
this.#acquisitionCircle.setLatLng(this.getPosition());
|
||||
}
|
||||
else {
|
||||
if (getApp().getMap().hasLayer(this.#acquisitionCircle))
|
||||
this.#acquisitionCircle.removeFrom(getApp().getMap());
|
||||
}
|
||||
|
||||
|
||||
/* Engagement circles */
|
||||
var shortEngagementRangeCheck = (engagementRange > nmToM(3) || !getApp().getMap().getVisibilityOptions()[HIDE_UNITS_SHORT_RANGE_RINGS]);
|
||||
if (getApp().getMap().getVisibilityOptions()[SHOW_UNITS_ENGAGEMENT_RINGS] && shortEngagementRangeCheck && (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, IRST, RWR].includes(value)))) {
|
||||
@@ -1382,7 +1442,8 @@ export class Unit extends CustomMarker {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.#engagementCircle.setLatLng(this.getPosition());
|
||||
if (this.getPosition() != this.#engagementCircle.getLatLng())
|
||||
this.#engagementCircle.setLatLng(this.getPosition());
|
||||
}
|
||||
else {
|
||||
if (getApp().getMap().hasLayer(this.#engagementCircle))
|
||||
@@ -1430,17 +1491,20 @@ export class Unit extends CustomMarker {
|
||||
this.#targetPositionPolyline.removeFrom(getApp().getMap());
|
||||
}
|
||||
|
||||
#onZoom() {
|
||||
#onZoom(e: any) {
|
||||
if (this.checkZoomRedraw())
|
||||
this.#redrawMarker();
|
||||
this.#updateMarker();
|
||||
}
|
||||
}
|
||||
|
||||
export class AirUnit extends Unit {
|
||||
export abstract class AirUnit extends Unit {
|
||||
getIconOptions() {
|
||||
var belongsToCommandedCoalition = this.belongsToCommandedCoalition();
|
||||
return {
|
||||
showState: belongsToCommandedCoalition,
|
||||
showVvi: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
|
||||
showHealth: false,
|
||||
showHotgroup: belongsToCommandedCoalition,
|
||||
showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
|
||||
showShortLabel: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value))),
|
||||
@@ -1514,9 +1578,10 @@ export class GroundUnit extends Unit {
|
||||
return {
|
||||
showState: belongsToCommandedCoalition,
|
||||
showVvi: false,
|
||||
showHealth: true,
|
||||
showHotgroup: belongsToCommandedCoalition,
|
||||
showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
|
||||
showShortLabel: false,
|
||||
showShortLabel: this.getDatabaseEntry()?.type === "SAM Site",
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: false,
|
||||
@@ -1566,6 +1631,31 @@ export class GroundUnit extends Unit {
|
||||
var blueprint = groundUnitDatabase.getByName(this.getName());
|
||||
return blueprint?.type ? blueprint.type : "";
|
||||
}
|
||||
|
||||
/* When a unit is a leader of a group, the map is zoomed out and grouping when zoomed out is enabled, check if the unit should be shown as a specific group. This is used to show a SAM battery instead of the group leader */
|
||||
getDatabaseEntry() {
|
||||
let unitWhenGrouped = null;
|
||||
if (!this.getSelected() && this.getIsLeader() && getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION) {
|
||||
unitWhenGrouped = this.getDatabase()?.getByName(this.getName())?.unitWhenGrouped ?? null;
|
||||
let member = this.getGroupMembers().reduce((prev: Unit | null, unit: Unit, index: number) => {
|
||||
if (unit.getDatabaseEntry()?.unitWhenGrouped != undefined)
|
||||
return unit
|
||||
return prev;
|
||||
}, null);
|
||||
unitWhenGrouped = (member !== null ? member?.getDatabaseEntry()?.unitWhenGrouped : unitWhenGrouped);
|
||||
}
|
||||
if (unitWhenGrouped)
|
||||
return this.getDatabase()?.getByName(unitWhenGrouped);
|
||||
else
|
||||
return this.getDatabase()?.getByName(this.getName());
|
||||
}
|
||||
|
||||
/* When we zoom past the grouping limit, grouping is enabled and the unit is a leader, we redraw the unit to apply any possible grouped marker */
|
||||
checkZoomRedraw(): boolean {
|
||||
return (this.getIsLeader() && getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] &&
|
||||
(getApp().getMap().getZoom() >= GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() < GROUPING_ZOOM_TRANSITION ||
|
||||
getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() >= GROUPING_ZOOM_TRANSITION))
|
||||
}
|
||||
}
|
||||
|
||||
export class NavyUnit extends Unit {
|
||||
@@ -1578,6 +1668,7 @@ export class NavyUnit extends Unit {
|
||||
return {
|
||||
showState: belongsToCommandedCoalition,
|
||||
showVvi: false,
|
||||
showHealth: true,
|
||||
showHotgroup: true,
|
||||
showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
|
||||
showShortLabel: false,
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Popup } from "../popups/popup";
|
||||
import { HotgroupPanel } from "../panels/hotgrouppanel";
|
||||
import { Contact, UnitData, UnitSpawnTable } from "../interfaces";
|
||||
import { Dialog } from "../dialog/dialog";
|
||||
import { Group } from "./group";
|
||||
import { UnitDataFileExport } from "./unitdatafileexport";
|
||||
import { UnitDataFileImport } from "./unitdatafileimport";
|
||||
|
||||
@@ -27,8 +28,9 @@ export class UnitsManager {
|
||||
#deselectionEventDisabled: boolean = false;
|
||||
#requestDetectionUpdate: boolean = false;
|
||||
#selectionEventDisabled: boolean = false;
|
||||
#slowDeleteDialog!:Dialog;
|
||||
#slowDeleteDialog!: Dialog;
|
||||
#units: { [ID: number]: Unit };
|
||||
#groups: { [groupName: string]: Group } = {};
|
||||
#unitDataExport!:UnitDataFileExport;
|
||||
#unitDataImport!:UnitDataFileImport;
|
||||
|
||||
@@ -40,7 +42,7 @@ export class UnitsManager {
|
||||
document.addEventListener('contactsUpdated', (e: CustomEvent) => { this.#requestDetectionUpdate = true });
|
||||
document.addEventListener('copy', () => this.selectedUnitsCopy());
|
||||
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete());
|
||||
document.addEventListener('explodeSelectedUnits', () => this.selectedUnitsDelete(true));
|
||||
document.addEventListener('explodeSelectedUnits', (e: any) => this.selectedUnitsDelete(true, e.detail.type));
|
||||
document.addEventListener('exportToFile', () => this.exportToFile());
|
||||
document.addEventListener('importFromFile', () => this.importFromFile());
|
||||
document.addEventListener('keyup', (event) => this.#onKeyUp(event));
|
||||
@@ -49,10 +51,9 @@ export class UnitsManager {
|
||||
document.addEventListener('selectedUnitsChangeSpeed', (e: any) => { this.selectedUnitsChangeSpeed(e.detail.type) });
|
||||
document.addEventListener('unitDeselection', (e: CustomEvent) => this.#onUnitDeselection(e.detail));
|
||||
document.addEventListener('unitSelection', (e: CustomEvent) => this.#onUnitSelection(e.detail));
|
||||
document.addEventListener("toggleMarkerProtection", (ev: CustomEventInit) => { this.#showNumberOfSelectedProtectedUnits() });
|
||||
|
||||
document.addEventListener("toggleMarkerProtection", (ev:CustomEventInit) => { this.#showNumberOfSelectedProtectedUnits() });
|
||||
|
||||
this.#slowDeleteDialog = new Dialog( "slow-delete-dialog" );
|
||||
this.#slowDeleteDialog = new Dialog("slow-delete-dialog");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,6 +134,22 @@ export class UnitsManager {
|
||||
this.#units[ID]?.setData(dataExtractor);
|
||||
}
|
||||
|
||||
/* Update the unit groups */
|
||||
for (let ID in this.#units) {
|
||||
const unit = this.#units[ID];
|
||||
const groupName = unit.getGroupName();
|
||||
|
||||
if (groupName !== "") {
|
||||
/* If the group does not yet exist, create it */
|
||||
if (!(groupName in this.#groups))
|
||||
this.#groups[groupName] = new Group(groupName);
|
||||
|
||||
/* If the unit was not assigned to a group yet, assign it */
|
||||
if (unit.getGroup() === null)
|
||||
this.#groups[groupName].addMember(unit);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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
|
||||
*/
|
||||
@@ -212,9 +229,9 @@ export class UnitsManager {
|
||||
*
|
||||
* @param hotgroup The hotgroup number
|
||||
*/
|
||||
selectUnitsByHotgroup(hotgroup: number, deselectAllUnits: boolean = true ) {
|
||||
selectUnitsByHotgroup(hotgroup: number, deselectAllUnits: boolean = true) {
|
||||
|
||||
if ( deselectAllUnits ) {
|
||||
if (deselectAllUnits) {
|
||||
this.deselectAllUnits();
|
||||
}
|
||||
|
||||
@@ -226,8 +243,8 @@ export class UnitsManager {
|
||||
* @param options Selection options
|
||||
* @returns Array of selected units
|
||||
*/
|
||||
getSelectedUnits(options?: { excludeHumans?: boolean, excludeProtected?:boolean, onlyOnePerGroup?: boolean, showProtectionReminder?:boolean }) {
|
||||
let selectedUnits:Unit[] = [];
|
||||
getSelectedUnits(options?: { excludeHumans?: boolean, excludeProtected?: boolean, onlyOnePerGroup?: boolean, showProtectionReminder?: boolean }) {
|
||||
let selectedUnits: Unit[] = [];
|
||||
let numProtectedUnits = 0;
|
||||
for (const [ID, unit] of Object.entries(this.#units)) {
|
||||
if (unit.getSelected()) {
|
||||
@@ -536,7 +553,7 @@ export class UnitsManager {
|
||||
* @param operateAsBool If true, units will operate as blue
|
||||
*/
|
||||
selectedUnitsSetOperateAs(operateAsBool: boolean) {
|
||||
var operateAs = operateAsBool? "blue": "red";
|
||||
var operateAs = operateAsBool ? "blue" : "red";
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setOperateAs(operateAs);
|
||||
@@ -588,7 +605,7 @@ export class UnitsManager {
|
||||
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if ( selectedUnits.length === 0)
|
||||
if (selectedUnits.length === 0)
|
||||
return;
|
||||
|
||||
var count = 1;
|
||||
@@ -675,7 +692,7 @@ export class UnitsManager {
|
||||
});
|
||||
this.#showActionMessage(selectedUnits, `unit simulating fire fight`);
|
||||
}
|
||||
|
||||
|
||||
/** Instruct units to enter into scenic AAA mode. Units will shoot in the air without aiming
|
||||
*
|
||||
*/
|
||||
@@ -764,7 +781,7 @@ export class UnitsManager {
|
||||
var unit = selectedUnits[idx];
|
||||
units.push({ ID: unit.ID, location: unit.getPosition() });
|
||||
}
|
||||
getApp().getServerManager().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 {
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Groups can only be created from units of the same category`);
|
||||
}
|
||||
@@ -797,8 +814,8 @@ export class UnitsManager {
|
||||
* @param explosion If true, the unit will be deleted using an explosion
|
||||
* @returns
|
||||
*/
|
||||
selectedUnitsDelete(explosion: boolean = false) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeProtected:true}); /* Can be applied to humans too */
|
||||
selectedUnitsDelete(explosion: boolean = false, explosionType: string = "") {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeProtected: true }); /* Can be applied to humans too */
|
||||
const selectionContainsAHuman = selectedUnits.some((unit: Unit) => {
|
||||
return unit.getHuman() === true;
|
||||
});
|
||||
@@ -807,22 +824,22 @@ export class UnitsManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const doDelete = (explosion = false, immediate = false) => {
|
||||
const doDelete = (explosion = false, explosionType = "", immediate = false) => {
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].delete(explosion, immediate);
|
||||
selectedUnits[idx].delete(explosion, explosionType, immediate);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `deleted`);
|
||||
}
|
||||
|
||||
if (selectedUnits.length >= DELETE_SLOW_THRESHOLD)
|
||||
this.#showSlowDeleteDialog(selectedUnits).then((action:any) => {
|
||||
this.#showSlowDeleteDialog(selectedUnits).then((action: any) => {
|
||||
if (action === "delete-slow")
|
||||
doDelete(explosion, false);
|
||||
doDelete(explosion, explosionType, false);
|
||||
else if (action === "delete-immediate")
|
||||
doDelete(explosion, true);
|
||||
doDelete(explosion, explosionType, true);
|
||||
})
|
||||
else
|
||||
doDelete(explosion);
|
||||
doDelete(explosion, explosionType);
|
||||
|
||||
}
|
||||
|
||||
@@ -871,7 +888,7 @@ export class UnitsManager {
|
||||
this.#copiedUnits = JSON.parse(JSON.stringify(this.getSelectedUnits().map((unit: Unit) => { return unit.getData() }))); /* Can be applied to humans too */
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${this.#copiedUnits.length} units copied`);
|
||||
}
|
||||
|
||||
|
||||
/*********************** Unit manipulation functions ************************/
|
||||
/** Paste the copied units
|
||||
*
|
||||
@@ -892,7 +909,7 @@ export class UnitsManager {
|
||||
if (unitSpawnPoints !== undefined)
|
||||
spawnPoints += unitSpawnPoints;
|
||||
})
|
||||
|
||||
|
||||
if (spawnPoints > getApp().getMissionManager().getAvailableSpawnPoints()) {
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Not enough spawn points available!");
|
||||
return false;
|
||||
@@ -927,7 +944,7 @@ export class UnitsManager {
|
||||
markers.push(getApp().getMap().addTemporaryMarker(position, unit.name, unit.coalition));
|
||||
units.push({ ID: unit.ID, location: position });
|
||||
});
|
||||
|
||||
|
||||
getApp().getServerManager().cloneUnits(units, false, spawnPoints, (res: any) => {
|
||||
if (res.commandHash !== undefined) {
|
||||
markers.forEach((marker: TemporaryUnitMarker) => {
|
||||
@@ -975,7 +992,7 @@ export class UnitsManager {
|
||||
if (Math.random() < IADSDensities[type]) {
|
||||
/* Get a random blueprint depending on the selected parameters and spawn the unit */
|
||||
const unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { type: type, eras: activeEras, ranges: activeRanges });
|
||||
if (unitBlueprint)
|
||||
if (unitBlueprint)
|
||||
this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "" }], coalitionArea.getCoalition(), true);
|
||||
}
|
||||
}
|
||||
@@ -1013,24 +1030,24 @@ export class UnitsManager {
|
||||
* @param callback CallableFunction called when the command is received by the server
|
||||
* @returns True if the spawn command was successfully sent
|
||||
*/
|
||||
spawnUnits(category: string, units: UnitSpawnTable[], coalition: string = "blue", immediate: boolean = true, airbase: string = "", country: string = "", callback: CallableFunction = () => {}) {
|
||||
spawnUnits(category: string, units: UnitSpawnTable[], coalition: string = "blue", immediate: boolean = true, airbase: string = "", country: string = "", callback: CallableFunction = () => { }) {
|
||||
var spawnPoints = 0;
|
||||
var spawnFunction = () => {};
|
||||
var spawnFunction = () => { };
|
||||
var spawnsRestricted = getApp().getMissionManager().getCommandModeOptions().restrictSpawns && getApp().getMissionManager().getRemainingSetupTime() < 0 && getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER;
|
||||
|
||||
|
||||
if (category === "Aircraft") {
|
||||
if (airbase == "" && spawnsRestricted) {
|
||||
(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);
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { return points + aircraftDatabase.getSpawnPointsByName(unit.unitType) }, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints, callback);
|
||||
} else if (category === "Helicopter") {
|
||||
if (airbase == "" && spawnsRestricted) {
|
||||
(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);
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { return points + helicopterDatabase.getSpawnPointsByName(unit.unitType) }, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints, callback);
|
||||
|
||||
} else if (category === "GroundUnit") {
|
||||
@@ -1038,7 +1055,7 @@ export class UnitsManager {
|
||||
(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);
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType) }, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnGroundUnits(units, coalition, country, immediate, spawnPoints, callback);
|
||||
|
||||
} else if (category === "NavyUnit") {
|
||||
@@ -1046,7 +1063,7 @@ export class UnitsManager {
|
||||
(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);
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { return points + navyUnitDatabase.getSpawnPointsByName(unit.unitType) }, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback);
|
||||
}
|
||||
|
||||
@@ -1112,30 +1129,30 @@ export class UnitsManager {
|
||||
else if (units.length > 1)
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`);
|
||||
}
|
||||
|
||||
#showSlowDeleteDialog(selectedUnits:Unit[]) {
|
||||
let button:HTMLButtonElement | null = null;
|
||||
const deletionTime = Math.round( selectedUnits.length * DELETE_CYCLE_TIME ).toString();
|
||||
|
||||
#showSlowDeleteDialog(selectedUnits: Unit[]) {
|
||||
let button: HTMLButtonElement | null = null;
|
||||
const deletionTime = Math.round(selectedUnits.length * DELETE_CYCLE_TIME).toString();
|
||||
const dialog = this.#slowDeleteDialog;
|
||||
const element = dialog.getElement();
|
||||
const listener = (ev:MouseEvent) => {
|
||||
const listener = (ev: MouseEvent) => {
|
||||
if (ev.target instanceof HTMLButtonElement && ev.target.matches("[data-action]"))
|
||||
button = ev.target;
|
||||
}
|
||||
|
||||
element.querySelectorAll(".deletion-count").forEach( el => el.innerHTML = selectedUnits.length.toString() );
|
||||
element.querySelectorAll(".deletion-time").forEach( el => el.innerHTML = deletionTime );
|
||||
element.querySelectorAll(".deletion-count").forEach(el => el.innerHTML = selectedUnits.length.toString());
|
||||
element.querySelectorAll(".deletion-time").forEach(el => el.innerHTML = deletionTime);
|
||||
dialog.show();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
element.addEventListener("click", listener);
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (button instanceof HTMLButtonElement ) {
|
||||
if (button instanceof HTMLButtonElement) {
|
||||
clearInterval(interval);
|
||||
dialog.hide();
|
||||
element.removeEventListener("click", listener);
|
||||
resolve( button.getAttribute("data-action") );
|
||||
resolve(button.getAttribute("data-action"));
|
||||
}
|
||||
}, 250);
|
||||
});
|
||||
@@ -1143,18 +1160,18 @@ export class UnitsManager {
|
||||
|
||||
#showNumberOfSelectedProtectedUnits() {
|
||||
const map = getApp().getMap();
|
||||
const selectedUnits = this.getSelectedUnits();
|
||||
const numSelectedUnits = selectedUnits.length;
|
||||
const numProtectedUnits = selectedUnits.filter((unit:Unit) => map.unitIsProtected(unit) ).length;
|
||||
const selectedUnits = this.getSelectedUnits();
|
||||
const numSelectedUnits = selectedUnits.length;
|
||||
const numProtectedUnits = selectedUnits.filter((unit: Unit) => map.unitIsProtected(unit)).length;
|
||||
|
||||
if (numProtectedUnits === 1 && numSelectedUnits === numProtectedUnits)
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Notice: unit is protected`);
|
||||
|
||||
|
||||
if (numProtectedUnits > 1)
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Notice: selection contains ${numProtectedUnits} protected units.`);
|
||||
}
|
||||
|
||||
#unitIsProtected(unit:Unit) {
|
||||
#unitIsProtected(unit: Unit) {
|
||||
return getApp().getMap().unitIsProtected(unit)
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,7 @@ export class Weapon extends CustomMarker {
|
||||
return {
|
||||
showState: false,
|
||||
showVvi: false,
|
||||
showHealth: false,
|
||||
showHotgroup: false,
|
||||
showUnitIcon: true,
|
||||
showShortLabel: false,
|
||||
@@ -276,6 +277,7 @@ export class Missile extends Weapon {
|
||||
return {
|
||||
showState: false,
|
||||
showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
|
||||
showHealth: false,
|
||||
showHotgroup: false,
|
||||
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
|
||||
showShortLabel: false,
|
||||
@@ -308,6 +310,7 @@ export class Bomb extends Weapon {
|
||||
return {
|
||||
showState: false,
|
||||
showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
|
||||
showHealth: false,
|
||||
showHotgroup: false,
|
||||
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
|
||||
showShortLabel: false,
|
||||
|
||||
@@ -49,9 +49,12 @@
|
||||
<button class="smoke-button" title="" data-smoke-color="orange" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "orange" }'>Orange smoke</button>
|
||||
</div>
|
||||
<div id="explosion-menu" class="ol-panel ol-contexmenu-panel hide">
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 1 }'>Small explosion</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 2 }'>Medium explosion</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 3 }'>Big explosion</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 4 }'>Huge explosion</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "normal", "strength": 1 }'>Small explosion</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "normal", "strength": 10 }'>Big explosion</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "phosphorous"}'>White phosphorous</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "napalm"}'>Napalm</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "secondary"}'>Explosion with secondaries</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "fire"}'>Static fire</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "depthCharge"}'>Depth charge</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,113 +1,128 @@
|
||||
<div id="unit-control-panel" class="ol-panel ol-panel-padding-lg" oncontextmenu="return false;">
|
||||
<div id="unit-control-panel" oncontextmenu="return false;">
|
||||
<div class="ol-panel"><img src="resources/theme/images/icons/gamepad-solid.svg" inject-svg></div>
|
||||
|
||||
<div class="ol-panel ol-panel-padding-lg" id="unit-control-panel-content">
|
||||
<h3>Selected Units</h3>
|
||||
|
||||
<h3>Selected Units</h3>
|
||||
<div id="unit-selection">
|
||||
|
||||
<div id="unit-selection">
|
||||
<div id="selected-units-container" class="ol-scrollable">
|
||||
<!-- This is where all the unit selection buttons will be shown-->
|
||||
<!-- <button class="pill highlight-coalition" data-coalition="blue" data-label="18">Olympus 1-1</button> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="selected-units-container" class="ol-scrollable">
|
||||
<!-- This is where all the unit selection buttons will be shown-->
|
||||
<!-- <button class="pill highlight-coalition" data-coalition="blue" data-label="18">Olympus 1-1</button> -->
|
||||
<hr />
|
||||
|
||||
<div id="unit-controls" class="ol-scrollable">
|
||||
<div id="flight-data">
|
||||
<h4>Controls</h4>
|
||||
<div id="speed-slider" class="ol-slider-container flight-control-ol-slider">
|
||||
<dl class="ol-data-grid">
|
||||
<dt>Speed</dt>
|
||||
<dd>
|
||||
<div class="ol-slider-value"></div>
|
||||
<div id="speed-type-switch" class="ol-switch"></div>
|
||||
</dd>
|
||||
</dl>
|
||||
<input type="range" min="0" max="100" value="0" class="ol-slider">
|
||||
<div class="ol-slider-min-max"></div>
|
||||
</div>
|
||||
<div id="altitude-slider" class="ol-slider-container flight-control-ol-slider">
|
||||
<dl class="ol-data-grid">
|
||||
<dt> Altitude
|
||||
</dt>
|
||||
<dd>
|
||||
<div class="ol-slider-value"></div>
|
||||
<div id="altitude-type-switch" class="ol-switch"></div>
|
||||
</dd>
|
||||
</dl>
|
||||
<input type="range" min="0" max="100" value="0" class="ol-slider">
|
||||
<div class="ol-slider-min-max"></div>
|
||||
</div>
|
||||
<h5 id="categories-tooltip">Multiple categories selected</h5>
|
||||
</div>
|
||||
|
||||
<div id="roe">
|
||||
<h4>Rules of engagement</h4>
|
||||
<div id="roe-buttons-container" class="ol-group ol-button-box ol-option-button">
|
||||
<!-- This is where the roe buttons will be shown -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="threat">
|
||||
<h4>Reaction to threat</h4>
|
||||
<div id="reaction-to-threat-buttons-container" class="ol-group ol-button-box ol-option-button">
|
||||
<!-- This is where the reaction to threat buttons will be shown -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="emissions-countermeasures">
|
||||
<h4>Radar & ECM</h4>
|
||||
<div id="emissions-countermeasures-buttons-container" class="ol-group ol-button-box ol-option-button">
|
||||
<!-- This is where the emissions/countermeasures buttons will be shown -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="shots-scatter">
|
||||
<h4>Shots scatter</h4>
|
||||
<div id="shots-scatter-buttons-container" class="ol-group ol-button-box ol-option-button">
|
||||
<!-- This is where the shots scatter buttons will be shown -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="shots-intensity">
|
||||
<h4>Shots intensity</h4>
|
||||
<div id="shots-intensity-buttons-container" class="ol-group ol-button-box ol-option-button">
|
||||
<!-- This is where the shots intensity buttons will be shown -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tanker-on" class="switch-control">
|
||||
<h4>Enable tanker <img src="/resources/theme/images/icons/circle-question-regular.svg" title="Instructs the unit to operate as AAR tanker. A/A TACAN, radio frequency and callsign set in Settings dialog."></h4>
|
||||
<div id="tanker-on-switch" class="ol-switch"></div>
|
||||
</div>
|
||||
|
||||
<div id="AWACS-on" class="switch-control">
|
||||
<h4>Airborne Early Warning <img src="/resources/theme/images/icons/circle-question-regular.svg" title="Enables datalink and AI radio calls. Radio frequency and callsign set in Settings dialog."></h4>
|
||||
<div id="AWACS-on-switch" class="ol-switch"></div>
|
||||
</div>
|
||||
|
||||
<div id="operate-as" class="switch-control">
|
||||
<h4>Operate as <img src="/resources/theme/images/icons/circle-question-regular.svg" title="Determines if the unit will target red or blue units when performing scenic tasks."></h4>
|
||||
<div id="operate-as-switch" class="ol-switch"></div>
|
||||
</div>
|
||||
|
||||
<div id="ai-on-off" class="switch-control">
|
||||
<h4>Unit active <img src="/resources/theme/images/icons/circle-question-regular.svg" title="Toggling this disables unit AI completely. It will no longer move, react or emit radio waves."></h4>
|
||||
<div id="on-off-switch" class="ol-switch" title=""></div>
|
||||
</div>
|
||||
|
||||
<div id="follow-roads" class="switch-control">
|
||||
<h4>Follow roads <img src="/resources/theme/images/icons/circle-question-regular.svg" title=""></h4>
|
||||
<div id="follow-roads-switch" class="ol-switch"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div id="advanced-settings-div">
|
||||
<button id="advanced-settings-button" class="ol-button-settings" data-on-click="showAdvancedSettings">Settings</button>
|
||||
<div id="delete-options" class="ol-select">
|
||||
<div class="ol-select-value ol-select-warning">
|
||||
Delete unit
|
||||
</div>
|
||||
<div class="ol-select-options">
|
||||
<div><button class="ol-button-white" data-on-click="deleteSelectedUnits" title="Immediately remove the unit from the simulation"><img src="/resources/theme/images/icons/trash-can-regular.svg" inject-svg>Delete</button></div>
|
||||
<div><hr></div>
|
||||
<div><button class="ol-button-warning" data-on-click="explodeSelectedUnits" data-on-click-params='{ "type": "normal" }' title="Normal explosion"><img src="/resources/theme/images/icons/explosion-solid.svg" inject-svg>Blow up</button></div>
|
||||
<div><button class="ol-button-warning" data-on-click="explodeSelectedUnits" data-on-click-params='{ "type": "secondary" }' title="The unit will keep exploding at random intervals, simulating ammunition cooking"><img src="/resources/theme/images/icons/burst-solid.svg" inject-svg>Cook off</button></div>
|
||||
<div><button class="ol-button-warning" data-on-click="explodeSelectedUnits" data-on-click-params='{ "type": "phosphorous" }' title="White phosphorous explosion"><img src="/resources/theme/images/icons/smog-solid.svg" inject-svg>Phosp.</button></div>
|
||||
<div><button class="ol-button-warning" data-on-click="explodeSelectedUnits" data-on-click-params='{ "type": "napalm" }' title="Napalm"><img src="/resources/theme/images/icons/fire-solid.svg" inject-svg>Napalm</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div id="flight-data">
|
||||
<h4>Controls</h4>
|
||||
<div id="speed-slider" class="ol-slider-container flight-control-ol-slider">
|
||||
<dl class="ol-data-grid">
|
||||
<dt>Speed</dt>
|
||||
<dd>
|
||||
<div class="ol-slider-value"></div>
|
||||
<div id="speed-type-switch" class="ol-switch"></div>
|
||||
</dd>
|
||||
</dl>
|
||||
<input type="range" min="0" max="100" value="0" class="ol-slider">
|
||||
<div class="ol-slider-min-max"></div>
|
||||
</div>
|
||||
<div id="altitude-slider" class="ol-slider-container flight-control-ol-slider">
|
||||
<dl class="ol-data-grid">
|
||||
<dt> Altitude
|
||||
</dt>
|
||||
<dd>
|
||||
<div class="ol-slider-value"></div>
|
||||
<div id="altitude-type-switch" class="ol-switch"></div>
|
||||
</dd>
|
||||
</dl>
|
||||
<input type="range" min="0" max="100" value="0" class="ol-slider">
|
||||
<div class="ol-slider-min-max"></div>
|
||||
</div>
|
||||
<h5 id="categories-tooltip">Multiple categories selected</h5>
|
||||
</div>
|
||||
|
||||
<div id="roe">
|
||||
<h4>Rules of engagement</h4>
|
||||
<div id="roe-buttons-container" class="ol-group ol-button-box ol-option-button">
|
||||
<!-- This is where the roe buttons will be shown -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="threat">
|
||||
<h4>Reaction to threat</h4>
|
||||
<div id="reaction-to-threat-buttons-container" class="ol-group ol-button-box ol-option-button">
|
||||
<!-- This is where the reaction to threat buttons will be shown -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="emissions-countermeasures">
|
||||
<h4>Radar & ECM</h4>
|
||||
<div id="emissions-countermeasures-buttons-container" class="ol-group ol-button-box ol-option-button">
|
||||
<!-- This is where the emissions/countermeasures buttons will be shown -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="shots-scatter">
|
||||
<h4>Shots scatter</h4>
|
||||
<div id="shots-scatter-buttons-container" class="ol-group ol-button-box ol-option-button">
|
||||
<!-- This is where the shots scatter buttons will be shown -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="shots-intensity">
|
||||
<h4>Shots intensity</h4>
|
||||
<div id="shots-intensity-buttons-container" class="ol-group ol-button-box ol-option-button">
|
||||
<!-- This is where the shots intensity buttons will be shown -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tanker-on" class="switch-control">
|
||||
<h4>Enable tanker <img src="/resources/theme/images/icons/circle-question-regular.svg" title="Instructs the unit to operate as AAR tanker. A/A TACAN, radio frequency and callsign set in Settings dialog."></h4>
|
||||
<div id="tanker-on-switch" class="ol-switch"></div>
|
||||
</div>
|
||||
|
||||
<div id="AWACS-on" class="switch-control">
|
||||
<h4>Airborne Early Warning <img src="/resources/theme/images/icons/circle-question-regular.svg" title="Enables datalink and AI radio calls. Radio frequency and callsign set in Settings dialog."></h4>
|
||||
<div id="AWACS-on-switch" class="ol-switch"></div>
|
||||
</div>
|
||||
|
||||
<div id="operate-as" class="switch-control">
|
||||
<h4>Operate as <img src="/resources/theme/images/icons/circle-question-regular.svg" title="Determines if the unit will target red or blue units when performing scenic tasks."></h4>
|
||||
<div id="operate-as-switch" class="ol-switch"></div>
|
||||
</div>
|
||||
|
||||
<div id="ai-on-off" class="switch-control">
|
||||
<h4>Unit active <img src="/resources/theme/images/icons/circle-question-regular.svg" title="Toggling this disables unit AI completely. It will no longer move, react or emit radio waves."></h4>
|
||||
<div id="on-off-switch" class="ol-switch" title=""></div>
|
||||
</div>
|
||||
|
||||
<div id="follow-roads" class="switch-control">
|
||||
<h4>Follow roads <img src="/resources/theme/images/icons/circle-question-regular.svg" title=""></h4>
|
||||
<div id="follow-roads-switch" class="ol-switch"></div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div id="advanced-settings-div">
|
||||
<button id="advanced-settings-button" class="ol-button-settings" data-on-click="showAdvancedSettings">Settings</button>
|
||||
<button class="ol-button-warning" data-on-click="deleteSelectedUnits"><img src="/resources/theme/images/icons/trash-can-regular.svg" inject-svg>Delete</button>
|
||||
<button class="ol-button-warning" data-on-click="explodeSelectedUnits"><img src="/resources/theme/images/icons/explosion-solid.svg" inject-svg></button>
|
||||
</div>
|
||||
|
||||
<div id="rapid-controls" class="ol-panel">
|
||||
<button id="climb" title="Increase units altitude" class="ol-button" data-on-click="selectedUnitsChangeAltitude" data-on-click-params='{ "type": "climb" }'><img src="/resources/theme/images/icons/climb.svg" inject-svg></button>
|
||||
<button id="descend" title="Descrease units altitude" class="ol-button" data-on-click="selectedUnitsChangeAltitude" data-on-click-params='{ "type": "descend" }'><img src="/resources/theme/images/icons/descent.svg" inject-svg></button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div id="unit-info-panel" class="ol-panel" oncontextmenu="return false;">
|
||||
<img src="resources/theme/images/icons/circle-info-solid.svg" inject-svg>
|
||||
|
||||
<div id="general" class="panel-section">
|
||||
<h3 id="unit-name"></h3>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<nav id="command-mode-toolbar" class="ol-panel hide" oncontextmenu="return false;">
|
||||
<img src="resources/theme/images/icons/person-military-pointing-solid.svg" inject-svg>
|
||||
<span id="command-mode"></span>
|
||||
<div id="spawn-points-container">Spawn points<span id="spawn-points"></span></div>
|
||||
<span id="command-mode-phase"></span>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="ol-select-options">
|
||||
<div id="toolbar-summary">
|
||||
<h3>DCS Olympus</h3>
|
||||
<div class="accent-green app-version-number">version v0.4.5-alpha</div>
|
||||
<div class="accent-green app-version-number">version v0.4.6-alpha</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://www.discord.com" target="_blank">Discord</a>
|
||||
@@ -35,13 +35,15 @@
|
||||
</div>
|
||||
|
||||
<div id="map-visibility-options" class="ol-select">
|
||||
<div class="ol-select-value"><img src="resources/theme/images/icons/eye-solid.svg" inject-svg>Options</div>
|
||||
<div class="ol-select-value"><img src="/resources/theme/images/icons/gears-solid.svg" inject-svg>Options</div>
|
||||
<div class="ol-select-options">
|
||||
<!-- This is where the advanced visibility options will be listed -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
<nav class="ol-panel" oncontextmenu="return false;">
|
||||
<img src="resources/theme/images/icons/eye-solid.svg" inject-svg>
|
||||
<div id="unit-visibility-control" class="ol-group ol-navbar-buttons-group">
|
||||
<!-- Here the available visibility controls will be listed -->
|
||||
</div>
|
||||
|
||||