diff --git a/frontend/website/.vscode/launch.json b/frontend/website/.vscode/launch.json deleted file mode 100644 index 50f1a45e..00000000 --- a/frontend/website/.vscode/launch.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "chrome", - "request": "launch", - "name": "Launch Chrome", - "url": "http://localhost:3000", - "webRoot": "${workspaceFolder}/public/", - "sourceMapPathOverrides": { - "src/*": "${workspaceFolder}/src/*" - }, - "preLaunchTask": "watch", - "port": 9222 - } - ] -} \ No newline at end of file diff --git a/frontend/website/.vscode/tasks.json b/frontend/website/.vscode/tasks.json deleted file mode 100644 index 88db0e1e..00000000 --- a/frontend/website/.vscode/tasks.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "check-setup", - "type": "shell", - "command": "cd .. ; ./check_setup.bat", - "isBackground": false - }, - { - "label": "watch", - "type": "shell", - "command": "./scripts/watch.bat", - "isBackground": true, - "problemMatcher":{ - "owner": "custom", - "base": "$tsc-watch", - "background": { - "activeOnStart": true, - "beginsPattern": "watchify", - "endsPattern": "bytes written" - } - }, - "dependsOn": ["check-setup"] - } - ] -} \ No newline at end of file diff --git a/frontend/website/@types/olympus/index.d.ts b/frontend/website/@types/olympus/index.d.ts deleted file mode 100644 index d244f748..00000000 --- a/frontend/website/@types/olympus/index.d.ts +++ /dev/null @@ -1,2639 +0,0 @@ -declare module "map/boxselect" { - export var BoxSelect: (new (...args: any[]) => any) & typeof import("leaflet").Class; -} -declare module "contextmenus/contextmenu" { - import { LatLng } from "leaflet"; - /** Base class for map contextmenus. By default it is empty and requires to be extended. */ - export class ContextMenu { - #private; - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - /** Show the contextmenu on top of the map, usually at the location where the user has clicked on it. - * - * @param x X screen coordinate of the top left corner of the context menu. If undefined, use the old value - * @param y Y screen coordinate of the top left corner of the context menu. If undefined, use the old value - * @param latlng Leaflet latlng object of the mouse click. If undefined, use the old value - */ - show(x?: number | undefined, y?: number | undefined, latlng?: LatLng | undefined): void; - /** Hide the contextmenu - * - */ - hide(): void; - /** - * - * @returns The HTMLElement that contains the contextmenu - */ - getContainer(): HTMLElement | null; - /** - * - * @returns The Leaflet latlng object associated to the click that caused the contextmenu to be shown - */ - getLatLng(): LatLng; - /** - * - * @returns The x coordinate of the top left corner of the menu - */ - getX(): number; - /** - * - * @returns The y coordinate of the top left corner of the menu - */ - getY(): number; - /** Clips the contextmenu, meaning it moves it on the screen to make sure it does not overflow the window. - * - */ - clip(): void; - /** Sets the currently visible submenu - * - * @param menu The name of the currently visibile submenu, or null if no submenu is visible - */ - setVisibleSubMenu(menu: string | null): void; - /** - * - * @returns The name of the currently visible submenu - */ - getVisibleSubMenu(): string | null; - } -} -declare module "controls/control" { - export class Control { - #private; - expectedValue: any; - constructor(container: string | null, options?: any); - show(): void; - hide(): void; - getContainer(): HTMLElement | null; - setExpectedValue(expectedValue: any): void; - resetExpectedValue(): void; - checkExpectedValue(value: any): boolean; - createElement(options?: any): HTMLElement | null; - } -} -declare module "controls/switch" { - import { Control } from "controls/control"; - export class Switch extends Control { - #private; - constructor(ID: string, callback: CallableFunction, initialValue?: boolean); - setValue(newValue: boolean | undefined, ignoreExpectedValue?: boolean): void; - getValue(): boolean | undefined; - } -} -declare module "constants/constants" { - import { LatLng, LatLngBounds } from "leaflet"; - import { MapMarkerVisibilityControl } from "map/map"; - export const UNITS_URI = "units"; - export const WEAPONS_URI = "weapons"; - export const LOGS_URI = "logs"; - export const AIRBASES_URI = "airbases"; - export const BULLSEYE_URI = "bullseyes"; - export const MISSION_URI = "mission"; - export const COMMANDS_URI = "commands"; - export const NONE = "None"; - export const GAME_MASTER = "Game master"; - export const BLUE_COMMANDER = "Blue commander"; - export const RED_COMMANDER = "Red commander"; - export const VISUAL = 1; - export const OPTIC = 2; - export const RADAR = 4; - export const IRST = 8; - export const RWR = 16; - export const DLINK = 32; - export const states: string[]; - export const ROEs: string[]; - export const reactionsToThreat: string[]; - export const emissionsCountermeasures: string[]; - export const ERAS: { - name: string; - chronologicalOrder: number; - }[]; - export const ROEDescriptions: string[]; - export const reactionsToThreatDescriptions: string[]; - export const emissionsCountermeasuresDescriptions: string[]; - export const shotsScatterDescriptions: string[]; - export const shotsIntensityDescriptions: string[]; - export const minSpeedValues: { - [key: string]: number; - }; - export const maxSpeedValues: { - [key: string]: number; - }; - export const speedIncrements: { - [key: string]: number; - }; - export const minAltitudeValues: { - [key: string]: number; - }; - export const maxAltitudeValues: { - [key: string]: number; - }; - export const altitudeIncrements: { - [key: string]: number; - }; - export const minimapBoundaries: { - Nevada: LatLng[]; - Syria: LatLng[]; - Caucasus: LatLng[]; - PersianGulf: LatLng[]; - MarianaIslands: LatLng[]; - Falklands: LatLng[]; - Normandy: LatLng[]; - SinaiMap: LatLng[]; - Kola: LatLng[]; - }; - export const mapBounds: { - Syria: { - bounds: LatLngBounds; - zoom: number; - }; - MarianaIslands: { - bounds: LatLngBounds; - zoom: number; - }; - Nevada: { - bounds: LatLngBounds; - zoom: number; - }; - PersianGulf: { - bounds: LatLngBounds; - zoom: number; - }; - Caucasus: { - bounds: LatLngBounds; - zoom: number; - }; - Falklands: { - bounds: LatLngBounds; - zoom: number; - }; - Normandy: { - bounds: LatLngBounds; - zoom: number; - }; - SinaiMap: { - bounds: LatLngBounds; - zoom: number; - }; - Kola: { - bounds: LatLngBounds; - zoom: number; - }; - }; - export const defaultMapLayers: { - "ArcGIS Satellite": { - urlTemplate: string; - minZoom: number; - maxZoom: number; - attribution: string; - }; - "OpenStreetMap Mapnik": { - urlTemplate: string; - minZoom: number; - maxZoom: number; - attribution: string; - }; - "DCS Marianas Modern": ({ - urlTemplate: string; - minZoom: number; - maxZoom: number; - minNativeZoom?: undefined; - maxNativeZoom?: undefined; - attribution?: undefined; - } | { - urlTemplate: string; - minNativeZoom: number; - maxNativeZoom: number; - minZoom: number; - maxZoom: number; - attribution?: undefined; - } | { - urlTemplate: string; - minNativeZoom: number; - maxNativeZoom: number; - minZoom: number; - maxZoom: number; - attribution: string; - })[]; - }; - export const IDLE = "Idle"; - export const MOVE_UNIT = "Move unit"; - export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area"; - export const visibilityControls: string[]; - export const visibilityControlsTypes: string[][]; - export const visibilityControlsTooltips: string[]; - export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[]; - export const IADSTypes: string[]; - export const IADSDensities: { - [key: string]: number; - }; - export const GROUND_UNIT_AIR_DEFENCE_REGEX: RegExp; - export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out"; - export const SHOW_UNIT_LABELS = "Show unit labels (L)"; - export const SHOW_UNITS_ENGAGEMENT_RINGS = "Show units threat range rings (Q)"; - export const HIDE_UNITS_SHORT_RANGE_RINGS = "Hide short range units threat range rings (R)"; - export const SHOW_UNITS_ACQUISITION_RINGS = "Show units detection range rings (E)"; - export const FILL_SELECTED_RING = "Fill the threat range rings of selected units (F)"; - export const SHOW_UNIT_CONTACTS = "Show selected units contact lines"; - export const SHOW_UNIT_PATHS = "Show selected unit paths"; - export const SHOW_UNIT_TARGETS = "Show selected unit targets"; - export const DCS_LINK_PORT = "DCS Camera link port"; - export enum DataIndexes { - startOfData = 0, - category = 1, - alive = 2, - human = 3, - controlled = 4, - coalition = 5, - country = 6, - name = 7, - unitName = 8, - groupName = 9, - state = 10, - task = 11, - hasTask = 12, - position = 13, - speed = 14, - horizontalVelocity = 15, - verticalVelocity = 16, - heading = 17, - track = 18, - isActiveTanker = 19, - isActiveAWACS = 20, - onOff = 21, - followRoads = 22, - fuel = 23, - desiredSpeed = 24, - desiredSpeedType = 25, - desiredAltitude = 26, - desiredAltitudeType = 27, - leaderID = 28, - formationOffset = 29, - targetID = 30, - targetPosition = 31, - ROE = 32, - reactionToThreat = 33, - emissionsCountermeasures = 34, - TACAN = 35, - radio = 36, - generalSettings = 37, - ammo = 38, - contacts = 39, - activePath = 40, - isLeader = 41, - operateAs = 42, - shotsScatter = 43, - shotsIntensity = 44, - health = 45, - endOfData = 255 - } - export const MGRS_PRECISION_10KM = 2; - export const MGRS_PRECISION_1KM = 3; - export const MGRS_PRECISION_100M = 4; - export const MGRS_PRECISION_10M = 5; - export const MGRS_PRECISION_1M = 6; - export const DELETE_CYCLE_TIME = 0.05; - export const DELETE_SLOW_THRESHOLD = 50; - export const GROUPING_ZOOM_TRANSITION = 13; - export const MAX_SHOTS_SCATTER = 3; - export const MAX_SHOTS_INTENSITY = 3; - export const SHOTS_SCATTER_DEGREES = 10; -} -declare module "map/markers/custommarker" { - import { Map, Marker } from "leaflet"; - import { MarkerOptions } from "leaflet"; - import { LatLngExpression } from "leaflet"; - export class CustomMarker extends Marker { - constructor(latlng: LatLngExpression, options?: MarkerOptions); - onAdd(map: Map): this; - onRemove(map: Map): this; - createIcon(): void; - } -} -declare module "map/coalitionarea/coalitionareahandle" { - import { LatLng } from "leaflet"; - import { CustomMarker } from "map/markers/custommarker"; - export class CoalitionAreaHandle extends CustomMarker { - constructor(latlng: LatLng); - createIcon(): void; - } -} -declare module "map/coalitionarea/coalitionareamiddlehandle" { - import { LatLng } from "leaflet"; - import { CustomMarker } from "map/markers/custommarker"; - export class CoalitionAreaMiddleHandle extends CustomMarker { - constructor(latlng: LatLng); - createIcon(): void; - } -} -declare module "map/coalitionarea/coalitionarea" { - import { LatLng, LatLngExpression, Map, Polygon, PolylineOptions } from "leaflet"; - export class CoalitionArea extends Polygon { - #private; - constructor(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][], options?: PolylineOptions); - setCoalition(coalition: string): void; - getCoalition(): string; - setSelected(selected: boolean): void; - getSelected(): boolean; - setEditing(editing: boolean): void; - getEditing(): boolean; - addTemporaryLatLng(latlng: LatLng): void; - moveActiveVertex(latlng: LatLng): void; - setOpacity(opacity: number): void; - onRemove(map: Map): this; - } -} -declare module "controls/dropdown" { - export class Dropdown { - #private; - constructor(ID: string | null, callback: CallableFunction, options?: string[] | null, defaultText?: string); - getContainer(): HTMLElement; - /** Set the dropdown options strings - * - * @param optionsList List of options. These are the keys that will always be returned on selection - * @param sort Sort method. null means no sorting. "string" performs js default sort. "number" sorts purely by numeric value. - * "string+number" sorts by string, unless two elements are lexicographically identical up to a numeric value (e.g. "SA-2" and "SA-3"), in which case it sorts by number. - * @param labelsList (Optional) List of labels to be shown instead of the keys directly. If provided, the options will be sorted by label. - */ - setOptions(optionsList: string[], sort?: null | "string" | "number" | "string+number", labelsList?: string[] | undefined): void; - getOptionsList(): string[]; - getLabelsList(): string[] | undefined; - /** Manually set the HTMLElements of the dropdown values. Handling of the selection must be performed externally. - * - * @param optionsElements List of elements to be added to the dropdown - */ - setOptionsElements(optionsElements: HTMLElement[]): void; - getOptionElements(): HTMLCollection; - addOptionElement(optionElement: HTMLElement): void; - addHorizontalDivider(): void; - /** Select the active value of the dropdown - * - * @param idx The index of the element to select - * @returns True if the index is valid, false otherwise - */ - selectValue(idx: number): boolean; - reset(): void; - /** Manually set the selected value of the dropdown - * - * @param value The value to select. Must be one of the valid options - */ - setValue(value: string): void; - getValue(): string; - /** Force the selected value of the dropdown. - * - * @param value Any string. Will be shown as selected value even if not one of the options. - */ - forceValue(value: string): void; - getIndex(): number; - clip(): void; - close(): void; - open(): void; - show(): void; - hide(): void; - isHidden(): boolean; - } -} -declare module "mission/airbase" { - import { CustomMarker } from "map/markers/custommarker"; - import { AirbaseChartData, AirbaseOptions } from "interfaces"; - export class Airbase extends CustomMarker { - #private; - constructor(options: AirbaseOptions); - chartDataHasBeenSet(): boolean; - createIcon(): void; - setCoalition(coalition: string): void; - getChartData(): AirbaseChartData; - getCoalition(): string; - setName(name: string): void; - getName(): string; - setChartData(chartData: AirbaseChartData): void; - setProperties(properties: string[]): void; - getProperties(): string[]; - setParkings(parkings: string[]): void; - getParkings(): string[]; - } -} -declare module "interfaces" { - import { LatLng } from "leaflet"; - import { OlympusApp } from "olympusapp"; - import { Airbase } from "mission/airbase"; - export interface OlympusPlugin { - getName: () => string; - initialize: (app: OlympusApp) => boolean; - } - global { - function getOlympusPlugin(): OlympusPlugin; - } - export interface ContextMenuOption { - tooltip: string; - src: string; - callback: CallableFunction; - } - export interface AirbasesData { - airbases: { - [key: string]: any; - }; - sessionHash: string; - time: number; - } - export interface BullseyesData { - bullseyes: { - [key: string]: { - latitude: number; - longitude: number; - coalition: string; - }; - }; - sessionHash: string; - time: number; - } - export interface MissionData { - mission: { - theatre: string; - dateAndTime: DateAndTime; - commandModeOptions: CommandModeOptions; - coalitions: { - red: string[]; - blue: string[]; - }; - }; - time: number; - sessionHash: string; - } - export interface CommandModeOptions { - commandMode: string; - restrictSpawns: boolean; - restrictToCoalition: boolean; - setupTime: number; - spawnPoints: { - red: number; - blue: number; - }; - eras: string[]; - } - export interface DateAndTime { - date: { - Year: number; - Month: number; - Day: number; - }; - time: { - h: number; - m: number; - s: number; - }; - elapsedTime: number; - startTime: number; - } - export interface LogData { - logs: { - [key: string]: string; - }; - sessionHash: string; - time: number; - } - export interface ServerRequestOptions { - time?: number; - commandHash?: string; - } - export interface UnitSpawnTable { - unitType: string; - location: LatLng; - altitude?: number; - loadout?: string; - skill?: string; - liveryID: string; - } - export interface ObjectIconOptions { - showState: boolean; - showVvi: boolean; - showHealth: boolean; - showHotgroup: boolean; - showUnitIcon: boolean; - showShortLabel: boolean; - showFuel: boolean; - showAmmo: boolean; - showSummary: boolean; - showCallsign: boolean; - rotateToHeading: boolean; - } - export interface GeneralSettings { - prohibitJettison: boolean; - prohibitAA: boolean; - prohibitAG: boolean; - prohibitAfterburner: boolean; - prohibitAirWpn: boolean; - } - export interface TACAN { - isOn: boolean; - channel: number; - XY: string; - callsign: string; - } - export interface Radio { - frequency: number; - callsign: number; - callsignNumber: number; - } - export interface Ammo { - quantity: number; - name: string; - guidance: number; - category: number; - missileCategory: number; - } - export interface Contact { - ID: number; - detectionMethod: number; - } - export interface Offset { - x: number; - y: number; - z: number; - } - export interface UnitData { - category: string; - categoryDisplayName: string; - ID: number; - alive: boolean; - human: boolean; - controlled: boolean; - coalition: string; - country: number; - name: string; - unitName: string; - groupName: string; - state: string; - task: string; - hasTask: boolean; - position: LatLng; - speed: number; - horizontalVelocity: number; - verticalVelocity: number; - heading: number; - track: number; - isActiveTanker: boolean; - isActiveAWACS: boolean; - onOff: boolean; - followRoads: boolean; - fuel: number; - desiredSpeed: number; - desiredSpeedType: string; - desiredAltitude: number; - desiredAltitudeType: string; - leaderID: number; - formationOffset: Offset; - targetID: number; - targetPosition: LatLng; - ROE: string; - reactionToThreat: string; - emissionsCountermeasures: string; - TACAN: TACAN; - radio: Radio; - generalSettings: GeneralSettings; - ammo: Ammo[]; - contacts: Contact[]; - activePath: LatLng[]; - isLeader: boolean; - operateAs: string; - shotsScatter: number; - shotsIntensity: number; - health: number; - } - export interface LoadoutItemBlueprint { - name: string; - quantity: number; - effectiveAgainst?: string; - } - export interface LoadoutBlueprint { - fuel: number; - items: LoadoutItemBlueprint[]; - roles: string[]; - code: string; - name: string; - enabled: boolean; - } - export interface UnitBlueprint { - name: string; - enabled: boolean; - coalition: string; - era: string; - label: string; - shortLabel: string; - type?: string; - loadouts?: LoadoutBlueprint[]; - filename?: string; - liveries?: { - [key: string]: { - name: string; - countries: string[]; - }; - }; - cost?: number; - barrelHeight?: number; - muzzleVelocity?: number; - aimTime?: number; - shotsToFire?: number; - shotsBaseInterval?: number; - shotsBaseScatter?: number; - description?: string; - abilities?: string; - tags?: string; - acquisitionRange?: number; - engagementRange?: number; - targetingRange?: number; - aimMethodRange?: number; - alertnessTimeConstant?: number; - canTargetPoint?: boolean; - canRearm?: boolean; - canAAA?: boolean; - indirectFire?: boolean; - markerFile?: string; - unitWhenGrouped?: string; - } - export interface UnitSpawnOptions { - roleType: string; - name: string; - latlng: LatLng; - coalition: string; - count: number; - country: string; - skill: string; - loadout: LoadoutBlueprint | undefined; - airbase: Airbase | undefined; - liveryID: string | undefined; - altitude: number | undefined; - } - export interface AirbaseOptions { - name: string; - position: L.LatLng; - } - export interface AirbaseChartData { - elevation: string; - ICAO: string; - TACAN: string; - runways: AirbaseChartRunwayData[]; - } - export interface AirbaseChartRunwayHeadingData { - [index: string]: { - magHeading: string; - ILS: string; - }; - } - export interface AirbaseChartRunwayData { - headings: AirbaseChartRunwayHeadingData[]; - length: string; - } - export interface Listener { - callback: CallableFunction; - name?: string; - } - export interface ShortcutOptions { - altKey?: boolean; - callback: CallableFunction; - context?: string; - ctrlKey?: boolean; - name?: string; - shiftKey?: boolean; - } - export interface ShortcutKeyboardOptions extends ShortcutOptions { - code: string; - event?: "keydown" | "keyup"; - } - export interface ShortcutMouseOptions extends ShortcutOptions { - button: number; - event: "mousedown" | "mouseup"; - } - export interface Manager { - add: CallableFunction; - } -} -declare module "unit/databases/unitdatabase" { - import { LatLng } from "leaflet"; - import { UnitBlueprint } from "interfaces"; - export abstract class UnitDatabase { - #private; - blueprints: { - [key: string]: UnitBlueprint; - }; - constructor(url?: string); - load(callback: CallableFunction): void; - abstract getCategory(): string; - getByName(name: string): UnitBlueprint | null; - getByLabel(label: string): UnitBlueprint | null; - getBlueprints(includeDisabled?: boolean): { - [key: string]: UnitBlueprint; - }; - getRoles(): string[]; - getTypes(unitFilter?: CallableFunction): string[]; - getEras(): string[]; - getByRange(range: string): UnitBlueprint[]; - getByType(type: string): UnitBlueprint[]; - getByRole(role: string): UnitBlueprint[]; - getLoadoutNamesByRole(name: string, role: string): string[]; - getLiveryNamesByName(name: string): { - name: string; - countries: string[]; - }[]; - getLoadoutByName(name: string, loadoutName: string): import("interfaces").LoadoutBlueprint | null; - generateTestGrid(initialPosition: LatLng): void; - getSpawnPointsByLabel(label: string): number; - getSpawnPointsByName(name: string): number; - getUnkownUnit(name: string): UnitBlueprint; - } -} -declare module "unit/databases/aircraftdatabase" { - import { UnitDatabase } from "unit/databases/unitdatabase"; - export class AircraftDatabase extends UnitDatabase { - constructor(); - getCategory(): string; - getSpawnPointsByName(name: string): number; - } - export var aircraftDatabase: AircraftDatabase; -} -declare module "unit/databases/helicopterdatabase" { - import { UnitDatabase } from "unit/databases/unitdatabase"; - export class HelicopterDatabase extends UnitDatabase { - constructor(); - getSpawnPointsByName(name: string): number; - getCategory(): string; - } - export var helicopterDatabase: HelicopterDatabase; -} -declare module "unit/databases/groundunitdatabase" { - import { UnitDatabase } from "unit/databases/unitdatabase"; - export class GroundUnitDatabase extends UnitDatabase { - constructor(); - getSpawnPointsByName(name: string): number; - getCategory(): string; - } - export var groundUnitDatabase: GroundUnitDatabase; -} -declare module "unit/databases/navyunitdatabase" { - import { UnitDatabase } from "unit/databases/unitdatabase"; - export class NavyUnitDatabase extends UnitDatabase { - constructor(); - getSpawnPointsByName(name: string): number; - getCategory(): string; - } - export var navyUnitDatabase: NavyUnitDatabase; -} -declare module "other/utils" { - import { LatLng, Polygon } from "leaflet"; - import { UnitDatabase } from "unit/databases/unitdatabase"; - import { Dropdown } from "controls/dropdown"; - import { DateAndTime, UnitBlueprint } from "interfaces"; - export function bearing(lat1: number, lon1: number, lat2: number, lon2: number): number; - export function distance(lat1: number, lon1: number, lat2: number, lon2: number): number; - export function bearingAndDistanceToLatLng(lat: number, lon: number, brng: number, dist: number): LatLng; - export function ConvertDDToDMS(D: number, lng: boolean): string; - export function dataPointMap(container: HTMLElement, data: any): void; - export function deg2rad(deg: number): number; - export function rad2deg(rad: number): number; - export function generateUUIDv4(): string; - export function keyEventWasInInput(event: KeyboardEvent): boolean; - export function reciprocalHeading(heading: number): number; - /** - * Prepend numbers to the start of a string - * - * @param num subject number - * @param places places to pad - * @param decimal whether this is a decimal number or not - * - * */ - export const zeroAppend: (num: number, places: number, decimal?: boolean) => string; - export const zeroPad: (num: number, places: number) => string; - export function similarity(s1: string, s2: string): number; - export function editDistance(s1: string, s2: string): any; - export type MGRS = { - bandLetter: string; - columnLetter: string; - easting: string; - groups: string[]; - northing: string; - precision: number; - rowLetter: string; - string: string; - zoneNumber: string; - }; - export function latLngToMGRS(lat: number, lng: number, precision?: number): MGRS | false; - export function latLngToUTM(lat: number, lng: number): any; - export function latLngToMercator(lat: number, lng: number): { - x: number; - y: number; - }; - export function mercatorToLatLng(x: number, y: number): { - lng: number; - lat: number; - }; - export function createDivWithClass(className: string): HTMLDivElement; - export function knotsToMs(knots: number): number; - export function msToKnots(ms: number): number; - export function ftToM(ft: number): number; - export function mToFt(m: number): number; - export function mToNm(m: number): number; - export function nmToM(nm: number): number; - export function nmToFt(nm: number): number; - export function polyContains(latlng: LatLng, polygon: Polygon): boolean; - export function randomPointInPoly(polygon: Polygon): LatLng; - export function polygonArea(polygon: Polygon): number; - export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: { - type?: string; - role?: string; - ranges?: string[]; - eras?: string[]; - coalition?: string; - }): UnitBlueprint | null; - export function getMarkerCategoryByName(name: string): "aircraft" | "helicopter" | "groundunit-sam" | "navyunit" | "groundunit-other"; - 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 getCategoryBlueprintIconSVG(category: string, unitName: string): string | false; - export function base64ToBytes(base64: string): ArrayBufferLike; - export function enumToState(state: number): string; - export function enumToROE(ROE: number): string; - export function enumToReactionToThreat(reactionToThreat: number): string; - export function enumToEmissioNCountermeasure(emissionCountermeasure: number): string; - export function enumToCoalition(coalitionID: number): "" | "blue" | "red" | "neutral"; - export function coalitionToEnum(coalition: string): 0 | 1 | 2; - export function convertDateAndTimeToDate(dateAndTime: DateAndTime): Date; - export function createCheckboxOption(text: string, description: string, checked?: boolean, callback?: CallableFunction, options?: any): HTMLElement; - export function getCheckboxOptions(dropdown: Dropdown): { - [key: string]: boolean; - }; - export function createTextInputOption(text: string, description: string, initialValue: string, type: string, callback?: CallableFunction, options?: any): HTMLElement; - export function getGroundElevation(latlng: LatLng, callback: CallableFunction): void; -} -declare module "controls/slider" { - import { Control } from "controls/control"; - export class Slider extends Control { - #private; - constructor(ID: string | null, minValue: number, maxValue: number, unitOfMeasure: string, callback: CallableFunction, options?: any); - setActive(newActive: boolean): void; - setMinMax(newMinValue: number, newMaxValue: number): void; - setIncrement(newIncrement: number): void; - setValue(newValue: number, ignoreExpectedValue?: boolean): void; - getValue(): number; - setDragged(newDragged: boolean): void; - getDragged(): boolean; - createElement(options?: any): HTMLElement | null; - } -} -declare module "controls/unitspawnmenu" { - import { LatLng } from "leaflet"; - import { Dropdown } from "controls/dropdown"; - import { Slider } from "controls/slider"; - import { UnitDatabase } from "unit/databases/unitdatabase"; - import { Airbase } from "mission/airbase"; - import { UnitSpawnOptions } from "interfaces"; - /** This is the common code for all the unit spawn menus. It is shown both when right clicking on the map and when spawning from airbase. - * - */ - export abstract class UnitSpawnMenu { - #private; - protected showRangeCircles: boolean; - protected unitTypeFilter: (unit: any) => boolean; - protected spawnOptions: UnitSpawnOptions; - constructor(ID: string, unitDatabase: UnitDatabase, orderByRole: boolean); - abstract deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number): void; - getContainer(): HTMLElement; - getVisible(): boolean; - reset(): void; - setCountries(): void; - showCirclesPreviews(): void; - clearCirclesPreviews(): void; - setAirbase(airbase: Airbase | undefined): void; - setLatLng(latlng: LatLng): void; - setMaxUnitCount(maxUnitCount: number): void; - getRoleTypeDrodown(): Dropdown; - getLabelDropdown(): Dropdown; - getCountDropdown(): Dropdown; - getLoadoutDropdown(): Dropdown; - getSkillDropdown(): Dropdown; - getCountryDropdown(): Dropdown; - getLiveryDropdown(): Dropdown; - getLoadoutPreview(): HTMLDivElement; - getAltitudeSlider(): Slider; - setShowLoadout(showLoadout: boolean): void; - setShowSkill(showSkill: boolean): void; - setShowAltitudeSlider(showAltitudeSlider: boolean): void; - } - export class AircraftSpawnMenu extends UnitSpawnMenu { - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number): void; - } - export class HelicopterSpawnMenu extends UnitSpawnMenu { - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number): void; - } - export class GroundUnitSpawnMenu extends UnitSpawnMenu { - protected showRangeCircles: boolean; - protected unitTypeFilter: (unit: any) => boolean; - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number): void; - } - export class AirDefenceUnitSpawnMenu extends GroundUnitSpawnMenu { - protected unitTypeFilter: (unit: any) => boolean; - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - } - export class NavyUnitSpawnMenu extends UnitSpawnMenu { - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number): void; - } -} -declare module "map/markers/smokemarker" { - import { LatLngExpression, MarkerOptions } from "leaflet"; - import { CustomMarker } from "map/markers/custommarker"; - export class SmokeMarker extends CustomMarker { - #private; - constructor(latlng: LatLngExpression, color: string, options?: MarkerOptions); - createIcon(): void; - } -} -declare module "contextmenus/mapcontextmenu" { - import { LatLng } from "leaflet"; - import { ContextMenu } from "contextmenus/contextmenu"; - import { CoalitionArea } from "map/coalitionarea/coalitionarea"; - /** The MapContextMenu is the main contextmenu shown to the user whenever it rightclicks on the map. It is the primary interaction method for the user. - * It allows to spawn units, create explosions and smoke, and edit CoalitionAreas. - */ - export class MapContextMenu extends ContextMenu { - #private; - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - /** Show the contextmenu on top of the map, usually at the location where the user has clicked on it. - * - * @param x X screen coordinate of the top left corner of the context menu - * @param y Y screen coordinate of the top left corner of the context menu - * @param latlng Leaflet latlng object of the mouse click - */ - show(x: number, y: number, latlng: LatLng): false | undefined; - /** If the user rightclicked on a CoalitionArea, it will be given the ability to edit it. - * - * @param coalitionArea The CoalitionArea the user can edit - */ - setCoalitionArea(coalitionArea: CoalitionArea): void; - } -} -declare module "map/markers/targetmarker" { - import { LatLngExpression, MarkerOptions } from "leaflet"; - import { CustomMarker } from "map/markers/custommarker"; - export class TargetMarker extends CustomMarker { - constructor(latlng: LatLngExpression, options?: MarkerOptions); - createIcon(): void; - } -} -declare module "server/dataextractor" { - import { LatLng } from "leaflet"; - import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "interfaces"; - export class DataExtractor { - #private; - constructor(buffer: ArrayBuffer); - setSeekPosition(seekPosition: number): void; - getSeekPosition(): number; - extractBool(): boolean; - extractUInt8(): number; - extractUInt16(): number; - extractUInt32(): number; - extractUInt64(): bigint; - extractFloat64(): number; - extractLatLng(): LatLng; - extractFromBitmask(bitmask: number, position: number): boolean; - extractString(length?: number): string; - extractChar(): string; - extractTACAN(): TACAN; - extractRadio(): Radio; - extractGeneralSettings(): GeneralSettings; - extractAmmo(): Ammo[]; - extractContacts(): Contact[]; - extractActivePath(): LatLng[]; - extractOffset(): Offset; - } -} -declare module "weapon/weapon" { - import { LatLng, Map } from 'leaflet'; - import { CustomMarker } from "map/markers/custommarker"; - import { DataExtractor } from "server/dataextractor"; - import { ObjectIconOptions } from "interfaces"; - export class Weapon extends CustomMarker { - #private; - ID: number; - getAlive(): boolean; - getCoalition(): string; - getName(): string; - getPosition(): LatLng; - getSpeed(): number; - getHeading(): number; - static getConstructor(type: string): typeof Missile | typeof Bomb | undefined; - constructor(ID: number); - getCategory(): string; - /********************** Unit data *************************/ - setData(dataExtractor: DataExtractor): void; - getData(): { - category: string; - ID: number; - alive: boolean; - coalition: string; - name: string; - position: LatLng; - speed: number; - heading: number; - }; - getMarkerCategory(): string; - getIconOptions(): ObjectIconOptions; - setAlive(newAlive: boolean): void; - belongsToCommandedCoalition(): boolean; - getType(): string; - /********************** Icon *************************/ - createIcon(): void; - /********************** Visibility *************************/ - updateVisibility(): void; - setHidden(hidden: boolean): void; - getHidden(): boolean; - setDetectionMethods(newDetectionMethods: number[]): void; - getDetectionMethods(): number[]; - /***********************************************/ - onAdd(map: Map): this; - } - export class Missile extends Weapon { - constructor(ID: number); - getCategory(): string; - getMarkerCategory(): "aircraft" | "missile"; - getIconOptions(): { - showState: boolean; - showVvi: boolean; - showHealth: boolean; - showHotgroup: boolean; - showUnitIcon: boolean; - showShortLabel: boolean; - showFuel: boolean; - showAmmo: boolean; - showSummary: boolean; - showCallsign: boolean; - rotateToHeading: boolean; - }; - } - export class Bomb extends Weapon { - constructor(ID: number); - getCategory(): string; - getMarkerCategory(): "aircraft" | "bomb"; - getIconOptions(): { - showState: boolean; - showVvi: boolean; - showHealth: boolean; - showHotgroup: boolean; - showUnitIcon: boolean; - showShortLabel: boolean; - showFuel: boolean; - showAmmo: boolean; - showSummary: boolean; - showCallsign: boolean; - rotateToHeading: boolean; - }; - } -} -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/group" { - import { Unit } from "unit/unit"; - export class Group { - #private; - constructor(name: string); - getName(): string; - addMember(member: Unit): void; - removeMember(member: Unit): void; - getMembers(): Unit[]; - getLeader(): Unit | undefined; - } -} -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"; - import { Group } from "unit/group"; - import { ContextActionSet } from "unit/contextactionset"; - /** - * Unit class which controls unit behaviour - */ - export abstract class Unit extends CustomMarker { - #private; - ID: number; - getAlive(): boolean; - getHuman(): boolean; - getControlled(): boolean; - getCoalition(): string; - getCountry(): number; - getName(): string; - getUnitName(): string; - getGroupName(): string; - getState(): string; - getTask(): string; - getHasTask(): boolean; - getPosition(): LatLng; - getSpeed(): number; - getHorizontalVelocity(): number; - getVerticalVelocity(): number; - getHeading(): number; - getTrack(): number; - getIsActiveAWACS(): boolean; - getIsActiveTanker(): boolean; - getOnOff(): boolean; - getFollowRoads(): boolean; - getFuel(): number; - getDesiredSpeed(): number; - getDesiredSpeedType(): string; - getDesiredAltitude(): number; - getDesiredAltitudeType(): string; - getLeaderID(): number; - getFormationOffset(): Offset; - getTargetID(): number; - getTargetPosition(): LatLng; - getROE(): string; - getReactionToThreat(): string; - getEmissionsCountermeasures(): string; - getTACAN(): TACAN; - getRadio(): Radio; - getGeneralSettings(): GeneralSettings; - getAmmo(): Ammo[]; - getContacts(): Contact[]; - getActivePath(): LatLng[]; - getIsLeader(): boolean; - getOperateAs(): string; - getShotsScatter(): number; - getShotsIntensity(): number; - getHealth(): number; - static getConstructor(type: string): typeof Aircraft | undefined; - constructor(ID: number); - /********************** 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 - * - */ - abstract appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null): void; - /** - * - * @returns string containing the marker category - */ - abstract getMarkerCategory(): string; - /** - * - * @returns string containing the default marker - */ - abstract getDefaultMarker(): string; - /** Get the category but for display use - for the user. (i.e. has spaces in it) - * - * @returns string - */ - getCategoryLabel(): string; - /********************** 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): void; - /** Get unit data collated into an object - * - * @returns object populated by unit information which can also be retrieved using getters - */ - getData(): UnitData; - /** Get a database of information also in this unit's category - * - * @returns UnitDatabase - */ - getDatabase(): UnitDatabase | null; - /** 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 the number of the hotgroup to which the unit belongss - * - * @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[]; - /** Return the leader of the group - * - * @returns Unit The leader of the group - */ - getGroupLeader(): Unit | null | undefined; - /** Returns whether the user is allowed to command this unit, based on coalition - * - * @returns boolean - */ - belongsToCommandedCoalition(): boolean; - getType(): string; - getSpawnPoints(): number | undefined; - getDatabaseEntry(): import("interfaces").UnitBlueprint | undefined; - getGroup(): Group | null; - setGroup(group: Group | null): void; - drawLines(): void; - checkZoomRedraw(): boolean; - isControlledByDCS(): boolean; - isControlledByOlympus(): boolean; - /********************** Icon *************************/ - createIcon(): void; - /********************** Visibility *************************/ - updateVisibility(): void; - setHidden(hidden: boolean): void; - getHidden(): boolean; - setDetectionMethods(newDetectionMethods: number[]): void; - getDetectionMethods(): number[]; - getLeader(): Unit | null; - canFulfillRole(roles: string | string[]): boolean; - isInViewport(): boolean; - canTargetPoint(): boolean; - canRearm(): boolean; - canAAA(): boolean; - isIndirectFire(): boolean; - isTanker(): boolean; - isAWACS(): boolean; - /********************** Unit commands *************************/ - addDestination(latlng: L.LatLng): void; - clearDestinations(): void; - attackUnit(targetID: number): void; - followUnit(targetID: number, offset: { - "x": number; - "y": number; - "z": number; - }): void; - landAt(latlng: LatLng): void; - changeSpeed(speedChange: string): void; - changeAltitude(altitudeChange: string): void; - setSpeed(speed: number): void; - setSpeedType(speedType: string): void; - setAltitude(altitude: number): void; - setAltitudeType(altitudeType: string): void; - setROE(ROE: string): void; - setReactionToThreat(reactionToThreat: string): void; - setEmissionsCountermeasures(emissionCountermeasure: string): void; - setOnOff(onOff: boolean): void; - setFollowRoads(followRoads: boolean): void; - setOperateAs(operateAs: string): 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; - carpetBomb(latlng: LatLng): void; - bombBuilding(latlng: LatLng): void; - fireAtArea(latlng: LatLng): void; - simulateFireFight(latlng: LatLng, targetGroundElevation: number | null): void; - scenicAAA(): void; - missOnPurpose(): void; - landAtPoint(latlng: LatLng): void; - setShotsScatter(shotsScatter: number): void; - setShotsIntensity(shotsIntensity: number): void; - /***********************************************/ - onAdd(map: Map): this; - onGroupChanged(member: Unit): void; - showFollowOptions(units: Unit[]): void; - applyFollowOptions(formation: string, units: Unit[]): void; - } - export abstract class AirUnit extends Unit { - getIconOptions(): { - showState: boolean; - showVvi: boolean; - showHealth: boolean; - showHotgroup: boolean; - showUnitIcon: boolean; - showShortLabel: boolean; - showFuel: boolean; - showAmmo: boolean; - showSummary: boolean; - showCallsign: boolean; - rotateToHeading: boolean; - }; - appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null): void; - } - export class Aircraft extends AirUnit { - constructor(ID: number); - getCategory(): string; - appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null): void; - getMarkerCategory(): string; - getDefaultMarker(): string; - } - export class Helicopter extends AirUnit { - constructor(ID: number); - getCategory(): string; - appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null): void; - getMarkerCategory(): string; - getDefaultMarker(): string; - } - export class GroundUnit extends Unit { - constructor(ID: number); - getIconOptions(): { - showState: boolean; - showVvi: boolean; - showHealth: boolean; - showHotgroup: boolean; - showUnitIcon: boolean; - showShortLabel: boolean; - showFuel: boolean; - showAmmo: boolean; - showSummary: boolean; - showCallsign: boolean; - rotateToHeading: boolean; - }; - appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null): void; - getCategory(): string; - getType(): string; - getDatabaseEntry(): import("interfaces").UnitBlueprint | undefined; - checkZoomRedraw(): boolean; - getMarkerCategory(): "groundunit-sam" | "groundunit"; - getDefaultMarker(): string; - } - export class NavyUnit extends Unit { - constructor(ID: number); - getIconOptions(): { - showState: boolean; - showVvi: boolean; - showHealth: boolean; - showHotgroup: boolean; - showUnitIcon: boolean; - showShortLabel: boolean; - showFuel: boolean; - showAmmo: boolean; - showSummary: boolean; - showCallsign: boolean; - rotateToHeading: boolean; - }; - appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null): void; - getCategory(): string; - getType(): string; - getMarkerCategory(): string; - getDefaultMarker(): string; - } -} -declare module "unit/contextaction" { - import { Unit } from "unit/unit"; - export interface ContextActionOptions { - isScenic?: boolean; - } - export class ContextAction { - #private; - constructor(id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean | undefined, options: ContextActionOptions); - addUnit(unit: Unit): void; - getId(): string; - getLabel(): string; - getOptions(): ContextActionOptions; - getDescription(): string; - getCallback(): CallableFunction | null; - executeCallback(): void; - getHideContextAfterExecution(): boolean; - } -} -declare module "unit/contextactionset" { - import { ContextAction, ContextActionOptions } from "unit/contextaction"; - import { Unit } from "unit/unit"; - export class ContextActionSet { - #private; - constructor(); - addContextAction(unit: Unit, id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution?: boolean, options?: ContextActionOptions): void; - getContextActions(): { - [key: string]: ContextAction; - }; - } -} -declare module "contextmenus/unitcontextmenu" { - import { ContextActionSet } from "unit/contextactionset"; - import { ContextMenu } from "contextmenus/contextmenu"; - /** The UnitContextMenu is shown when the user rightclicks on a unit. It dynamically presents the user with possible actions to perform on the unit. */ - export class UnitContextMenu extends ContextMenu { - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - /** Set the options that will be presented to the user in the contextmenu - * - * @param options Dictionary element containing the text and tooltip of the options shown in the menu - * @param callback Callback that will be called when the user clicks on one of the options - */ - setContextActions(contextActionSet: ContextActionSet): void; - } -} -declare module "contextmenus/airbasecontextmenu" { - import { Airbase } from "mission/airbase"; - import { ContextMenu } from "contextmenus/contextmenu"; - /** This context menu is shown to the user when the airbase marker is right clicked on the map. - * It allows the user to inspect information about the airbase, as well as allowing to spawn units from the airbase itself and land units on it. */ - export class AirbaseContextMenu extends ContextMenu { - #private; - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - /** Sets the airbase for which data will be shown in the context menu - * - * @param airbase The airbase for which data will be shown in the context menu. Note: the airbase must be present in the public/databases/airbases/.json database. - */ - setAirbase(airbase: Airbase): void; - } -} -declare module "map/markers/destinationpreviewmarker" { - import { LatLngExpression, MarkerOptions } from "leaflet"; - import { CustomMarker } from "map/markers/custommarker"; - export class DestinationPreviewMarker extends CustomMarker { - constructor(latlng: LatLngExpression, options?: MarkerOptions); - createIcon(): void; - } -} -declare module "map/markers/temporaryunitmarker" { - import { CustomMarker } from "map/markers/custommarker"; - import { LatLng } from "leaflet"; - export class TemporaryUnitMarker extends CustomMarker { - #private; - constructor(latlng: LatLng, name: string, coalition: string, commandHash?: string); - setCommandHash(commandHash: string): void; - createIcon(): void; - } -} -declare module "map/clickableminimap" { - import { MiniMap, MiniMapOptions } from "leaflet-control-mini-map"; - export class ClickableMiniMap extends MiniMap { - constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions); - getMap(): any; - } -} -declare module "contextmenus/coalitionareacontextmenu" { - import { LatLng } from "leaflet"; - import { CoalitionArea } from "map/coalitionarea/coalitionarea"; - import { ContextMenu } from "contextmenus/contextmenu"; - /** This context menu allows the user to edit or delete a CoalitionArea. Moreover, it allows the user to create a IADS automatically using the CoalitionArea as bounds. */ - export class CoalitionAreaContextMenu extends ContextMenu { - #private; - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - /** - * - * @param x X screen coordinate of the top left corner of the context menu - * @param y Y screen coordinate of the top left corner of the context menu - * @param latlng Leaflet latlng object of the mouse click - */ - show(x: number, y: number, latlng: LatLng): void; - /** Set the CoalitionArea object the user will be able to edit using this menu - * - * @param coalitionArea The CoalitionArea object to edit - */ - setCoalitionArea(coalitionArea: CoalitionArea): void; - /** Get the CoalitionArea object the contextmenu is editing - * - * @returns The CoalitionArea the contextmenu is editing - */ - getCoalitionArea(): CoalitionArea | null; - } -} -declare module "map/coalitionarea/drawingcursor" { - import { CustomMarker } from "map/markers/custommarker"; - export class DrawingCursor extends CustomMarker { - constructor(); - createIcon(): void; - } -} -declare module "contextmenus/airbasespawnmenu" { - import { ContextMenu } from "contextmenus/contextmenu"; - import { Airbase } from "mission/airbase"; - /** This context menu is shown when the user wants to spawn a new aircraft or helicopter from the ground at an airbase. - * It is shown by clicking on the "spawn" button of a AirbaseContextMenu. */ - export class AirbaseSpawnContextMenu extends ContextMenu { - #private; - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - /** Show the context menu - * - * @param x X screen coordinate of the top left corner of the context menu - * @param y Y screen coordinate of the top left corner of the context menu - */ - show(x: number | undefined, y: number | undefined): void; - /** Sets the airbase at which the new unit will be spawned - * - * @param airbase The airbase at which the new unit will be spawned. Note: if the airbase has no suitable parking spots, the airplane may be spawned on the runway, or spawning may fail. - */ - setAirbase(airbase: Airbase): void; - } -} -declare module "map/touchboxselect" { - export var TouchBoxSelect: (new (...args: any[]) => any) & typeof import("leaflet").Class; -} -declare module "map/markers/destinationpreviewHandle" { - import { LatLng } from "leaflet"; - import { CustomMarker } from "map/markers/custommarker"; - export class DestinationPreviewHandle extends CustomMarker { - constructor(latlng: LatLng); - createIcon(): void; - } -} -declare module "map/map" { - import * as L from "leaflet"; - import { MapContextMenu } from "contextmenus/mapcontextmenu"; - import { UnitContextMenu } from "contextmenus/unitcontextmenu"; - import { AirbaseContextMenu } from "contextmenus/airbasecontextmenu"; - import { Airbase } from "mission/airbase"; - import { Unit } from "unit/unit"; - import { TemporaryUnitMarker } from "map/markers/temporaryunitmarker"; - import { CoalitionArea } from "map/coalitionarea/coalitionarea"; - import { CoalitionAreaContextMenu } from "contextmenus/coalitionareacontextmenu"; - import { AirbaseSpawnContextMenu } from "contextmenus/airbasespawnmenu"; - export type MapMarkerVisibilityControl = { - "category"?: string; - "image": string; - "isProtected"?: boolean; - "name": string; - "protectable"?: boolean; - "toggles": string[]; - "tooltip": string; - }; - export class Map extends L.Map { - #private; - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - addVisibilityOption(option: string, defaultValue: boolean | number | string, options?: { - [key: string]: any; - }): void; - setLayer(layerName: string): void; - getLayers(): string[]; - setState(state: string): void; - getState(): string; - deselectAllCoalitionAreas(): void; - deleteCoalitionArea(coalitionArea: CoalitionArea): void; - setHiddenType(key: string, value: boolean): void; - getHiddenTypes(): string[]; - hideAllContextMenus(): void; - showMapContextMenu(x: number, y: number, latlng: L.LatLng): void; - hideMapContextMenu(): void; - getMapContextMenu(): MapContextMenu; - showUnitContextMenu(x?: number | undefined, y?: number | undefined, latlng?: L.LatLng | undefined): void; - getUnitContextMenu(): UnitContextMenu; - hideUnitContextMenu(): void; - showAirbaseContextMenu(airbase: Airbase, x?: number | undefined, y?: number | undefined, latlng?: L.LatLng | undefined): void; - getAirbaseContextMenu(): AirbaseContextMenu; - hideAirbaseContextMenu(): void; - showAirbaseSpawnMenu(airbase: Airbase, x?: number | undefined, y?: number | undefined, latlng?: L.LatLng | undefined): void; - getAirbaseSpawnMenu(): AirbaseSpawnContextMenu; - hideAirbaseSpawnMenu(): void; - showCoalitionAreaContextMenu(x: number, y: number, latlng: L.LatLng, coalitionArea: CoalitionArea): void; - getCoalitionAreaContextMenu(): CoalitionAreaContextMenu; - hideCoalitionAreaContextMenu(): void; - getMousePosition(): L.Point; - getMouseCoordinates(): L.LatLng; - centerOnUnit(ID: number | null): void; - getCenteredOnUnit(): Unit | null; - setTheatre(theatre: string): void; - getMiniMapLayerGroup(): L.LayerGroup; - handleMapPanning(e: any): void; - addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string, commandHash?: string): TemporaryUnitMarker; - getSelectedCoalitionArea(): CoalitionArea | undefined; - bringCoalitionAreaToBack(coalitionArea: CoalitionArea): void; - getVisibilityOptions(): { - [key: string]: string | number | boolean; - }; - isZooming(): boolean; - getPreviousZoom(): number; - getIsUnitProtected(unit: Unit): boolean; - getMapMarkerVisibilityControls(): MapMarkerVisibilityControl[]; - setSlaveDCSCamera(newSlaveDCSCamera: boolean): void; - setCameraControlMode(newCameraControlMode: string): void; - } -} -declare module "mission/bullseye" { - import { CustomMarker } from "map/markers/custommarker"; - export class Bullseye extends CustomMarker { - #private; - createIcon(): void; - setCoalition(coalition: string): void; - getCoalition(): string; - } -} -declare module "context/context" { - export interface ContextInterface { - allowUnitCopying?: boolean; - allowUnitPasting?: boolean; - useSpawnMenu?: boolean; - useUnitControlPanel?: boolean; - useUnitInfoPanel?: boolean; - } - export class Context { - #private; - constructor(config: ContextInterface); - getAllowUnitCopying(): boolean; - getAllowUnitPasting(): boolean; - getUseSpawnMenu(): boolean; - getUseUnitControlPanel(): boolean; - getUseUnitInfoPanel(): boolean; - } -} -declare module "other/manager" { - export class Manager { - #private; - constructor(); - add(name: string, item: any): this; - get(name: string): any; - getAll(): { - [key: string]: any; - }; - } -} -declare module "other/eventsmanager" { - import { Manager } from "other/manager"; - export abstract class EventsManager extends Manager { - constructor(); - } -} -declare module "panels/paneleventsmanager" { - import { Listener } from "interfaces"; - import { EventsManager } from "other/eventsmanager"; - export class PanelEventsManager extends EventsManager { - constructor(); - on(eventName: string, listener: Listener): void; - trigger(eventName: string, contextData: object): void; - } -} -declare module "panels/panel" { - import { PanelEventsManager } from "panels/paneleventsmanager"; - export abstract class Panel { - #private; - constructor(ID: string); - show(): void; - hide(): void; - toggle(): void; - getElement(): HTMLElement; - getVisible(): boolean; - getEventsManager(): PanelEventsManager; - } -} -declare module "popups/popup" { - import { Panel } from "panels/panel"; - export class PopupMessage { - #private; - constructor(text: string, fateTime: number); - getElement(): HTMLDivElement; - } - export class Popup extends Panel { - #private; - constructor(ID: string, stackAfter?: number); - setFadeTime(fadeTime: number): void; - setText(text: string): void; - } -} -declare module "mission/missionmanager" { - import { Airbase } from "mission/airbase"; - import { Bullseye } from "mission/bullseye"; - import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionData } from "interfaces"; - /** The MissionManager */ - export class MissionManager { - #private; - constructor(); - /** Update location of bullseyes - * - * @param object - */ - updateBullseyes(data: BullseyesData): void; - /** Update airbase information - * - * @param object - */ - updateAirbases(data: AirbasesData): void; - /** Update mission information - * - * @param object - */ - updateMission(data: MissionData): void; - /** Get the bullseyes set in this theatre - * - * @returns object - */ - getBullseyes(): { - [name: string]: Bullseye; - }; - /** Get the airbases in this theatre - * - * @returns object - */ - getAirbases(): { - [name: string]: Airbase; - }; - /** Get the options/settings as set in the command mode - * - * @returns object - */ - getCommandModeOptions(): CommandModeOptions; - /** Get the current date and time of the mission (based on local time) - * - * @returns object - */ - getDateAndTime(): DateAndTime; - /** - * Get the number of seconds left of setup time - * @returns number - */ - getRemainingSetupTime(): number; - /** Get an object with the coalitions in it - * - * @returns object - */ - getCoalitions(): { - red: string[]; - blue: string[]; - }; - /** Get the current theatre (map) name - * - * @returns string - */ - getTheatre(): string; - getAvailableSpawnPoints(): number; - getCommandedCoalition(): "blue" | "red" | "all"; - refreshSpawnPoints(): void; - setSpentSpawnPoints(spawnPoints: number): void; - showCommandModeDialog(): void; - } -} -declare module "panels/connectionstatuspanel" { - import { Panel } from "panels/panel"; - export class ConnectionStatusPanel extends Panel { - #private; - constructor(ID: string); - setElapsedTime(time: string): void; - setMissionTime(time: string): void; - showDisconnected(): void; - showConnected(): void; - showServerPaused(): void; - } -} -declare module "panels/hotgrouppanel" { - import { Panel } from "panels/panel"; - export class HotgroupPanel extends Panel { - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - refreshHotgroups(): void; - addHotgroup(hotgroup: number): void; - removeHotgroup(hotgroup: number): void; - } -} -declare module "panels/mouseinfopanel" { - import { Panel } from "panels/panel"; - export class MouseInfoPanel extends Panel { - #private; - constructor(ID: string); - } -} -declare module "panels/logpanel" { - import { Panel } from "panels/panel"; - export class LogPanel extends Panel { - #private; - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - show(): void; - appendLogs(logs: { - [key: string]: string; - }): void; - appendLog(log: string): void; - } -} -declare module "panels/serverstatuspanel" { - import { Panel } from "panels/panel"; - export class ServerStatusPanel extends Panel { - constructor(ID: string); - update(frameRate: number, load: number): void; - } -} -declare module "panels/unitcontrolpanel" { - import { Panel } from "panels/panel"; - export class UnitControlPanel extends Panel { - #private; - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string); - show(): void; - addButtons(): void; - update(): void; - } -} -declare module "panels/unitinfopanel" { - import { Panel } from "panels/panel"; - export class UnitInfoPanel extends Panel { - #private; - constructor(ID: string); - show(): void; - } -} -declare module "plugin/pluginmanager" { - import { Manager } from "other/manager"; - /** The plugins manager is responsible for loading and initializing all the plugins. Plugins are located in the public/plugins folder. - * Each plugin must be comprised of a single folder containing a index.js file. Each plugin must set the globalThis.getOlympusPlugin variable to - * return a valid class implementing the OlympusPlugin interface. - */ - export class PluginsManager extends Manager { - #private; - constructor(); - } -} -declare module "shortcut/shortcut" { - import { ShortcutKeyboardOptions, ShortcutMouseOptions, ShortcutOptions } from "interfaces"; - export abstract class Shortcut { - #private; - constructor(config: ShortcutOptions); - getConfig(): ShortcutOptions; - } - export class ShortcutKeyboard extends Shortcut { - constructor(config: ShortcutKeyboardOptions); - } - export class ShortcutMouse extends Shortcut { - constructor(config: ShortcutMouseOptions); - } -} -declare module "shortcut/shortcutmanager" { - import { ShortcutKeyboardOptions, ShortcutMouseOptions } from "interfaces"; - import { Manager } from "other/manager"; - export class ShortcutManager extends Manager { - #private; - constructor(); - add(name: string, shortcut: any): this; - addKeyboardShortcut(name: string, shortcutKeyboardOptions: ShortcutKeyboardOptions): this; - addMouseShortcut(name: string, shortcutMouseOptions: ShortcutMouseOptions): this; - getKeysBeingHeld(): string[]; - keyComboMatches(combo: string[]): boolean; - onKeyDown(callback: CallableFunction): void; - 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/databases/citiesdatabase" { - export var citiesDatabase: { - lat: number; - lng: number; - pop: number; - }[]; -} -declare module "dialog/dialog" { - import { Panel } from "panels/panel"; - export class Dialog extends Panel { - constructor(element: string); - hide(): void; - show(): void; - } -} -declare module "unit/importexport/unitdatafile" { - import { Dialog } from "dialog/dialog"; - export abstract class UnitDataFile { - #private; - protected data: any; - protected dialog: Dialog; - constructor(); - buildCategoryCoalitionTable(): void; - getData(): any; - } -} -declare module "unit/importexport/unitdatafileexport" { - import { Dialog } from "dialog/dialog"; - import { Unit } from "unit/unit"; - import { UnitDataFile } from "unit/importexport/unitdatafile"; - export class UnitDataFileExport extends UnitDataFile { - #private; - protected data: any; - protected dialog: Dialog; - constructor(elementId: string); - /** - * Show the form to start the export journey - */ - showForm(units: Unit[]): void; - } -} -declare module "schemas/schema" { - import Ajv from "ajv"; - import { AnySchemaObject } from "ajv/dist/core"; - abstract class JSONSchemaValidator { - #private; - constructor(schema: AnySchemaObject); - getAjv(): Ajv; - getCompiledValidator(): any; - getErrors(): any; - getSchema(): AnySchemaObject; - validate(data: any): any; - } - export class ImportFileJSONSchemaValidator extends JSONSchemaValidator { - constructor(); - } -} -declare module "unit/importexport/unitdatafileimport" { - import { Dialog } from "dialog/dialog"; - import { UnitDataFile } from "unit/importexport/unitdatafile"; - export class UnitDataFileImport extends UnitDataFile { - #private; - protected data: any; - protected dialog: Dialog; - constructor(elementId: string); - selectFile(): void; - } -} -declare module "unit/unitsmanager" { - import { LatLng, LatLngBounds } from "leaflet"; - import { Unit } from "unit/unit"; - import { CoalitionArea } from "map/coalitionarea/coalitionarea"; - import { UnitSpawnTable } from "interfaces"; - /** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only - * result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows - * to avoid client/server and client/client inconsistencies. - */ - export class UnitsManager { - #private; - constructor(); - /** - * - * @returns All the existing units, both alive and dead - */ - getUnits(): { - [ID: number]: Unit; - }; - /** Get a specific unit by ID - * - * @param ID ID of the unit. The ID shall be the same as the unit ID in DCS. - * @returns Unit object, or null if no unit with said ID exists. - */ - getUnitByID(ID: number): Unit | null; - /** Returns all the units that belong to a hotgroup - * - * @param hotgroup Hotgroup number - * @returns Array of units that belong to hotgroup - */ - getUnitsByHotgroup(hotgroup: number): Unit[]; - /** Add a new unit to the manager - * - * @param ID ID of the new unit - * @param category Either "Aircraft", "Helicopter", "GroundUnit", or "NavyUnit". Determines what class will be used to create the new unit accordingly. - */ - addUnit(ID: number, category: string): void; - /** Sort units segregated groups based on controlling type and protection, if DCS-controlled - * - * @param units - * @returns Object - */ - segregateUnits(units: Unit[]): { - [key: string]: []; - }; - /** - * - * @param numOfProtectedUnits number - */ - showProtectedUnitsPopup(numOfProtectedUnits: number): void; - /** Update the data of all the units. The data is directly decoded from the binary buffer received from the REST Server. This is necessary for performance and bandwidth reasons. - * - * @param buffer The arraybuffer, encoded according to the ICD defined in: TODO Add reference to ICD - * @returns The decoded updateTime of the data update. - */ - update(buffer: ArrayBuffer): number; - /** Set a unit as "selected", which will allow to perform operations on it, like giving it a destination, setting it to attack a target, and so on - * - * @param ID The ID of the unit to select - * @param deselectAllUnits If true, the unit will be the only selected unit - */ - selectUnit(ID: number, deselectAllUnits?: boolean): void; - /** Select all visible units inside a bounding rectangle - * - * @param bounds Leaflet bounds object defining the selection area - */ - selectFromBounds(bounds: LatLngBounds): void; - /** Select units by hotgroup. A hotgroup can be created to quickly select multiple units using keyboard bindings - * - * @param hotgroup The hotgroup number - */ - selectUnitsByHotgroup(hotgroup: number, deselectAllUnits?: boolean): void; - /** Get all the currently selected units - * - * @param options Selection options - * @returns Array of selected units - */ - getSelectedUnits(options?: { - excludeHumans?: boolean; - excludeProtected?: boolean; - onlyOnePerGroup?: boolean; - showProtectionReminder?: boolean; - }): Unit[]; - /** Deselects all currently selected units - * - */ - deselectAllUnits(): void; - /** Deselect a specific unit - * - * @param ID ID of the unit to deselect - */ - deselectUnit(ID: number): void; - /** This function allows to quickly determine the categories (Aircraft, Helicopter, GroundUnit, NavyUnit) of an array units. This allows to enable/disable specific controls which can only be applied - * to specific categories. - * - * @param units Array of units of which to retrieve the categories - * @returns Array of categories. Each category is present only once. - */ - getUnitsCategories(units: Unit[]): string[]; - /** This function returns the value of a variable for each of the units in the input array. If all the units have the same value, returns the value, else returns undefined. This function is useful to - * present units data in the control panel, which will print a specific value only if it is the same for all the units. If the values are different, the control panel will show a "mixed values" value, or similar. - * - * @param variableGetter CallableFunction that returns the requested variable. Example: getUnitsVariable((unit: Unit) => unit.getName(), foo) will return a string value if all the units have the same name, otherwise it will return undefined. - * @param units Array of units of which to retrieve the variable - * @returns The value of the variable if all units have the same value, else undefined - */ - getUnitsVariable(variableGetter: CallableFunction, units: Unit[]): any; - /** For a given unit, it returns if and how it is being detected by other units. NOTE: this function will return how a unit is being detected, i.e. how other units are detecting it. It will not return - * what the unit is detecting. - * - * @param unit The unit of which to retrieve the "detected" methods. - * @returns Array of detection methods - */ - getUnitDetectedMethods(unit: Unit): number[]; - /*********************** Unit actions on selected units ************************/ - /** Give a new destination to the selected units - * - * @param latlng Position of the new destination - * @param mantainRelativePosition If true, the selected units will mantain their relative positions when reaching the target. This is useful to maintain a formation for groun/navy units - * @param rotation Rotation in radians by which the formation will be rigidly rotated. E.g. a ( V ) formation will look like this ( < ) if rotated pi/4 radians (90 degrees) - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - addDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number, units?: Unit[] | null): void; - /** Clear the destinations of all the selected units - * - */ - clearDestinations(units?: Unit[] | null): void; - /** Instruct all the selected units to land at a specific location - * - * @param latlng Location where to land at - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - landAt(latlng: LatLng, units?: Unit[] | null): void; - /** Instruct all the selected units to change their speed - * - * @param speedChange Speed change, either "stop", "slow", or "fast". The specific value depends on the unit category - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - changeSpeed(speedChange: string, units?: Unit[] | null): void; - /** Instruct all the selected units to change their altitude - * - * @param altitudeChange Altitude change, either "climb" or "descend". The specific value depends on the unit category - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - changeAltitude(altitudeChange: string, units?: Unit[] | null): void; - /** Set a specific speed to all the selected units - * - * @param speed Value to set, in m/s - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setSpeed(speed: number, units?: Unit[] | null): void; - /** Set a specific speed type to all the selected units - * - * @param speedType Value to set, either "CAS" or "GS". If "CAS" is selected, the unit will try to maintain the selected Calibrated Air Speed, but DCS will still only maintain a Ground Speed value so errors may arise depending on wind. - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setSpeedType(speedType: string, units?: Unit[] | null): void; - /** Set a specific altitude to all the selected units - * - * @param altitude Value to set, in m - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setAltitude(altitude: number, units?: Unit[] | null): void; - /** Set a specific altitude type to all the selected units - * - * @param altitudeType Value to set, either "ASL" or "AGL". If "AGL" is selected, the unit will try to maintain the selected Above Ground Level altitude. Due to a DCS bug, this will only be true at the final position. - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setAltitudeType(altitudeType: string, units?: Unit[] | null): void; - /** Set a specific ROE to all the selected units - * - * @param ROE Value to set, see constants for acceptable values - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setROE(ROE: string, units?: Unit[] | null): void; - /** Set a specific reaction to threat to all the selected units - * - * @param reactionToThreat Value to set, see constants for acceptable values - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setReactionToThreat(reactionToThreat: string, units?: Unit[] | null): void; - /** Set a specific emissions & countermeasures to all the selected units - * - * @param emissionCountermeasure Value to set, see constants for acceptable values - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setEmissionsCountermeasures(emissionCountermeasure: string, units?: Unit[] | null): void; - /** Turn selected units on or off, only works on ground and navy units - * - * @param onOff If true, the unit will be turned on - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setOnOff(onOff: boolean, units?: Unit[] | null): void; - /** Instruct the selected units to follow roads, only works on ground units - * - * @param followRoads If true, units will follow roads - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setFollowRoads(followRoads: boolean, units?: Unit[] | null): void; - /** Instruct selected units to operate as a certain coalition - * - * @param operateAsBool If true, units will operate as blue - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setOperateAs(operateAsBool: boolean, units?: Unit[] | null): void; - /** Instruct units to attack a specific unit - * - * @param ID ID of the unit to attack - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - attackUnit(ID: number, units?: Unit[] | null): void; - /** Instruct units to refuel at the nearest tanker, if possible. Else units will RTB - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - refuel(units?: Unit[] | null): void; - /** Instruct the selected units to follow another unit in a formation. Only works for aircrafts and helicopters. - * - * @param ID ID of the unit to follow - * @param offset Optional parameter, defines a static offset. X: front-rear, positive front, Y: top-bottom, positive top, Z: left-right, positive right - * @param formation Optional parameter, defines a predefined formation type. Values are: "trail", "echelon-lh", "echelon-rh", "line-abreast-lh", "line-abreast-rh", "front", "diamond" - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - followUnit(ID: number, offset?: { - "x": number; - "y": number; - "z": number; - }, formation?: string, units?: Unit[] | null): void; - /** Instruct the selected units to perform precision bombing of specific coordinates - * - * @param latlng Location to bomb - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - bombPoint(latlng: LatLng, units?: Unit[] | null): void; - /** Instruct the selected units to perform carpet bombing of specific coordinates - * - * @param latlng Location to bomb - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - carpetBomb(latlng: LatLng, units?: Unit[] | null): void; - /** Instruct the selected units to fire at specific coordinates - * - * @param latlng Location to fire at - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - fireAtArea(latlng: LatLng, units?: Unit[] | null): void; - /** Instruct the selected units to simulate a fire fight at specific coordinates - * - * @param latlng Location to fire at - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - simulateFireFight(latlng: LatLng, units?: Unit[] | null): void; - /** Instruct units to enter into scenic AAA mode. Units will shoot in the air without aiming - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - scenicAAA(units?: Unit[] | null): void; - /** Instruct units to enter into dynamic accuracy/miss on purpose mode. Units will aim to the nearest enemy unit but not precisely. - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - missOnPurpose(units?: Unit[] | null): void; - /** Instruct units to land at specific point - * - * @param latlng Point where to land - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - landAtPoint(latlng: LatLng, units?: Unit[] | null): void; - /** Set a specific shots scatter to all the selected units - * - * @param shotsScatter Value to set - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setShotsScatter(shotsScatter: number, units?: Unit[] | null): void; - /** Set a specific shots intensity to all the selected units - * - * @param shotsScatter Value to set - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setShotsIntensity(shotsIntensity: number, units?: Unit[] | null): void; - /*********************** Control operations on selected units ************************/ - /** See getUnitsCategories for more info - * - * @returns Category array of the selected units. - */ - getSelectedUnitsCategories(): string[]; - /** See getUnitsVariable for more info - * - * @param variableGetter CallableFunction that returns the requested variable. Example: getUnitsVariable((unit: Unit) => unit.getName(), foo) will return a string value if all the units have the same name, otherwise it will return undefined. - * @returns The value of the variable if all units have the same value, else undefined - */ - getSelectedUnitsVariable(variableGetter: CallableFunction): any; - /** Groups the selected units in a single (DCS) group, if all the units have the same category - * - */ - createGroup(units?: Unit[] | null): void; - /** Set the hotgroup for the selected units. It will be the only hotgroup of the unit - * - * @param hotgroup Hotgroup number - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setHotgroup(hotgroup: number, units?: Unit[] | null): void; - /** Add the selected units to a hotgroup. Units can be in multiple hotgroups at the same type - * - * @param hotgroup Hotgroup number - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - addToHotgroup(hotgroup: number, units?: Unit[] | null): void; - /** Delete the selected units - * - * @param explosion If true, the unit will be deleted using an explosion - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - * @returns - */ - delete(explosion?: boolean, explosionType?: string, units?: Unit[] | null): 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 - * @param rotation Rotation of the group, in radians - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - * @returns Array of positions for each unit, in order - */ - computeGroupDestination(latlng: LatLng, rotation: number, units?: Unit[] | null): { - [key: number]: LatLng; - }; - /** Copy the selected units and store their properties in memory - * - */ - copy(units?: Unit[] | null): void; - /*********************** Unit manipulation functions ************************/ - /** Paste the copied units - * - * @returns True if units were pasted successfully - */ - paste(): false | undefined; - /** Automatically create an Integrated Air Defence System from a CoalitionArea object. The units will be mostly focused around big cities. The bigger the city, the larger the amount of units created next to it. - * If the CoalitionArea does not contain any city, no units will be created - * - * @param coalitionArea Boundaries of the IADS - * @param types Array of unit types to add to the IADS, e.g. AAA, SAM, flak, MANPADS - * @param eras Array of eras to which the units added to the IADS can belong - * @param ranges Array of weapon ranges the units can have - * @param density Value between 0 and 100, controls the amout of units created - * @param distribution Value between 0 and 100, controls how "scattered" the units will be - */ - createIADS(coalitionArea: CoalitionArea, types: { - [key: string]: boolean; - }, eras: { - [key: string]: boolean; - }, ranges: { - [key: string]: boolean; - }, density: number, distribution: number, forceCoalition: boolean): void; - /** Export all the ground and navy units to file. Does not work on Aircraft and Helicopter units. - * TODO: Extend to aircraft and helicopters - */ - exportToFile(): void; - /** Import ground and navy units from file - * TODO: extend to support aircraft and helicopters - */ - importFromFile(): void; - /** Spawn a new group of units - * - * @param category Category of the new units - * @param units Array of unit tables - * @param coalition Coalition to which the new units will belong - * @param immediate If true the command will be performed immediately, but this may cause lag on the server - * @param airbase If true, the location of the units will be ignored and the units will spawn at the given airbase. Only works for aircrafts and helicopters - * @param country Set the country of the units. If empty string, the country will be assigned automatically - * @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, immediate?: boolean, airbase?: string, country?: string, callback?: CallableFunction): boolean; - } -} -declare module "weapon/weaponsmanager" { - import { Weapon } from "weapon/weapon"; - /** The WeaponsManager handles the creation and update of weapons. Data is strictly updated by the server ONLY. */ - export class WeaponsManager { - #private; - constructor(); - /** - * - * @returns All the existing weapons, both active and destroyed - */ - getWeapons(): { - [ID: number]: Weapon; - }; - /** Get a weapon by ID - * - * @param ID ID of the weapon - * @returns Weapon object, or null if input ID does not exist - */ - getWeaponByID(ID: number): Weapon | null; - /** Add a new weapon to the manager - * - * @param ID ID of the new weapon - * @param category Either "Missile" or "Bomb". Determines what class will be used to create the new unit accordingly. - */ - addWeapon(ID: number, category: string): void; - /** Update the data of all the weapons. The data is directly decoded from the binary buffer received from the REST Server. This is necessary for performance and bandwidth reasons. - * - * @param buffer The arraybuffer, encoded according to the ICD defined in: TODO Add reference to ICD - * @returns The decoded updateTime of the data update. - */ - update(buffer: ArrayBuffer): number; - /** For a given weapon, it returns if and how it is being detected by other units. NOTE: this function will return how a weapon is being detected, i.e. how other units are detecting it. It will not return - * what the weapon is detecting (mostly because weapons can't detect units). - * - * @param weapon The unit of which to retrieve the "detected" methods. - * @returns Array of detection methods - */ - getWeaponDetectedMethods(weapon: Weapon): number[]; - } -} -declare module "server/servermanager" { - import { LatLng } from 'leaflet'; - import { GeneralSettings, Radio, ServerRequestOptions, TACAN } from "interfaces"; - export class ServerManager { - #private; - constructor(); - setCredentials(newUsername: string, newPassword: string): void; - GET(callback: CallableFunction, uri: string, options?: ServerRequestOptions, responseType?: string, force?: boolean): void; - PUT(request: object, callback: CallableFunction): void; - getConfig(callback: CallableFunction): void; - setAddress(address: string): void; - getAirbases(callback: CallableFunction): void; - getBullseye(callback: CallableFunction): void; - getLogs(callback: CallableFunction, refresh?: boolean): void; - getMission(callback: CallableFunction): void; - getUnits(callback: CallableFunction, refresh?: boolean): void; - getWeapons(callback: CallableFunction, refresh?: boolean): void; - 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, 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; - spawnNavyUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback?: CallableFunction): void; - attackUnit(ID: number, targetID: number, callback?: CallableFunction): void; - followUnit(ID: number, targetID: number, offset: { - "x": number; - "y": number; - "z": number; - }, callback?: CallableFunction): void; - cloneUnits(units: { - ID: number; - location: LatLng; - }[], deleteOriginal: boolean, spawnPoints: number, 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; - setSpeedType(ID: number, speedType: string, callback?: CallableFunction): void; - changeAltitude(ID: number, altitudeChange: string, callback?: CallableFunction): void; - setAltitudeType(ID: number, altitudeType: string, callback?: CallableFunction): void; - setAltitude(ID: number, altitude: number, callback?: CallableFunction): void; - createFormation(ID: number, isLeader: boolean, wingmenIDs: number[], callback?: CallableFunction): void; - setROE(ID: number, ROE: string, callback?: CallableFunction): void; - setReactionToThreat(ID: number, reactionToThreat: string, callback?: CallableFunction): void; - setEmissionsCountermeasures(ID: number, emissionCountermeasure: string, callback?: CallableFunction): void; - setOnOff(ID: number, onOff: boolean, callback?: CallableFunction): void; - setFollowRoads(ID: number, followRoads: boolean, callback?: CallableFunction): void; - setOperateAs(ID: number, operateAs: number, callback?: CallableFunction): void; - refuel(ID: number, callback?: CallableFunction): void; - bombPoint(ID: number, latlng: LatLng, callback?: CallableFunction): void; - carpetBomb(ID: number, latlng: LatLng, callback?: CallableFunction): void; - bombBuilding(ID: number, latlng: LatLng, callback?: CallableFunction): void; - fireAtArea(ID: number, latlng: LatLng, callback?: CallableFunction): void; - simulateFireFight(ID: number, latlng: LatLng, altitude: number, callback?: CallableFunction): void; - scenicAAA(ID: number, coalition: string, callback?: CallableFunction): void; - missOnPurpose(ID: number, coalition: string, callback?: CallableFunction): void; - landAtPoint(ID: number, latlng: LatLng, callback?: CallableFunction): void; - setShotsScatter(ID: number, shotsScatter: number, callback?: CallableFunction): void; - setShotsIntensity(ID: number, shotsIntensity: number, callback?: CallableFunction): void; - setAdvacedOptions(ID: number, isActiveTanker: boolean, isActiveAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback?: CallableFunction): void; - setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: { - blue: number; - red: number; - }, eras: string[], setupTime: number, callback?: CallableFunction): void; - reloadDatabases(callback?: CallableFunction): void; - startUpdate(): void; - refreshAll(): void; - checkSessionHash(newSessionHash: string): void; - setConnected(newConnected: boolean): void; - getConnected(): boolean; - setPaused(newPaused: boolean): void; - getPaused(): boolean; - getServerIsPaused(): boolean; - getRequests(): { - [key: string]: XMLHttpRequest; - }; - } -} -declare module "panels/unitlistpanel" { - import { Panel } from "panels/panel"; - export class UnitListPanel extends Panel { - #private; - constructor(panelElement: string, contentElement: string); - doUpdate(): void; - getContentElement(): HTMLElement; - startUpdates(): void; - stopUpdates(): void; - toggle(): void; - } -} -declare module "context/contextmanager" { - import { Manager } from "other/manager"; - import { ContextInterface } from "context/context"; - export class ContextManager extends Manager { - #private; - constructor(); - add(name: string, contextConfig: ContextInterface): this; - currentContextIs(contextName: string): boolean; - getCurrentContext(): any; - setContext(contextName: string): false | undefined; - } -} -declare module "olympusapp" { - import { Map } from "map/map"; - import { MissionManager } from "mission/missionmanager"; - import { PluginsManager } from "plugin/pluginmanager"; - import { ShortcutManager } from "shortcut/shortcutmanager"; - import { UnitsManager } from "unit/unitsmanager"; - import { WeaponsManager } from "weapon/weaponsmanager"; - import { Manager } from "other/manager"; - import { ServerManager } from "server/servermanager"; - import { ContextManager } from "context/contextmanager"; - import { Context } from "context/context"; - export class OlympusApp { - #private; - constructor(); - getDialogManager(): Manager; - getMap(): Map; - getCurrentContext(): Context; - getContextManager(): ContextManager; - getServerManager(): ServerManager; - getPanelsManager(): Manager; - getPopupsManager(): Manager; - getToolbarsManager(): Manager; - getShortcutManager(): ShortcutManager; - getUnitsManager(): UnitsManager; - getWeaponsManager(): WeaponsManager; - getMissionManager(): MissionManager; - getPluginsManager(): PluginsManager; - /** Set the active coalition, i.e. the currently controlled coalition. A game master can change the active coalition, while a commander is bound to his/her coalition - * - * @param newActiveCoalition - */ - setActiveCoalition(newActiveCoalition: string): void; - /** - * - * @returns The active coalition - */ - getActiveCoalition(): string; - /** - * - * @returns The aircraft database - */ - getAircraftDatabase(): import("unit/databases/aircraftdatabase").AircraftDatabase; - /** - * - * @returns The helicopter database - */ - getHelicopterDatabase(): import("unit/databases/helicopterdatabase").HelicopterDatabase; - /** - * - * @returns The ground unit database - */ - getGroundUnitDatabase(): import("unit/databases/groundunitdatabase").GroundUnitDatabase; - /** - * - * @returns The navy unit database - */ - getNavyUnitDatabase(): import("unit/databases/navyunitdatabase").NavyUnitDatabase; - /** Set a message in the login splash screen - * - * @param status The message to show in the login splash screen - */ - setLoginStatus(status: string): void; - start(): void; - getConfig(): any; - } -} -declare module "index" { - import { OlympusApp } from "olympusapp"; - export function getApp(): OlympusApp; -} -declare module "context/contextmenumanager" { - import { ContextMenu } from "contextmenus/contextmenu"; - import { Manager } from "other/manager"; - export class ContextMenuManager extends Manager { - constructor(); - add(name: string, contextMenu: ContextMenu): this; - } -} diff --git a/frontend/website/package.json b/frontend/website/package.json deleted file mode 100644 index f278b2d5..00000000 --- a/frontend/website/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "DCSOlympus Website", - "version": "{{OLYMPUS_VERSION_NUMBER}}", - "private": true, - "scripts": { - "emit-declarations": "call ./scripts/emit-declarations.bat", - "build-release": "call ./scripts/build-release.bat", - "build-debug": "call ./scripts/build-debug.bat", - "watch": "call ./scripts/watch.bat" - }, - "devDependencies": { - "@babel/preset-env": "^7.21.4", - "@tanem/svg-injector": "^10.1.68", - "@turf/turf": "^6.5.0", - "@types/formatcoords": "^1.1.0", - "@types/geojson": "^7946.0.10", - "@types/leaflet": "^1.9.0", - "@types/node": "^18.16.1", - "@types/sortablejs": "^1.15.0", - "@types/svg-injector": "^0.0.29", - "ajv": "^8.12.0", - "babelify": "^10.0.0", - "browserify": "^17.0.0", - "concurrently": "^7.6.0", - "cp": "^0.2.0", - "esmify": "^2.1.1", - "formatcoords": "^1.1.3", - "geodesy": "^1.1.2", - "js-sha256": "^0.10.1", - "leaflet": "^1.9.3", - "leaflet-control-mini-map": "^0.4.0", - "leaflet-gesture-handling": "^1.2.2", - "leaflet-path-drag": "*", - "leaflet.nauticscale": "^1.1.0", - "nodemon": "^3.0.2", - "requirejs": "^2.3.6", - "sortablejs": "^1.15.0", - "tsify": "^5.0.4", - "tslib": "latest", - "typescript": "^4.9.4", - "usng.js": "^0.4.5", - "watchify": "^4.0.0" - } -} diff --git a/frontend/website/plugins/_boilerplate/copy.bat b/frontend/website/plugins/_boilerplate/copy.bat deleted file mode 100644 index be0fc21e..00000000 --- a/frontend/website/plugins/_boilerplate/copy.bat +++ /dev/null @@ -1,11 +0,0 @@ -set plugin-name=boilerplateplugin - -mkdir .\\..\\..\\..\\server\\public\\plugins\\%plugin-name% - -copy .\\index.js .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\index.js -copy .\\plugin.json .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\plugin.json -copy .\\style.css .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\style.css - - -mkdir .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\images -copy .\\images\\*.* .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\images\\ \ No newline at end of file diff --git a/frontend/website/plugins/_boilerplate/images/placeholder1x1.png b/frontend/website/plugins/_boilerplate/images/placeholder1x1.png deleted file mode 100644 index 94ac31eb..00000000 Binary files a/frontend/website/plugins/_boilerplate/images/placeholder1x1.png and /dev/null differ diff --git a/frontend/website/plugins/_boilerplate/package.json b/frontend/website/plugins/_boilerplate/package.json deleted file mode 100644 index 1fae0141..00000000 --- a/frontend/website/plugins/_boilerplate/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "boilerplateplugin", - "version": "v0.0.1", - "private": true, - "scripts": { - "build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat" - }, - "devDependencies": { - "@types/sortablejs": "^1.15.4", - "browserify": "^17.0.0", - "sortablejs": "^1.15.0", - "tsify": "^5.0.4" - } -} diff --git a/frontend/website/plugins/_boilerplate/plugin.json b/frontend/website/plugins/_boilerplate/plugin.json deleted file mode 100644 index 173c83cc..00000000 --- a/frontend/website/plugins/_boilerplate/plugin.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "Boilerplate", - "version": "0.0.1", - "description": "Base plugin starter", - "authorName": "", - "authorContact": "" -} \ No newline at end of file diff --git a/frontend/website/plugins/_boilerplate/readme.md b/frontend/website/plugins/_boilerplate/readme.md deleted file mode 100644 index 1fee2044..00000000 --- a/frontend/website/plugins/_boilerplate/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -# Boilerplate plugin - -See: https://github.com/Pax1601/DCSOlympus/wiki/Developer-Guide \ No newline at end of file diff --git a/frontend/website/plugins/_boilerplate/src/boilerplate.ts b/frontend/website/plugins/_boilerplate/src/boilerplate.ts deleted file mode 100644 index 629bbc29..00000000 --- a/frontend/website/plugins/_boilerplate/src/boilerplate.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { OlympusPlugin } from "interfaces"; -import { OlympusApp } from "olympusapp"; - - -export class BoilerplatePlugin implements OlympusPlugin { - #app!: OlympusApp; - - constructor() { - } - - /** - * @param app - * - * @returns boolean on success/fail - */ - - initialize(app: OlympusApp) : boolean { - this.#app = app; - - return true; // Return true on success - } - - getName() { - return "Boilerplate"; - } - -} \ No newline at end of file diff --git a/frontend/website/plugins/_boilerplate/src/index.ts b/frontend/website/plugins/_boilerplate/src/index.ts deleted file mode 100644 index b1bef5f3..00000000 --- a/frontend/website/plugins/_boilerplate/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { BoilerplatePlugin } from "./boilerplate"; - -globalThis.getOlympusPlugin = () => { - return new BoilerplatePlugin(); -} \ No newline at end of file diff --git a/frontend/website/plugins/_boilerplate/style.css b/frontend/website/plugins/_boilerplate/style.css deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/website/plugins/_boilerplate/tsconfig.json b/frontend/website/plugins/_boilerplate/tsconfig.json deleted file mode 100644 index a445c704..00000000 --- a/frontend/website/plugins/_boilerplate/tsconfig.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - "rootDirs": ["./src", "./@types"], /* Specify the root folder within your source files. */ - // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - "typeRoots": [ - "./node_modules/@types", - "@types" - ], /* Specify multiple folders that act like './node_modules/@types'. */ - "types": [ - "olympus" - ], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - }, - "include": [ - "src/*.ts", - "../DCSOlympus/client/@types/olympus/index.d.ts" - ] -} \ No newline at end of file diff --git a/frontend/website/plugins/controltips/copy.bat b/frontend/website/plugins/controltips/copy.bat deleted file mode 100644 index 867598a7..00000000 --- a/frontend/website/plugins/controltips/copy.bat +++ /dev/null @@ -1,7 +0,0 @@ -set plugin-name=controltipsplugin - -mkdir .\\..\\..\\..\\server\\public\\plugins\\%plugin-name% - -copy .\\index.js .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\index.js -copy .\\plugin.json .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\plugin.json -copy .\\style.css .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\style.css \ No newline at end of file diff --git a/frontend/website/plugins/controltips/package.json b/frontend/website/plugins/controltips/package.json deleted file mode 100644 index 141b7bec..00000000 --- a/frontend/website/plugins/controltips/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "ControlTipsPlugin", - "version": "v0.0.1", - "private": true, - "scripts": { - "build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat", - "build-release": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat" - }, - "dependencies": {}, - "devDependencies": { - "@babel/preset-env": "^7.21.4", - "@types/node": "^18.16.1", - "@types/sortablejs": "^1.15.0", - "babelify": "^10.0.0", - "browserify": "^17.0.0", - "concurrently": "^7.6.0", - "cp": "^0.2.0", - "esmify": "^2.1.1", - "nodemon": "^2.0.20", - "requirejs": "^2.3.6", - "sortablejs": "^1.15.0", - "tinyify": "^4.0.0", - "tsify": "^5.0.4", - "tslib": "latest", - "typescript": "^4.9.4", - "usng.js": "^0.4.5", - "watchify": "^4.0.0" - } -} diff --git a/frontend/website/plugins/controltips/plugin.json b/frontend/website/plugins/controltips/plugin.json deleted file mode 100644 index 77a1f817..00000000 --- a/frontend/website/plugins/controltips/plugin.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Control Tip Plugin", - "version": "0.0.1", - "description": "This plugin shows useful control tips on the right side of the screen. The tips change dynamically depending on what the user does", - "author": "Peekaboo" -} \ No newline at end of file diff --git a/frontend/website/plugins/controltips/src/controltipsplugin.ts b/frontend/website/plugins/controltips/src/controltipsplugin.ts deleted file mode 100644 index 4d2bfe4d..00000000 --- a/frontend/website/plugins/controltips/src/controltipsplugin.ts +++ /dev/null @@ -1,396 +0,0 @@ -import { OlympusPlugin } from "interfaces"; -import { OlympusApp } from "olympusapp"; -import { Unit } from "unit/unit"; - - -const SHOW_CONTROL_TIPS = "Show control tips" - -export class ControlTipsPlugin implements OlympusPlugin { - #element: HTMLElement; - #app: any; - #shortcutManager: any; - #cursorIsHoveringOverUnit: boolean = false; - #cursorIsHoveringOverAirbase: boolean = false; - #mouseoverElement!: HTMLElement; - - constructor() { - this.#element = document.createElement("div"); - this.#element.id = "control-tips-panel"; - document.body.appendChild(this.#element); - } - - getName() { - return "Control Tips Plugin" - } - - initialize(app: any) { - this.#app = app; - - this.#shortcutManager = this.#app.getShortcutManager(); - - this.#shortcutManager.onKeyDown(() => { - this.#updateTips() - }); - - this.#shortcutManager.onKeyUp(() => { - this.#updateTips() - }); - - document.addEventListener("airbaseMouseover", (ev: CustomEventInit) => { - this.#cursorIsHoveringOverAirbase = true; - this.#updateTips(); - }); - - document.addEventListener("airbaseMouseout", (ev: CustomEventInit) => { - this.#cursorIsHoveringOverAirbase = false; - this.#updateTips(); - }); - - document.addEventListener("unitDeselection", (ev: CustomEventInit ) => { - this.#updateTips(); - }); - - document.addEventListener("unitMouseover", (ev: CustomEventInit) => { - this.#cursorIsHoveringOverUnit = true; - this.#updateTips(); - }); - - document.addEventListener("unitMouseout", (ev: CustomEventInit) => { - this.#cursorIsHoveringOverUnit = false; - this.#updateTips(); - }); - - document.addEventListener("unitsSelection", (ev: CustomEventInit ) => { - this.#updateTips(); - }); - - document.addEventListener("mapOptionsChanged", () => { - this.toggle( !this.#app.getMap().getVisibilityOptions()[SHOW_CONTROL_TIPS] ); - }); - - document.addEventListener( "mouseover", ( ev: MouseEvent ) => { - if ( ev.target instanceof HTMLElement ) { - this.#mouseoverElement = ev.target; - } - this.#updateTips(); - }); - - document.addEventListener( "mouseup", ( ev: MouseEvent ) => { - this.#updateTips(); - }); - - this.#updateTips(); - - this.#app.getMap().addVisibilityOption(SHOW_CONTROL_TIPS, true); - - return true; - } - - getElement() { - return this.#element; - } - - toggle(bool?: boolean) { - this.getElement().classList.toggle("hide", bool); - } - - #updateTips() { - const combos: Array = [ - { - "keys": [], - "tips": [ - { - "key": `SHIFT`, - "action": `Box select`, - "showIfHoveringOverAirbase": false, - "showIfHoveringOverUnit": false, - "showIfUnitSelected": false - }, - { - "key": `Mouse1`, - "action": `Deselect`, - "showIfUnitSelected": true - }, - { - "key": `Mouse1+drag`, - "action": `Move map`, - "showIfHoveringOverAirbase": false, - "showIfHoveringOverUnit": false, - "showIfUnitSelected": false - }, - { - "key": `Mouse2`, - "action": `Spawn menu`, - "showIfUnitSelected": false, - "showIfHoveringOverAirbase": false, - "showIfHoveringOverUnit": false - }, - { - "key": `Mouse2`, - "action": `Airbase menu`, - "showIfUnitSelected": false, - "showIfHoveringOverAirbase": true, - "showIfHoveringOverUnit": false - }, - { - "key": `Mouse2`, - "action": `Set first waypoint`, - "showIfHoveringOverAirbase": false, - "showIfUnitSelected": true, - "unitsMustBeControlled": true - }, - { - "key": `Mouse2 (hold)`, - "action": `Interact (ground)`, - "showIfUnitSelected": true, - "showIfHoveringOverAirbase": false, - "showIfHoveringOverUnit": false, - "unitsMustBeControlled": true - }, - { - "key": `Shift`, - "action": " in formation...", - "showIfUnitSelected": true, - "minSelectedUnits": 2 - }, - { - "key": "CTRL", - "action": " ... more", - "showIfUnitSelected": true, - "showIfHoveringOverAirbase": false, - "unitsMustBeControlled": true - }, - { - "key": "CTRL", - "action": " Pin tool", - "showIfUnitSelected": false, - "showIfHoveringOverAirbase": false, - "showIfHoveringOverUnit": false, - "unitsMustBeControlled": true - }, - { - "key": "CTRL+Mouse2", - "action": " Airbase menu", - "showIfUnitSelected": true, - "showIfHoveringOverAirbase": true, - "unitsMustBeControlled": true - }, - { - "key": `Mouse1`, - "action": "Toggle Blue/Red", - "mouseoverSelector": "#coalition-switch .ol-switch-fill" - }, - { - "key": `Mouse2`, - "action": "Set Neutral", - "mouseoverSelector": "#coalition-switch .ol-switch-fill" - }, - { - "key": `Mouse1`, - "action": "Toggle time display", - "mouseoverSelector": "#connection-status-panel[data-is-connected] #connection-status-message abbr" - }, - { - "key": `Mouse1 or Z`, - "action": "Change location system", - "mouseoverSelector": "#coordinates-tool, #coordinates-tool *" - }, - { - "key": `Comma`, - "action": "Decrease precision", - "mouseoverSelector": `#coordinates-tool[data-location-system="MGRS"], #coordinates-tool[data-location-system="MGRS"] *` - }, - { - "key": `Period`, - "action": "Increase precision", - "mouseoverSelector": `#coordinates-tool[data-location-system="MGRS"], #coordinates-tool[data-location-system="MGRS"] *` - } - ] - }, - { - "keys": ["ControlLeft"], - "tips": [ - { - "key": `Mouse1`, - "action": "Toggle pin", - "showIfUnitSelected": false, - "showIfHoveringOverAirbase": false, - "showIfHoveringOverUnit": false - }, - { - "key": `Mouse1`, - "action": "Toggle selection", - "showIfUnitSelected": true, - "showIfHoveringOverAirbase": false, - "showIfHoveringOverUnit": true - }, - { - "key": `Mouse2`, - "action": `Add waypoint`, - "showIfHoveringOverAirbase": false, - "showIfHoveringOverUnit": false, - "showIfUnitSelected": true, - "unitsMustBeControlled": true - }, - { - "key": `Mouse2`, - "action": `Interact (airbase)`, - "showIfHoveringOverAirbase": true, - "showIfUnitSelected": true, - "unitsMustBeControlled": true - }, - { - "key": `Mouse2`, - "action": `Interact (unit)`, - "showIfHoveringOverAirbase": false, - "showIfHoveringOverUnit": true, - "showIfUnitSelected": true, - "unitsMustBeControlled": true - }, - { - "key": `Shift`, - "action": " in formation...", - "showIfUnitSelected": true, - "minSelectedUnits": 2 - }, - { - "key": `[Num 1-9]`, - "action": "Set hotgroup", - "showIfUnitSelected": true - } - ] - }, - { - "keys": ["ShiftLeft"], - "tips": [ - { - "key": `Mouse1+drag`, - "action": "Box select", - "showIfUnitSelected": false - }, - { - "key": `Mouse2`, - "action": "Set first formation waypoint", - "showIfUnitSelected": true, - "minSelectedUnits": 2 - }, - { - "key": `[Num 1-9]`, - "action": "Add to hotgroup", - "showIfUnitSelected": true - }, - { - "key": "CTRL", - "action": " ... more", - "minSelectedUnits": 2, - "showIfUnitSelected": true, - "showIfHoveringOverAirbase": false, - "unitsMustBeControlled": true - } - ] - }, - { - "keys": ["ControlLeft", "ShiftLeft"], - "tips": [ - { - "key": `Mouse2`, - "action": "Add formation waypoint", - "showIfUnitSelected": true, - "minSelectedUnits": 2, - "unitsMustBeControlled": true - }, { - "key": `[Num 1-9]`, - "action": "Add hotgroup to selection", - "callback": ( tip:object ) => { - return (Object.values( this.#app.getUnitsManager().getUnits() ).some( ( unit:Unit ) => { - return unit.getAlive() && unit.getControlled() && unit.getHotgroup(); - })); - }, - "showIfUnitSelected": true, - "minSelectedUnits": 1 - } - ] - } - ]; - - const currentCombo: any = combos.find((combo: any) => this.#shortcutManager.keyComboMatches(combo.keys)) || combos[0]; - - const element = this.getElement(); - - element.innerHTML = ""; - - let numSelectedUnits = 0; - let numSelectedControlledUnits = 0; - let unitSelectionContainsControlled = false; - - if (this.#app.getUnitsManager()) { - let selectedUnits = Object.values(this.#app.getUnitsManager().getSelectedUnits()); - numSelectedUnits = selectedUnits.length; - numSelectedControlledUnits = selectedUnits.filter((unit: any) => unit.getControlled()).length; - unitSelectionContainsControlled = numSelectedControlledUnits > 0; - } - - const tipsIncludesActiveMouseover = ( currentCombo.tips.some( ( tip:any ) => { - if ( !tip.mouseoverSelector ) { - return false; - } - - if ( this.#mouseoverElement instanceof HTMLElement === false ) { - return false; - } - - if ( !this.#mouseoverElement.matches( tip.mouseoverSelector ) ) { - return false; - } - - return true; - })); - - currentCombo.tips.filter((tip: any) => { - if (numSelectedUnits > 0) { - if (tip.showIfUnitSelected === false) { - return false; - } - - if (tip.unitsMustBeControlled === true && unitSelectionContainsControlled === false) { - return false; - } - - if ( typeof tip.minSelectedUnits === "number" && numSelectedControlledUnits < tip.minSelectedUnits ) { - return false; - } - } - - if (numSelectedUnits === 0 && tip.showIfUnitSelected === true) { - return false; - } - - if (typeof tip.showIfHoveringOverAirbase === "boolean") { - if (tip.showIfHoveringOverAirbase !== this.#cursorIsHoveringOverAirbase) { - return false; - } - } - - if (typeof tip.showIfHoveringOverUnit === "boolean") { - if (tip.showIfHoveringOverUnit !== this.#cursorIsHoveringOverUnit) { - return false; - } - } - - if ( tipsIncludesActiveMouseover && ( typeof tip.mouseoverSelector !== "string" || !this.#mouseoverElement.matches( tip.mouseoverSelector ) ) ) { - return false; - } - - if ( !tipsIncludesActiveMouseover && typeof tip.mouseoverSelector === "string" ) { - return false; - } - - if ( typeof tip.callback === "function" && !tip.callback( tip ) ) { - return false; - } - - element.innerHTML += `
${tip.key}${tip.action}
`; - - }); - } -} \ No newline at end of file diff --git a/frontend/website/plugins/controltips/src/index.ts b/frontend/website/plugins/controltips/src/index.ts deleted file mode 100644 index 425907e0..00000000 --- a/frontend/website/plugins/controltips/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ControlTipsPlugin } from "./controltipsplugin"; - -globalThis.getOlympusPlugin = () => { - return new ControlTipsPlugin(); -} \ No newline at end of file diff --git a/frontend/website/plugins/controltips/style.css b/frontend/website/plugins/controltips/style.css deleted file mode 100644 index 0ab48856..00000000 --- a/frontend/website/plugins/controltips/style.css +++ /dev/null @@ -1,33 +0,0 @@ -#control-tips-panel { - align-self: center; - display: flex; - flex-flow: column wrap; - font-size: 13px; - justify-self: flex-end; - position: absolute; - right: 10px; - row-gap: 20px; - text-align: right; - z-index: 999; -} - -#control-tips-panel>* { - align-items: center; - align-self: end; - background-color: var(--background-steel); - border-radius: var(--border-radius-md); - color: white; - column-gap: 8px; - display: flex; - justify-items: right; - opacity: .9; - padding: 5px; - width: fit-content; -} - -#control-tips-panel>*>.key { - background-color: var(--background-grey); - border-radius: var(--border-radius-sm); - color: white; - padding: 1px 4px; -} \ No newline at end of file diff --git a/frontend/website/plugins/controltips/tsconfig.json b/frontend/website/plugins/controltips/tsconfig.json deleted file mode 100644 index ddbc11d9..00000000 --- a/frontend/website/plugins/controltips/tsconfig.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - "rootDirs": ["./src", "../../@types"], /* Specify the root folder within your source files. */ - // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - "typeRoots": [ - "./node_modules/@types", - "../../@types" - ], /* Specify multiple folders that act like './node_modules/@types'. */ - "types": [ - "olympus" - ], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - }, - "include": [ - "src/*.ts", - "../../@types/olympus/*.d.ts" - ] -} \ No newline at end of file diff --git a/frontend/website/plugins/databasemanager/.vscode/launch.json b/frontend/website/plugins/databasemanager/.vscode/launch.json deleted file mode 100644 index 3fb02a39..00000000 --- a/frontend/website/plugins/databasemanager/.vscode/launch.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Attach to Chrome", - "port": 9222, - "urlFilter": "http://localhost:3000/*", - "request": "attach", - "type": "chrome", - "webRoot": "${workspaceFolder}../../public/", - "sourceMapPathOverrides": { - "src/*": "src/*" - }, - "preLaunchTask": "start" - } - ] -} \ No newline at end of file diff --git a/frontend/website/plugins/databasemanager/.vscode/tasks.json b/frontend/website/plugins/databasemanager/.vscode/tasks.json deleted file mode 100644 index fdd8289c..00000000 --- a/frontend/website/plugins/databasemanager/.vscode/tasks.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "start", - "type": "shell", - "command": "npm run start", - "isBackground": true - } - ] -} \ No newline at end of file diff --git a/frontend/website/plugins/databasemanager/copy.bat b/frontend/website/plugins/databasemanager/copy.bat deleted file mode 100644 index 56bc3cea..00000000 --- a/frontend/website/plugins/databasemanager/copy.bat +++ /dev/null @@ -1,7 +0,0 @@ -set plugin-name=databasemanager - -mkdir .\\..\\..\\..\\server\\public\\plugins\\%plugin-name% - -copy .\\index.js .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\index.js -copy .\\plugin.json .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\plugin.json -copy .\\style.css .\\..\\..\\..\\server\\public\\plugins\\%plugin-name%\\style.css \ No newline at end of file diff --git a/frontend/website/plugins/databasemanager/index.js b/frontend/website/plugins/databasemanager/index.js deleted file mode 100644 index 9305ff01..00000000 --- a/frontend/website/plugins/databasemanager/index.js +++ /dev/null @@ -1 +0,0 @@ -!function(){var _$utils_8={};function addStringInput(a,t,e,i,n,s){var _=document.createElement("div"),l=document.createElement("dt"),r=document.createElement("dd");l.innerText=t;var o=document.createElement("input");o.value=e,o.textContent=e,o.type=null!=i?i:"text",o.disabled=null!=s&&s,o.onchange=()=>n(o.value),r.appendChild(o),_.appendChild(l),_.appendChild(r),_.classList.add("input-row"),a.appendChild(_)}function addDropdownInput(a,t,e,i,n,s){var _=document.createElement("div"),l=document.createElement("dt"),r=document.createElement("dd");l.innerText=t;var o=document.createElement("select");i.forEach(a=>{var t=document.createElement("option");t.value=a,t.innerText=a,o.appendChild(t)}),o.value=e,o.disabled=null!=s&&s,o.onchange=()=>n(o.value),r.appendChild(o),_.appendChild(l),_.appendChild(r),_.classList.add("input-row"),a.appendChild(_)}function addCheckboxInput(a,t,e,i,n){var s=document.createElement("div"),_=document.createElement("dt"),l=document.createElement("dd");_.innerText=t;var r=document.createElement("input");r.checked=e,r.type="checkbox",r.disabled=null!=n&&n,r.onchange=()=>i(r.checked),l.appendChild(r),s.appendChild(_),s.appendChild(l),s.classList.add("input-row"),a.appendChild(s)}function addLoadoutItemsEditor(a,t){var e=document.createElement("div");e.classList.add("dm-scroll-container","dm-items-container"),t.items.sort((a,t)=>a.name.localeCompare(t.name,void 0,{sensitivity:"base"})),t.items.forEach((i,n)=>{var s=document.createElement("div"),_=document.createElement("label");_.innerText="Name",s.appendChild(_);var l=document.createElement("input");s.appendChild(l),l.textContent=i.name,l.value=i.name,l.onchange=()=>{t.items[n].name=l.value};var r=document.createElement("label");r.innerText="Quantity",s.appendChild(r);var o=document.createElement("input");s.appendChild(o),o.textContent=String(i.quantity),o.value=String(i.quantity),o.type="number",o.step="1",o.onchange=()=>{t.items[n].quantity=parseInt(o.value)};var d=document.createElement("button");d.innerText="X",d.onclick=()=>{t.items.splice(n,1),a.dispatchEvent(new Event("refresh"))},s.appendChild(d),e.appendChild(s)}),a.appendChild(e);var i=document.createElement("div");i.classList.add("dm-new-item-input");var n=document.createElement("button");n.innerText="Add",i.appendChild(n),a.appendChild(i),n.addEventListener("click",e=>{null==t||t.items.push({name:"",quantity:1}),a.dispatchEvent(new Event("refresh"))})}function addNewElementInput(a,t){var e=document.createElement("div");e.classList.add("dm-new-element-input");var i=document.createElement("input");e.appendChild(i);var n=document.createElement("button");n.innerText="Add",n.addEventListener("click",a=>t(a,i)),e.appendChild(n),a.appendChild(e)}function addBlueprintsScroll(div,database,filter,callback){var scrollDiv=document.createElement("div");if(scrollDiv.classList.add("dm-scroll-container"),null!==database){var blueprints=database.blueprints;for(let key of Object.keys(blueprints).sort((a,t)=>a.localeCompare(t,void 0,{sensitivity:"base"}))){var addKey=!0;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 a=document.createElement("div");a.innerHTML=`
${key}
${blueprints[key].label}
`,a.onclick=()=>{callback(key);const t=document.getElementsByClassName("blueprint-selected");for(let a=0;a{console.log(t.checked),blueprints[key].enabled=t.checked},rowDiv.appendChild(t);var button=document.createElement("button");button.innerText="X",button.onclick=()=>{delete blueprints[key],div.dispatchEvent(new Event("refresh"))},rowDiv.appendChild(button)}}}div.appendChild(scrollDiv)}function addLoadoutsScroll(a,t,e){var i=document.createElement("div");i.classList.add("dm-scroll-container","dm-loadout-container"),t.sort((a,t)=>a.name.localeCompare(t.name,void 0,{sensitivity:"base"})),t.forEach((n,s)=>{var _=document.createElement("div");i.appendChild(_);var l=document.createElement("label");if(l.textContent=n.name,l.onclick=()=>{e(n)},_.appendChild(l),"Empty loadout"!==n.name){let e=document.createElement("input");e.type="checkbox",e.checked=n.enabled,e.onclick=()=>{console.log(e.checked),n.enabled=e.checked},_.appendChild(e);var r=document.createElement("button");r.innerText="X",r.onclick=()=>{t.splice(s,1),a.dispatchEvent(new Event("refresh"))},_.appendChild(r)}}),a.appendChild(i)}function arrayToString(a){return"["+a.join(", ")+"]"}function stringToArray(a){var t;return null!==(t=a.match(/(\w)+/g))&&void 0!==t?t:[]}Object.defineProperty(_$utils_8,"__esModule",{value:!0}),_$utils_8.stringToArray=_$utils_8.arrayToString=_$utils_8.addLoadoutsScroll=_$utils_8.addBlueprintsScroll=_$utils_8.addNewElementInput=_$utils_8.addLoadoutItemsEditor=_$utils_8.addCheckboxInput=_$utils_8.addDropdownInput=_$utils_8.addStringInput=void 0,_$utils_8.addStringInput=addStringInput,_$utils_8.addDropdownInput=addDropdownInput,_$utils_8.addCheckboxInput=addCheckboxInput,_$utils_8.addLoadoutItemsEditor=addLoadoutItemsEditor,_$utils_8.addNewElementInput=addNewElementInput,_$utils_8.addBlueprintsScroll=addBlueprintsScroll,_$utils_8.addLoadoutsScroll=addLoadoutsScroll,_$utils_8.arrayToString=arrayToString,_$utils_8.stringToArray=stringToArray;var _$uniteditor_7={};Object.defineProperty(_$uniteditor_7,"__esModule",{value:!0}),_$uniteditor_7.UnitEditor=void 0,_$uniteditor_7.UnitEditor=class{constructor(a,t,e){this.blueprint=null,this.database=null,this.visible=!1,this.contentDiv1=a,this.contentDiv2=t,this.contentDiv3=e,this.contentDiv1.addEventListener("refresh",()=>{this.visible&&this.show()}),this.contentDiv2.addEventListener("refresh",()=>{this.visible&&null!==this.blueprint&&this.setBlueprint(this.blueprint)}),this.contentDiv3.addEventListener("refresh",()=>{this.visible&&null!==this.blueprint&&this.setBlueprint(this.blueprint)})}setDatabase(a){this.database=JSON.parse(JSON.stringify({blueprints:a.getBlueprints(!0)}))}show(a=""){if(this.visible=!0,this.contentDiv1.replaceChildren(),this.contentDiv2.replaceChildren(),this.contentDiv3.replaceChildren(),null!=this.database){var t=document.createElement("label");t.innerText="Units list",this.contentDiv1.appendChild(t);var e=document.createElement("input");e.value=a,this.contentDiv1.appendChild(e),e.onchange=a=>{this.show(a.target.value)},this.addBlueprints(a)}}hide(){this.visible=!1,this.contentDiv1.replaceChildren(),this.contentDiv2.replaceChildren(),this.contentDiv3.replaceChildren()}getDatabase(){return this.database}addBlueprints(a=""){this.database&&((0,_$utils_8.addBlueprintsScroll)(this.contentDiv1,this.database,a,a=>{null!=this.database&&this.setBlueprint(this.database.blueprints[a])}),(0,_$utils_8.addNewElementInput)(this.contentDiv1,(a,t)=>{""!=t.value&&this.addBlueprint(t.value)}))}};var _$loadouteditor_5={},_LoadoutEditor_contentDiv,_LoadoutEditor_loadout,_LoadoutEditor_visible,__classPrivateFieldSet=this&&this.__classPrivateFieldSet||function(a,t,e,i,n){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!n)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?a!==t||!n:!t.has(a))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?n.call(a,e):n?n.value=e:t.set(a,e),e},__classPrivateFieldGet=this&&this.__classPrivateFieldGet||function(a,t,e,i){if("a"===e&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?a!==t||!i:!t.has(a))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===e?i:"a"===e?i.call(a):i?i.value:t.get(a)};Object.defineProperty(_$loadouteditor_5,"__esModule",{value:!0}),_$loadouteditor_5.LoadoutEditor=void 0,_$loadouteditor_5.LoadoutEditor=class{constructor(a){_LoadoutEditor_contentDiv.set(this,void 0),_LoadoutEditor_loadout.set(this,null),_LoadoutEditor_visible.set(this,!1),__classPrivateFieldSet(this,_LoadoutEditor_contentDiv,a,"f"),__classPrivateFieldGet(this,_LoadoutEditor_contentDiv,"f").addEventListener("refresh",()=>{__classPrivateFieldGet(this,_LoadoutEditor_visible,"f")&&this.show()})}setLoadout(a){__classPrivateFieldSet(this,_LoadoutEditor_loadout,a,"f")}show(){__classPrivateFieldSet(this,_LoadoutEditor_visible,!0,"f"),__classPrivateFieldGet(this,_LoadoutEditor_contentDiv,"f").replaceChildren();var a=document.createElement("label");if(a.innerText="Loadout properties",__classPrivateFieldGet(this,_LoadoutEditor_contentDiv,"f").appendChild(a),__classPrivateFieldGet(this,_LoadoutEditor_loadout,"f")){var t=__classPrivateFieldGet(this,_LoadoutEditor_loadout,"f");(0,_$utils_8.addStringInput)(__classPrivateFieldGet(this,_LoadoutEditor_contentDiv,"f"),"Name",t.name,"text",a=>{t.name=a,__classPrivateFieldGet(this,_LoadoutEditor_contentDiv,"f").dispatchEvent(new Event("refresh"))}),(0,_$utils_8.addStringInput)(__classPrivateFieldGet(this,_LoadoutEditor_contentDiv,"f"),"Code",t.code,"text",a=>{t.code=a}),(0,_$utils_8.addStringInput)(__classPrivateFieldGet(this,_LoadoutEditor_contentDiv,"f"),"Roles",(0,_$utils_8.arrayToString)(t.roles),"text",a=>{t.roles=(0,_$utils_8.stringToArray)(a)}),(0,_$utils_8.addLoadoutItemsEditor)(__classPrivateFieldGet(this,_LoadoutEditor_contentDiv,"f"),__classPrivateFieldGet(this,_LoadoutEditor_loadout,"f"))}}hide(){__classPrivateFieldSet(this,_LoadoutEditor_visible,!1,"f"),__classPrivateFieldGet(this,_LoadoutEditor_contentDiv,"f").replaceChildren()}},_LoadoutEditor_contentDiv=new WeakMap,_LoadoutEditor_loadout=new WeakMap,_LoadoutEditor_visible=new WeakMap;var _$airuniteditor_1={},_AirUnitEditor_loadoutEditor,____classPrivateFieldSet_1=this&&this.__classPrivateFieldSet||function(a,t,e,i,n){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!n)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?a!==t||!n:!t.has(a))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?n.call(a,e):n?n.value=e:t.set(a,e),e},____classPrivateFieldGet_1=this&&this.__classPrivateFieldGet||function(a,t,e,i){if("a"===e&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?a!==t||!i:!t.has(a))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===e?i:"a"===e?i.call(a):i?i.value:t.get(a)};Object.defineProperty(_$airuniteditor_1,"__esModule",{value:!0}),_$airuniteditor_1.AirUnitEditor=void 0;class AirUnitEditor extends _$uniteditor_7.UnitEditor{constructor(a,t,e){super(a,t,e),_AirUnitEditor_loadoutEditor.set(this,null),____classPrivateFieldSet_1(this,_AirUnitEditor_loadoutEditor,new _$loadouteditor_5.LoadoutEditor(this.contentDiv3),"f"),this.contentDiv3.addEventListener("refresh",()=>{var a;this.visible&&(null===(a=____classPrivateFieldGet_1(this,_AirUnitEditor_loadoutEditor,"f"))||void 0===a||a.show())})}setBlueprint(a){var t,e,i,n,s,_,l,r;this.blueprint=a,null!==this.blueprint&&(this.contentDiv2.replaceChildren(),(r=document.createElement("label")).innerText="Unit properties",this.contentDiv2.appendChild(r),(0,_$utils_8.addStringInput)(this.contentDiv2,"Name",a.name,"text",t=>{a.name=t},!0),(0,_$utils_8.addStringInput)(this.contentDiv2,"Label",a.label,"text",t=>{a.label=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Short label",a.shortLabel,"text",t=>{a.shortLabel=t}),(0,_$utils_8.addDropdownInput)(this.contentDiv2,"Coalition",a.coalition,["","blue","red"],t=>{a.coalition=t}),(0,_$utils_8.addDropdownInput)(this.contentDiv2,"Era",a.era,["WW2","Early Cold War","Mid Cold War","Late Cold War","Modern"],t=>{a.era=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Filename",null!==(t=a.filename)&&void 0!==t?t:"","text",t=>{a.filename=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Cost",null!==(e=String(a.cost))&&void 0!==e?e:"","number",t=>{a.cost=parseFloat(t)}),(0,_$utils_8.addCheckboxInput)(this.contentDiv2,"Can target point",null!==(i=a.canTargetPoint)&&void 0!==i&&i,t=>{a.canTargetPoint=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Description",null!==(n=a.description)&&void 0!==n?n:"","text",t=>{a.description=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Tags",null!==(s=a.tags)&&void 0!==s?s:"","text",t=>{a.tags=t}),(r=document.createElement("label")).innerText="Loadouts",this.contentDiv2.appendChild(r),(0,_$utils_8.addLoadoutsScroll)(this.contentDiv2,null!==(_=a.loadouts)&&void 0!==_?_:[],a=>{var t,e;null===(t=____classPrivateFieldGet_1(this,_AirUnitEditor_loadoutEditor,"f"))||void 0===t||t.setLoadout(a),null===(e=____classPrivateFieldGet_1(this,_AirUnitEditor_loadoutEditor,"f"))||void 0===e||e.show()}),(0,_$utils_8.addNewElementInput)(this.contentDiv2,(a,t)=>{this.addLoadout(t.value)}),null===(l=____classPrivateFieldGet_1(this,_AirUnitEditor_loadoutEditor,"f"))||void 0===l||l.hide())}addBlueprint(a){null!=this.database&&(this.database.blueprints[a]={name:a,coalition:"",label:"",shortLabel:"",era:"",loadouts:[],enabled:!0},this.show(),this.setBlueprint(this.database.blueprints[a]))}addLoadout(a){var t;a&&null!==this.blueprint&&(null===(t=this.blueprint.loadouts)||void 0===t||t.push({name:a,code:"",fuel:1,items:[],roles:[],enabled:!0}),this.setBlueprint(this.blueprint))}hide(){var a;super.hide(),null===(a=____classPrivateFieldGet_1(this,_AirUnitEditor_loadoutEditor,"f"))||void 0===a||a.hide()}}_$airuniteditor_1.AirUnitEditor=AirUnitEditor,_AirUnitEditor_loadoutEditor=new WeakMap;var _$grounduniteditor_3={},_GroundUnitEditor_blueprint,____classPrivateFieldSet_3=this&&this.__classPrivateFieldSet||function(a,t,e,i,n){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!n)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?a!==t||!n:!t.has(a))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?n.call(a,e):n?n.value=e:t.set(a,e),e},____classPrivateFieldGet_3=this&&this.__classPrivateFieldGet||function(a,t,e,i){if("a"===e&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?a!==t||!i:!t.has(a))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===e?i:"a"===e?i.call(a):i?i.value:t.get(a)};Object.defineProperty(_$grounduniteditor_3,"__esModule",{value:!0}),_$grounduniteditor_3.GroundUnitEditor=void 0;class GroundUnitEditor extends _$uniteditor_7.UnitEditor{constructor(a,t,e){super(a,t,e),_GroundUnitEditor_blueprint.set(this,null)}setBlueprint(a){var t,e,i,n,s,_,l,r,o,d,u,c,g,v,b,h,p,P,D,M,m;if(____classPrivateFieldSet_3(this,_GroundUnitEditor_blueprint,a,"f"),null!==____classPrivateFieldGet_3(this,_GroundUnitEditor_blueprint,"f")){this.contentDiv2.replaceChildren();var f=document.createElement("label");f.innerText="Unit properties",this.contentDiv2.appendChild(f),(0,_$utils_8.addStringInput)(this.contentDiv2,"Name",a.name,"text",t=>{a.name=t},!0),(0,_$utils_8.addStringInput)(this.contentDiv2,"Label",a.label,"text",t=>{a.label=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Short label",a.shortLabel,"text",t=>{a.shortLabel=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Type",null!==(t=a.type)&&void 0!==t?t:"","text",t=>{a.type=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Unit when grouped",null!==(e=a.unitWhenGrouped)&&void 0!==e?e:"","text",t=>{a.unitWhenGrouped=t}),(0,_$utils_8.addDropdownInput)(this.contentDiv2,"Coalition",a.coalition,["","blue","red"],t=>{a.coalition=t}),(0,_$utils_8.addDropdownInput)(this.contentDiv2,"Era",a.era,["WW2","Early Cold War","Mid Cold War","Late Cold War","Modern"],t=>{a.era=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Cost",null!==(i=String(a.cost))&&void 0!==i?i:"","number",t=>{a.cost=parseFloat(t)}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Acquisition range [m]",null!==(n=String(a.acquisitionRange))&&void 0!==n?n:"","number",t=>{a.acquisitionRange=parseFloat(t)}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Engagement range [m]",null!==(s=String(a.engagementRange))&&void 0!==s?s:"","number",t=>{a.engagementRange=parseFloat(t)}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Targeting range [m]",null!==(_=String(a.targetingRange))&&void 0!==_?_:"","number",t=>{a.targetingRange=parseFloat(t)}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Aim method range [m]",null!==(l=String(a.aimMethodRange))&&void 0!==l?l:"","number",t=>{a.aimMethodRange=parseFloat(t)}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Barrel height [m]",null!==(r=String(a.barrelHeight))&&void 0!==r?r:"","number",t=>{a.barrelHeight=parseFloat(t)}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Muzzle velocity [m/s]",null!==(o=String(a.muzzleVelocity))&&void 0!==o?o:"","number",t=>{a.muzzleVelocity=parseFloat(t)}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Aim time [s]",null!==(d=String(a.aimTime))&&void 0!==d?d:"","number",t=>{a.aimTime=parseFloat(t)}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Shots to fire",null!==(u=String(a.shotsToFire))&&void 0!==u?u:"","number",t=>{a.shotsToFire=Math.round(parseFloat(t))}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Shots base interval [s]",null!==(c=String(a.shotsBaseInterval))&&void 0!==c?c:"","number",t=>{a.shotsBaseInterval=Math.round(parseFloat(t))}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Shots base scatter [\xb0]",null!==(g=String(a.shotsBaseScatter))&&void 0!==g?g:"","number",t=>{a.shotsBaseScatter=Math.round(parseFloat(t))}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Alertness time constant [s]",null!==(v=String(a.alertnessTimeConstant))&&void 0!==v?v:"","number",t=>{a.alertnessTimeConstant=Math.round(parseFloat(t))}),(0,_$utils_8.addCheckboxInput)(this.contentDiv2,"Can target point",null!==(b=a.canTargetPoint)&&void 0!==b&&b,t=>{a.canTargetPoint=t}),(0,_$utils_8.addCheckboxInput)(this.contentDiv2,"Can rearm",null!==(h=a.canRearm)&&void 0!==h&&h,t=>{a.canRearm=t}),(0,_$utils_8.addCheckboxInput)(this.contentDiv2,"Can operate as AAA",null!==(p=a.canAAA)&&void 0!==p&&p,t=>{a.canAAA=t}),(0,_$utils_8.addCheckboxInput)(this.contentDiv2,"Indirect fire (e.g. mortar)",null!==(P=a.indirectFire)&&void 0!==P&&P,t=>{a.indirectFire=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Description",null!==(D=a.description)&&void 0!==D?D:"","text",t=>{a.description=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Tags",null!==(M=a.tags)&&void 0!==M?M:"","text",t=>{a.tags=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Marker file",null!==(m=a.markerFile)&&void 0!==m?m:"","text",t=>{a.markerFile=t})}}addBlueprint(a){null!=this.database&&(this.database.blueprints[a]={name:a,coalition:"",label:"",shortLabel:"",era:"",enabled:!0},this.show(),this.setBlueprint(this.database.blueprints[a]))}}_$grounduniteditor_3.GroundUnitEditor=GroundUnitEditor,_GroundUnitEditor_blueprint=new WeakMap;var _$navyuniteditor_6={},_NavyUnitEditor_blueprint,____classPrivateFieldSet_6=this&&this.__classPrivateFieldSet||function(a,t,e,i,n){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!n)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?a!==t||!n:!t.has(a))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?n.call(a,e):n?n.value=e:t.set(a,e),e},____classPrivateFieldGet_6=this&&this.__classPrivateFieldGet||function(a,t,e,i){if("a"===e&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?a!==t||!i:!t.has(a))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===e?i:"a"===e?i.call(a):i?i.value:t.get(a)};Object.defineProperty(_$navyuniteditor_6,"__esModule",{value:!0}),_$navyuniteditor_6.NavyUnitEditor=void 0;class NavyUnitEditor extends _$uniteditor_7.UnitEditor{constructor(a,t,e){super(a,t,e),_NavyUnitEditor_blueprint.set(this,null)}setBlueprint(a){var t,e,i,n;if(____classPrivateFieldSet_6(this,_NavyUnitEditor_blueprint,a,"f"),null!==____classPrivateFieldGet_6(this,_NavyUnitEditor_blueprint,"f")){this.contentDiv2.replaceChildren();var s=document.createElement("label");s.innerText="Unit properties",this.contentDiv2.appendChild(s),(0,_$utils_8.addStringInput)(this.contentDiv2,"Name",a.name,"text",t=>{a.name=t},!0),(0,_$utils_8.addStringInput)(this.contentDiv2,"Label",a.label,"text",t=>{a.label=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Short label",a.shortLabel,"text",t=>{a.shortLabel=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Type",null!==(t=a.type)&&void 0!==t?t:"","text",t=>{a.type=t}),(0,_$utils_8.addDropdownInput)(this.contentDiv2,"Coalition",a.coalition,["","blue","red"],t=>{a.coalition=t}),(0,_$utils_8.addDropdownInput)(this.contentDiv2,"Era",a.era,["WW2","Early Cold War","Mid Cold War","Late Cold War","Modern"],t=>{a.era=t}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Cost",null!==(e=String(a.cost))&&void 0!==e?e:"","number",t=>{a.cost=parseFloat(t)}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Barrel height [m]",null!==(i=String(a.barrelHeight))&&void 0!==i?i:"","number",t=>{a.barrelHeight=parseFloat(t)}),(0,_$utils_8.addStringInput)(this.contentDiv2,"Muzzle velocity [m/s]",null!==(n=String(a.muzzleVelocity))&&void 0!==n?n:"","number",t=>{a.muzzleVelocity=parseFloat(t)})}}addBlueprint(a){null!=this.database&&(this.database.blueprints[a]={name:a,coalition:"",label:"",shortLabel:"",era:"",enabled:!0},this.show(),this.setBlueprint(this.database.blueprints[a]))}}_$navyuniteditor_6.NavyUnitEditor=NavyUnitEditor,_NavyUnitEditor_blueprint=new WeakMap;var _$databasemanagerplugin_2={},_DatabaseManagerPlugin_instances,_DatabaseManagerPlugin_app,_DatabaseManagerPlugin_element,_DatabaseManagerPlugin_mainContentContainer,_DatabaseManagerPlugin_contentDiv1,_DatabaseManagerPlugin_contentDiv2,_DatabaseManagerPlugin_contentDiv3,_DatabaseManagerPlugin_button1,_DatabaseManagerPlugin_button2,_DatabaseManagerPlugin_button3,_DatabaseManagerPlugin_button4,_DatabaseManagerPlugin_button5,_DatabaseManagerPlugin_button6,_DatabaseManagerPlugin_button7,_DatabaseManagerPlugin_button8,_DatabaseManagerPlugin_button9,_DatabaseManagerPlugin_aircraftEditor,_DatabaseManagerPlugin_helicopterEditor,_DatabaseManagerPlugin_groundUnitEditor,_DatabaseManagerPlugin_navyUnitEditor,_DatabaseManagerPlugin_hideAll,_DatabaseManagerPlugin_loadDatabases,_DatabaseManagerPlugin_saveDatabases,_DatabaseManagerPlugin_resetToDefaultDatabases,_DatabaseManagerPlugin_restoreToPreviousDatabases,_DatabaseManagerPlugin_uploadDatabase,_DatabaseManagerPlugin_resetToDefaultDatabase,_DatabaseManagerPlugin_restoreToPreviousDatabase,____classPrivateFieldSet_2=this&&this.__classPrivateFieldSet||function(a,t,e,i,n){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!n)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?a!==t||!n:!t.has(a))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?n.call(a,e):n?n.value=e:t.set(a,e),e},____classPrivateFieldGet_2=this&&this.__classPrivateFieldGet||function(a,t,e,i){if("a"===e&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?a!==t||!i:!t.has(a))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===e?i:"a"===e?i.call(a):i?i.value:t.get(a)};Object.defineProperty(_$databasemanagerplugin_2,"__esModule",{value:!0}),_$databasemanagerplugin_2.DatabaseManagerPlugin=void 0,_$databasemanagerplugin_2.DatabaseManagerPlugin=class{constructor(){_DatabaseManagerPlugin_instances.add(this),_DatabaseManagerPlugin_app.set(this,void 0),_DatabaseManagerPlugin_element.set(this,void 0),_DatabaseManagerPlugin_mainContentContainer.set(this,void 0),_DatabaseManagerPlugin_contentDiv1.set(this,void 0),_DatabaseManagerPlugin_contentDiv2.set(this,void 0),_DatabaseManagerPlugin_contentDiv3.set(this,void 0),_DatabaseManagerPlugin_button1.set(this,void 0),_DatabaseManagerPlugin_button2.set(this,void 0),_DatabaseManagerPlugin_button3.set(this,void 0),_DatabaseManagerPlugin_button4.set(this,void 0),_DatabaseManagerPlugin_button5.set(this,void 0),_DatabaseManagerPlugin_button6.set(this,void 0),_DatabaseManagerPlugin_button7.set(this,void 0),_DatabaseManagerPlugin_button8.set(this,void 0),_DatabaseManagerPlugin_button9.set(this,void 0),_DatabaseManagerPlugin_aircraftEditor.set(this,void 0),_DatabaseManagerPlugin_helicopterEditor.set(this,void 0),_DatabaseManagerPlugin_groundUnitEditor.set(this,void 0),_DatabaseManagerPlugin_navyUnitEditor.set(this,void 0),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_element,document.createElement("div"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_element,"f").id="database-manager-panel",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_element,"f").oncontextmenu=()=>!1,____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_element,"f").classList.add("ol-dialog"),document.body.appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_element,"f")),this.toggle(!1);let a=document.createElement("div");____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_button1,document.createElement("button"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button1,"f").classList.add("tab-button"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button1,"f").textContent="Aircraft database",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button1,"f").onclick=()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_hideAll).call(this),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_aircraftEditor,"f").show(),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button1,"f").classList.add("selected")},a.appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button1,"f")),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_button2,document.createElement("button"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button2,"f").classList.add("tab-button"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button2,"f").textContent="Helicopter database",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button2,"f").onclick=()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_hideAll).call(this),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_helicopterEditor,"f").show(),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button2,"f").classList.add("selected")},a.appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button2,"f")),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_button3,document.createElement("button"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button3,"f").classList.add("tab-button"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button3,"f").textContent="Ground Unit database",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button3,"f").onclick=()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_hideAll).call(this),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_groundUnitEditor,"f").show(),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button3,"f").classList.add("selected")},a.appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button3,"f")),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_button4,document.createElement("button"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button4,"f").classList.add("tab-button"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button4,"f").textContent="Navy Unit database",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button4,"f").onclick=()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_hideAll).call(this),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_navyUnitEditor,"f").show(),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button4,"f").classList.add("selected")},a.appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button4,"f")),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_element,"f").appendChild(a),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_mainContentContainer,document.createElement("div"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_mainContentContainer,"f").classList.add("dm-container"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_element,"f").appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_mainContentContainer,"f")),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_contentDiv1,document.createElement("div"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv1,"f").classList.add("dm-content-container","ol-scrollable"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_mainContentContainer,"f").appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv1,"f")),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_contentDiv2,document.createElement("div"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv2,"f").classList.add("dm-content-container","ol-scrollable"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_mainContentContainer,"f").appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv2,"f")),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_contentDiv3,document.createElement("div"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv3,"f").classList.add("dm-content-container","ol-scrollable"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_mainContentContainer,"f").appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv3,"f")),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_aircraftEditor,new _$airuniteditor_1.AirUnitEditor(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv1,"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv2,"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv3,"f")),"f"),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_helicopterEditor,new _$airuniteditor_1.AirUnitEditor(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv1,"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv2,"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv3,"f")),"f"),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_groundUnitEditor,new _$grounduniteditor_3.GroundUnitEditor(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv1,"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv2,"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv3,"f")),"f"),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_navyUnitEditor,new _$navyuniteditor_6.NavyUnitEditor(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv1,"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv2,"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_contentDiv3,"f")),"f");let t=document.createElement("div");____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_button5,document.createElement("button"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button5,"f").textContent="Save",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button5,"f").title="Save the changes on the server",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button5,"f").onclick=()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_saveDatabases).call(this)},t.appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button5,"f")),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_button6,document.createElement("button"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button6,"f").textContent="Discard",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button6,"f").title="Discard all changes and reload the database from the server",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button6,"f").onclick=()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_loadDatabases).call(this)},t.appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button6,"f")),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_button7,document.createElement("button"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button7,"f").textContent="Reset defaults",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button7,"f").onclick=()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_resetToDefaultDatabases).call(this)},____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button7,"f").title="Reset the databases to the default values",t.appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button7,"f")),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_button8,document.createElement("button"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button8,"f").textContent="Restore previous",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button8,"f").onclick=()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_restoreToPreviousDatabases).call(this)},____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button8,"f").title="Restore the previously saved databases. Use this if you saved a database by mistake.",t.appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button8,"f")),____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_button9,document.createElement("button"),"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button9,"f").textContent="Close",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button9,"f").title="Close the Database Manager",____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button9,"f").onclick=()=>{this.toggle(!1)},t.appendChild(____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button9,"f")),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_element,"f").appendChild(t)}getName(){return"Database Control Plugin"}initialize(a){var t;____classPrivateFieldSet_2(this,_DatabaseManagerPlugin_app,a,"f"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f").getContextManager().add("databaseManager",{allowUnitCopying:!1,allowUnitPasting:!1,useSpawnMenu:!1,useUnitControlPanel:!1,useUnitInfoPanel:!1}),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_loadDatabases).call(this);var e=document.createElement("div"),i=document.createElement("button");i.textContent="Database manager",e.appendChild(i);var n=null===(t=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===t?void 0:t.getToolbarsManager().get("primaryToolbar"),s=n.getMainDropdown().getOptionElements(),_=Array.prototype.slice.call(s);return _.splice(_.length-3,0,e),n.getMainDropdown().setOptionsElements(_),i.onclick=()=>{var a;n.getMainDropdown().close(),"Game master"===(null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a?void 0:a.getMissionManager().getCommandModeOptions().commandMode)&&this.toggle()},!0}getElement(){return ____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_element,"f")}toggle(a){a?this.getElement().classList.toggle("hide",!a):this.getElement().classList.toggle("hide"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f")&&____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f").getContextManager().setContext(this.getElement().classList.contains("hide")?"olympus":"databaseManager")}},_DatabaseManagerPlugin_app=new WeakMap,_DatabaseManagerPlugin_element=new WeakMap,_DatabaseManagerPlugin_mainContentContainer=new WeakMap,_DatabaseManagerPlugin_contentDiv1=new WeakMap,_DatabaseManagerPlugin_contentDiv2=new WeakMap,_DatabaseManagerPlugin_contentDiv3=new WeakMap,_DatabaseManagerPlugin_button1=new WeakMap,_DatabaseManagerPlugin_button2=new WeakMap,_DatabaseManagerPlugin_button3=new WeakMap,_DatabaseManagerPlugin_button4=new WeakMap,_DatabaseManagerPlugin_button5=new WeakMap,_DatabaseManagerPlugin_button6=new WeakMap,_DatabaseManagerPlugin_button7=new WeakMap,_DatabaseManagerPlugin_button8=new WeakMap,_DatabaseManagerPlugin_button9=new WeakMap,_DatabaseManagerPlugin_aircraftEditor=new WeakMap,_DatabaseManagerPlugin_helicopterEditor=new WeakMap,_DatabaseManagerPlugin_groundUnitEditor=new WeakMap,_DatabaseManagerPlugin_navyUnitEditor=new WeakMap,_DatabaseManagerPlugin_instances=new WeakSet,_DatabaseManagerPlugin_hideAll=function(){____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_aircraftEditor,"f").hide(),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_helicopterEditor,"f").hide(),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_groundUnitEditor,"f").hide(),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_navyUnitEditor,"f").hide(),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button1,"f").classList.remove("selected"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button2,"f").classList.remove("selected"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button3,"f").classList.remove("selected"),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button4,"f").classList.remove("selected")},_DatabaseManagerPlugin_loadDatabases=function(){var a,t,e,i,n=null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a?void 0:a.getAircraftDatabase();null!=n&&____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_aircraftEditor,"f").setDatabase(n);var s=null===(t=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===t?void 0:t.getHelicopterDatabase();null!=s&&____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_helicopterEditor,"f").setDatabase(s);var _=null===(e=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===e?void 0:e.getGroundUnitDatabase();null!=_&&____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_groundUnitEditor,"f").setDatabase(_);var l=null===(i=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===i?void 0:i.getNavyUnitDatabase();null!=l&&____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_navyUnitEditor,"f").setDatabase(l),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_hideAll).call(this),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_aircraftEditor,"f").show(),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button1,"f").classList.add("selected")},_DatabaseManagerPlugin_saveDatabases=function(){var a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_aircraftEditor,"f").getDatabase();a&&____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_uploadDatabase).call(this,a,"aircraftdatabase","Aircraft database",()=>{var a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_helicopterEditor,"f").getDatabase();a&&____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_uploadDatabase).call(this,a,"helicopterDatabase","Helicopter database",()=>{var a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_groundUnitEditor,"f").getDatabase();a&&____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_uploadDatabase).call(this,a,"groundUnitDatabase","Ground Unit database",()=>{var a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_navyUnitEditor,"f").getDatabase();a&&____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_uploadDatabase).call(this,a,"navyUnitDatabase","Navy Unit database",()=>{var a,t,e,i,n;null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a||a.getAircraftDatabase().load(()=>{}),null===(t=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===t||t.getHelicopterDatabase().load(()=>{}),null===(e=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===e||e.getGroundUnitDatabase().load(()=>{}),null===(i=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===i||i.getNavyUnitDatabase().load(()=>{}),null===(n=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===n||n.getServerManager().reloadDatabases(()=>{var a,t;null===(t=null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a?void 0:a.getPopupsManager().get("infoPopup"))||void 0===t||t.setText("Olympus core databases reloaded")})})})})})},_DatabaseManagerPlugin_resetToDefaultDatabases=function(){____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_resetToDefaultDatabase).call(this,"aircraftdatabase","Aircraft database",()=>{var a;null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a||a.getAircraftDatabase().load(()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_resetToDefaultDatabase).call(this,"helicopterdatabase","Helicopter database",()=>{var a;null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a||a.getHelicopterDatabase().load(()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_resetToDefaultDatabase).call(this,"groundunitdatabase","Ground Unit database",()=>{var a;null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a||a.getGroundUnitDatabase().load(()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_resetToDefaultDatabase).call(this,"navyunitdatabase","Navy Unit database",()=>{var a;null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a||a.getNavyUnitDatabase().load(()=>{var a;____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_loadDatabases).call(this),null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a||a.getServerManager().reloadDatabases(()=>{var a,t;null===(t=null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a?void 0:a.getPopupsManager().get("infoPopup"))||void 0===t||t.setText("Olympus core databases reloaded")}),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_hideAll).call(this),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_aircraftEditor,"f").show(),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button1,"f").classList.add("selected")})})})})})})})})},_DatabaseManagerPlugin_restoreToPreviousDatabases=function(){____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_restoreToPreviousDatabase).call(this,"aircraftdatabase","Aircraft database",()=>{var a;null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a||a.getAircraftDatabase().load(()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_restoreToPreviousDatabase).call(this,"helicopterdatabase","Helicopter database",()=>{var a;null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a||a.getHelicopterDatabase().load(()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_restoreToPreviousDatabase).call(this,"groundunitdatabase","Ground Unit database",()=>{var a;null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a||a.getGroundUnitDatabase().load(()=>{____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_restoreToPreviousDatabase).call(this,"navyunitdatabase","Navy Unit database",()=>{var a;null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a||a.getNavyUnitDatabase().load(()=>{var a;____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_loadDatabases).call(this),null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a||a.getServerManager().reloadDatabases(()=>{var a,t;null===(t=null===(a=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===a?void 0:a.getPopupsManager().get("infoPopup"))||void 0===t||t.setText("Olympus core databases reloaded")}),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_instances,"m",_DatabaseManagerPlugin_hideAll).call(this),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_aircraftEditor,"f").show(),____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_button1,"f").classList.add("selected")})})})})})})})})},_DatabaseManagerPlugin_uploadDatabase=function(a,t,e,i){var n=new XMLHttpRequest;n.open("PUT","/api/databases/save/units/"+t),n.setRequestHeader("Content-Type","application/json"),n.onload=a=>{var t,s,_,l;200==n.status?(null===(s=null===(t=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===t?void 0:t.getPopupsManager().get("infoPopup"))||void 0===s||s.setText(e+" saved successfully"),i()):null===(l=null===(_=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===_?void 0:_.getPopupsManager().get("infoPopup"))||void 0===l||l.setText("An error has occurred while saving the "+e)},n.onerror=a=>{var t,i;null===(i=null===(t=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===t?void 0:t.getPopupsManager().get("infoPopup"))||void 0===i||i.setText("An error has occurred while saving the "+e)},n.send(JSON.stringify(a))},_DatabaseManagerPlugin_resetToDefaultDatabase=function(a,t,e){var i=new XMLHttpRequest;i.open("PUT","/api/databases/reset/units/"+a),i.setRequestHeader("Content-Type","application/json"),i.onload=a=>{var n,s,_,l;200==i.status?(null===(s=null===(n=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===n?void 0:n.getPopupsManager().get("infoPopup"))||void 0===s||s.setText(t+" reset successfully"),e()):null===(l=null===(_=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===_?void 0:_.getPopupsManager().get("infoPopup"))||void 0===l||l.setText("An error has occurred while resetting the "+t)},i.onerror=a=>{var e,i;null===(i=null===(e=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===e?void 0:e.getPopupsManager().get("infoPopup"))||void 0===i||i.setText("An error has occurred while resetting the "+t)},i.send("")},_DatabaseManagerPlugin_restoreToPreviousDatabase=function(a,t,e){var i=new XMLHttpRequest;i.open("PUT","/api/databases/restore/units/"+a),i.setRequestHeader("Content-Type","application/json"),i.onload=a=>{var n,s,_,l;200==i.status?(null===(s=null===(n=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===n?void 0:n.getPopupsManager().get("infoPopup"))||void 0===s||s.setText(t+" restored successfully"),e()):null===(l=null===(_=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===_?void 0:_.getPopupsManager().get("infoPopup"))||void 0===l||l.setText("An error has occurred while restoring the "+t)},i.onerror=a=>{var e,i;null===(i=null===(e=____classPrivateFieldGet_2(this,_DatabaseManagerPlugin_app,"f"))||void 0===e?void 0:e.getPopupsManager().get("infoPopup"))||void 0===i||i.setText("An error has occurred while restoring the "+t)},i.send("")};var _$index_4={};Object.defineProperty(_$index_4,"__esModule",{value:!0}),globalThis.getOlympusPlugin=()=>new _$databasemanagerplugin_2.DatabaseManagerPlugin}(); \ No newline at end of file diff --git a/frontend/website/plugins/databasemanager/package.json b/frontend/website/plugins/databasemanager/package.json deleted file mode 100644 index bd252192..00000000 --- a/frontend/website/plugins/databasemanager/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "DatabaseManagerPlugin", - "version": "v0.0.1", - "private": true, - "scripts": { - "build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat", - "build-release": "browserify ./src/index.ts -p [ tsify --noImplicitAny] -p [ tinyify ] > index.js && copy.bat", - "start": "npm run copy & concurrently --kill-others \"npm run watch\"", - "copy": "copy.bat", - "watch": "watchify ./src/index.ts --debug -o ../../public/plugins/databasemanager/index.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js']" - }, - "dependencies": {}, - "devDependencies": { - "@babel/preset-env": "^7.21.4", - "@types/node": "^18.16.1", - "@types/sortablejs": "^1.15.0", - "babelify": "^10.0.0", - "browserify": "^17.0.0", - "concurrently": "^7.6.0", - "cp": "^0.2.0", - "esmify": "^2.1.1", - "nodemon": "^2.0.20", - "requirejs": "^2.3.6", - "sortablejs": "^1.15.0", - "tinyify": "^4.0.0", - "tsify": "^5.0.4", - "tslib": "latest", - "typescript": "^4.9.4", - "usng.js": "^0.4.5", - "watchify": "^4.0.0" - } -} diff --git a/frontend/website/plugins/databasemanager/plugin.json b/frontend/website/plugins/databasemanager/plugin.json deleted file mode 100644 index 357d8f13..00000000 --- a/frontend/website/plugins/databasemanager/plugin.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Database Manager", - "version": "0.0.1", - "description": "This plugin allows to edit the unit databases", - "author": "DCSOlympus team" -} \ No newline at end of file diff --git a/frontend/website/plugins/databasemanager/src/airuniteditor.ts b/frontend/website/plugins/databasemanager/src/airuniteditor.ts deleted file mode 100644 index 80d7aaf8..00000000 --- a/frontend/website/plugins/databasemanager/src/airuniteditor.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { LoadoutBlueprint, UnitBlueprint } from "interfaces"; -import { UnitEditor } from "./uniteditor"; -import { LoadoutEditor } from "./loadouteditor"; -import { addCheckboxInput, addDropdownInput, addLoadoutsScroll, addNewElementInput, addStringInput } from "./utils"; - -/** Database editor for Air Units, both Aircraft and Helicopter since they are identical in terms of datbase entries. - * - */ -export class AirUnitEditor extends UnitEditor { - - #loadoutEditor: LoadoutEditor | null = null; - - constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) { - super(contentDiv1, contentDiv2, contentDiv3); - - /* The loadout editor allows to edit the loadout (who could have thought eh?) */ - this.#loadoutEditor = new LoadoutEditor(this.contentDiv3); - - /* Refresh the loadout editor if needed */ - this.contentDiv3.addEventListener("refresh", () => { - if (this.visible) - this.#loadoutEditor?.show(); - }); - } - - /** Sets a unit blueprint as the currently active one - * - * @param blueprint The blueprint to edit - */ - setBlueprint(blueprint: UnitBlueprint) { - this.blueprint = blueprint; - - if (this.blueprint !== null) { - this.contentDiv2.replaceChildren(); - - var title = document.createElement("label"); - title.innerText = "Unit properties"; - this.contentDiv2.appendChild(title); - - addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => { blueprint.name = value; }, true); - addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => { blueprint.label = value; }); - addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => { blueprint.shortLabel = value; }); - addDropdownInput(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value: string) => {blueprint.coalition = value; }); - addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value: string) => {blueprint.era = value; }); - addStringInput(this.contentDiv2, "Filename", blueprint.filename ?? "", "text", (value: string) => { blueprint.filename = value; }); - addStringInput(this.contentDiv2, "Cost", String(blueprint.cost) ?? "", "number", (value: string) => { blueprint.cost = parseFloat(value); }); - addCheckboxInput(this.contentDiv2, "Can target point", blueprint.canTargetPoint ?? false, (value: boolean) => {blueprint.canTargetPoint = value;}) - addStringInput(this.contentDiv2, "Description", blueprint.description ?? "", "text", (value: string) => {blueprint.description = value; }); - addStringInput(this.contentDiv2, "Tags", blueprint.tags ?? "", "text", (value: string) => {blueprint.tags = value; }); - - /* Add a scrollable list of loadouts that the user can edit */ - var title = document.createElement("label"); - title.innerText = "Loadouts"; - this.contentDiv2.appendChild(title); - addLoadoutsScroll(this.contentDiv2, blueprint.loadouts ?? [], (loadout: LoadoutBlueprint) => { - this.#loadoutEditor?.setLoadout(loadout); - this.#loadoutEditor?.show(); - }); - addNewElementInput(this.contentDiv2, (ev: MouseEvent, input: HTMLInputElement) => { this.addLoadout(input.value); }); - - this.#loadoutEditor?.hide(); - } - } - - /** Add a new empty blueprint - * - * @param key Blueprint key - */ - addBlueprint(key: string) { - if (this.database != null) { - this.database.blueprints[key] = { - name: key, - coalition: "", - label: "", - shortLabel: "", - era: "", - loadouts: [], - enabled: true - } - this.show(); - this.setBlueprint(this.database.blueprints[key]); - } - } - - /** Add a new empty loadout to the currently active blueprint - * - * @param loadoutName The name of the new loadout - */ - addLoadout(loadoutName: string) { - if (loadoutName && this.blueprint !== null) { - this.blueprint.loadouts?.push({ - name: loadoutName, - code: "", - fuel: 1, - items: [], - roles: [], - enabled: true - }) - this.setBlueprint(this.blueprint); - } - } - - /** Hide the editor - * - */ - hide() { - super.hide(); - this.#loadoutEditor?.hide(); - } -} diff --git a/frontend/website/plugins/databasemanager/src/databasemanagerplugin.ts b/frontend/website/plugins/databasemanager/src/databasemanagerplugin.ts deleted file mode 100644 index f21bda93..00000000 --- a/frontend/website/plugins/databasemanager/src/databasemanagerplugin.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { OlympusPlugin, UnitBlueprint } from "interfaces"; -import { AirUnitEditor } from "./airuniteditor"; -import { OlympusApp } from "olympusapp"; -import { GroundUnitEditor } from "./grounduniteditor"; -import { PrimaryToolbar } from "toolbars/primarytoolbar"; -import { NavyUnitEditor } from "./navyuniteditor"; - -/** Database Manager - * - * This database provides a user interface to allow easier and convenient unit databases manipulation. It allows to edit all the fields of the units databases, save them - * on the server, and restore the defaults. - * - * TODO: - * Add ability to manage liveries - * - */ - -export class DatabaseManagerPlugin implements OlympusPlugin { - #app!: OlympusApp; - - #element: HTMLElement; - #mainContentContainer: HTMLElement; - #contentDiv1: HTMLElement; - #contentDiv2: HTMLElement; - #contentDiv3: HTMLElement; - - /* Upper tab buttons */ - #button1: HTMLButtonElement; - #button2: HTMLButtonElement; - #button3: HTMLButtonElement; - #button4: HTMLButtonElement; - - /* Lower operation buttons */ - #button5: HTMLButtonElement; - #button6: HTMLButtonElement; - #button7: HTMLButtonElement; - #button8: HTMLButtonElement; - #button9: HTMLButtonElement; - - /* Database editors */ - #aircraftEditor: AirUnitEditor; - #helicopterEditor: AirUnitEditor; - #groundUnitEditor: GroundUnitEditor; - #navyUnitEditor: NavyUnitEditor; - - constructor() { - /* Create main HTML element */ - this.#element = document.createElement("div"); - this.#element.id = "database-manager-panel"; - this.#element.oncontextmenu = () => { return false; } - this.#element.classList.add("ol-dialog"); - document.body.appendChild(this.#element); - - /* Start hidden */ - this.toggle(false); - - /* Create the top tab buttons container and buttons */ - let topButtonContainer = document.createElement("div"); - - this.#button1 = document.createElement("button"); - this.#button1.classList.add("tab-button"); - this.#button1.textContent = "Aircraft database"; - this.#button1.onclick = () => { this.#hideAll(); this.#aircraftEditor.show(); this.#button1.classList.add("selected"); }; - topButtonContainer.appendChild(this.#button1); - - this.#button2 = document.createElement("button"); - this.#button2.classList.add("tab-button"); - this.#button2.textContent = "Helicopter database"; - this.#button2.onclick = () => { this.#hideAll(); this.#helicopterEditor.show(); this.#button2.classList.add("selected"); }; - topButtonContainer.appendChild(this.#button2); - - this.#button3 = document.createElement("button"); - this.#button3.classList.add("tab-button"); - this.#button3.textContent = "Ground Unit database"; - this.#button3.onclick = () => { this.#hideAll(); this.#groundUnitEditor.show(); this.#button3.classList.add("selected"); }; - topButtonContainer.appendChild(this.#button3); - - this.#button4 = document.createElement("button"); - this.#button4.classList.add("tab-button"); - this.#button4.textContent = "Navy Unit database"; - this.#button4.onclick = () => { this.#hideAll(); this.#navyUnitEditor.show(); this.#button4.classList.add("selected"); }; - topButtonContainer.appendChild(this.#button4); - - this.#element.appendChild(topButtonContainer); - - /* Create the container for the database editor elements and the elements themselves */ - this.#mainContentContainer = document.createElement("div"); - this.#mainContentContainer.classList.add("dm-container"); - this.#element.appendChild(this.#mainContentContainer); - - this.#contentDiv1 = document.createElement("div"); - this.#contentDiv1.classList.add("dm-content-container", "ol-scrollable"); - this.#mainContentContainer.appendChild(this.#contentDiv1); - - this.#contentDiv2 = document.createElement("div"); - this.#contentDiv2.classList.add("dm-content-container", "ol-scrollable"); - this.#mainContentContainer.appendChild(this.#contentDiv2); - - this.#contentDiv3 = document.createElement("div"); - this.#contentDiv3.classList.add("dm-content-container", "ol-scrollable"); - this.#mainContentContainer.appendChild(this.#contentDiv3); - - /* Create the database editors, which use the three divs created before */ - this.#aircraftEditor = new AirUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3); - this.#helicopterEditor = new AirUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3); - this.#groundUnitEditor = new GroundUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3); - this.#navyUnitEditor = new NavyUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3); - - /* Create the bottom buttons container. These buttons allow to save, restore, reset, and discard the changes */ - let bottomButtonContainer = document.createElement("div"); - - this.#button5 = document.createElement("button"); - this.#button5.textContent = "Save"; - this.#button5.title = "Save the changes on the server" - this.#button5.onclick = () => { this.#saveDatabases();}; - bottomButtonContainer.appendChild(this.#button5); - - this.#button6 = document.createElement("button"); - this.#button6.textContent = "Discard"; - this.#button6.title = "Discard all changes and reload the database from the server"; - this.#button6.onclick = () => { this.#loadDatabases(); }; - bottomButtonContainer.appendChild(this.#button6); - - this.#button7 = document.createElement("button"); - this.#button7.textContent = "Reset defaults"; - this.#button7.onclick = () => { this.#resetToDefaultDatabases(); }; - this.#button7.title = "Reset the databases to the default values"; - bottomButtonContainer.appendChild(this.#button7); - - this.#button8 = document.createElement("button"); - this.#button8.textContent = "Restore previous"; - this.#button8.onclick = () => { this.#restoreToPreviousDatabases(); }; - this.#button8.title = "Restore the previously saved databases. Use this if you saved a database by mistake."; - bottomButtonContainer.appendChild(this.#button8); - - this.#button9 = document.createElement("button"); - this.#button9.textContent = "Close"; - this.#button9.title = "Close the Database Manager" - this.#button9.onclick = () => { this.toggle(false); }; - bottomButtonContainer.appendChild(this.#button9); - - this.#element.appendChild(bottomButtonContainer); - } - - /** - * - * @returns The name of the plugin - */ - getName() { - return "Database Control Plugin" - } - - /** Initialize the plugin - * - * @param app The OlympusApp singleton - * @returns True if successfull - */ - initialize(app: any) { - this.#app = app; - - const contextManager = this.#app.getContextManager(); - contextManager.add( "databaseManager", { - "allowUnitCopying": false, - "allowUnitPasting": false, - "useSpawnMenu": false, - "useUnitControlPanel": false, - "useUnitInfoPanel": false - }); - - /* Load the databases and initialize the editors */ - this.#loadDatabases(); - - /* Add a button to the main Olympus App to allow the users to open the dialog */ - var mainButtonDiv = document.createElement("div"); - var mainButton = document.createElement("button"); - mainButton.textContent = "Database manager"; - mainButtonDiv.appendChild(mainButton); - var toolbar: PrimaryToolbar = this.#app?.getToolbarsManager().get("primaryToolbar") as PrimaryToolbar; - var elements = toolbar.getMainDropdown().getOptionElements(); - var arr = Array.prototype.slice.call(elements); - arr.splice(arr.length - 3, 0, mainButtonDiv); - toolbar.getMainDropdown().setOptionsElements(arr); - mainButton.onclick = () => { - toolbar.getMainDropdown().close(); - if (this.#app?.getMissionManager().getCommandModeOptions().commandMode === "Game master") - this.toggle(); - } - - return true; - } - - /** - * - * @returns The main container element - */ - getElement() { - return this.#element; - } - - /** Toggles the visibility of the dialog - * - * @param bool Force a specific visibility state - */ - toggle(bool?: boolean) { - if (bool) - this.getElement().classList.toggle("hide", !bool); - else - this.getElement().classList.toggle("hide"); - - if ( this.#app ) - this.#app.getContextManager().setContext( this.getElement().classList.contains("hide") ? "olympus" : "databaseManager" ); - } - - /** Hide all the editors - * - */ - #hideAll() { - this.#aircraftEditor.hide(); - this.#helicopterEditor.hide(); - this.#groundUnitEditor.hide(); - this.#navyUnitEditor.hide(); - - this.#button1.classList.remove("selected"); - this.#button2.classList.remove("selected"); - this.#button3.classList.remove("selected"); - this.#button4.classList.remove("selected"); - } - - /** Load the databases from the app to the editor. Note, this does not reload the databases from the server to the app - * - */ - #loadDatabases() { - var aircraftDatabase = this.#app?.getAircraftDatabase(); - if (aircraftDatabase != null) - this.#aircraftEditor.setDatabase(aircraftDatabase); - - var helicopterDatabase = this.#app?.getHelicopterDatabase(); - if (helicopterDatabase != null) - this.#helicopterEditor.setDatabase(helicopterDatabase); - - var groundUnitDatabase = this.#app?.getGroundUnitDatabase(); - if (groundUnitDatabase != null) - this.#groundUnitEditor.setDatabase(groundUnitDatabase); - - var navyUnitDatabase = this.#app?.getNavyUnitDatabase(); - if (navyUnitDatabase != null) - this.#navyUnitEditor.setDatabase(navyUnitDatabase); - - this.#hideAll(); - this.#aircraftEditor.show(); - this.#button1.classList.add("selected"); - } - - /** Save the databases on the server and reloads it to apply the changes - * - */ - #saveDatabases() { - var aircraftDatabase = this.#aircraftEditor.getDatabase(); - if (aircraftDatabase){ - this.#uploadDatabase(aircraftDatabase, "aircraftdatabase", "Aircraft database", () => { - var helicopterDatabase = this.#helicopterEditor.getDatabase(); - if (helicopterDatabase) { - this.#uploadDatabase(helicopterDatabase, "helicopterDatabase", "Helicopter database", () => { - var groundUnitDatabase = this.#groundUnitEditor.getDatabase(); - if (groundUnitDatabase) { - this.#uploadDatabase(groundUnitDatabase, "groundUnitDatabase", "Ground Unit database", () => { - var navyUnitDatabase = this.#navyUnitEditor.getDatabase(); - if (navyUnitDatabase) { - this.#uploadDatabase(navyUnitDatabase, "navyUnitDatabase", "Navy Unit database", () => { - this.#app?.getAircraftDatabase().load(() => {}); - this.#app?.getHelicopterDatabase().load(() => {}); - this.#app?.getGroundUnitDatabase().load(() => {}); - this.#app?.getNavyUnitDatabase().load(() => {}); - - this.#app?.getServerManager().reloadDatabases(() => { - this.#app?.getPopupsManager().get("infoPopup")?.setText("Olympus core databases reloaded"); - }) - }); - } - }); - } - }); - } - }); - } - } - - /** Resets the databases to the default values - * - */ - #resetToDefaultDatabases() { - this.#resetToDefaultDatabase("aircraftdatabase", "Aircraft database", () => { - this.#app?.getAircraftDatabase().load(() => { - this.#resetToDefaultDatabase("helicopterdatabase", "Helicopter database", () => { - this.#app?.getHelicopterDatabase().load(() => { - this.#resetToDefaultDatabase("groundunitdatabase", "Ground Unit database", () => { - this.#app?.getGroundUnitDatabase().load(() => { - this.#resetToDefaultDatabase("navyunitdatabase", "Navy Unit database", () => { - this.#app?.getNavyUnitDatabase().load(() => { - this.#loadDatabases(); - - this.#app?.getServerManager().reloadDatabases(() => { - this.#app?.getPopupsManager().get("infoPopup")?.setText("Olympus core databases reloaded"); - }) - - this.#hideAll(); - this.#aircraftEditor.show(); - this.#button1.classList.add("selected"); - }); - }); - }); - }); - }); - }); - }); - }); - } - - /** Restores the databases to the previous saved values. This is useful if you saved the databases by mistake and want to undo the error. - * - */ - #restoreToPreviousDatabases() { - this.#restoreToPreviousDatabase("aircraftdatabase", "Aircraft database", () => { - this.#app?.getAircraftDatabase().load(() => { - this.#restoreToPreviousDatabase("helicopterdatabase", "Helicopter database", () => { - this.#app?.getHelicopterDatabase().load(() => { - this.#restoreToPreviousDatabase("groundunitdatabase", "Ground Unit database", () => { - this.#app?.getGroundUnitDatabase().load(() => { - this.#restoreToPreviousDatabase("navyunitdatabase", "Navy Unit database", () => { - this.#app?.getNavyUnitDatabase().load(() => { - this.#loadDatabases(); - - this.#app?.getServerManager().reloadDatabases(() => { - this.#app?.getPopupsManager().get("infoPopup")?.setText("Olympus core databases reloaded"); - }) - - this.#hideAll(); - this.#aircraftEditor.show(); - this.#button1.classList.add("selected"); - }); - }); - }); - }); - }); - }); - }); - }); - } - - /** Upload a single database to the server - * - * @param database The database - * @param name The name of the database as it will be saved on the server - * @param label A label used in the info popup - */ - #uploadDatabase(database: { blueprints: { [key: string]: UnitBlueprint } }, name: string, label: string, callback: CallableFunction) { - var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("PUT", "/api/databases/save/units/" + name); - xmlHttp.setRequestHeader("Content-Type", "application/json"); - xmlHttp.onload = (res: any) => { - if (xmlHttp.status == 200) { - this.#app?.getPopupsManager().get("infoPopup")?.setText(label + " saved successfully"); - callback(); - } - else { - this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while saving the " + label); - } - }; - xmlHttp.onerror = (res: any) => { - this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while saving the " + label); - } - xmlHttp.send(JSON.stringify(database)); - } - - /** Resets a database to its default values on the server. NOTE: this only resets the database on the server, it will not reload it in the app. - * - * @param name The name of the database as it is saved on the server - * @param label A label used in the info popup - * @param callback Called when the operation is completed - */ - #resetToDefaultDatabase(name: string, label: string, callback: CallableFunction) { - var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("PUT", "/api/databases/reset/units/" + name); - xmlHttp.setRequestHeader("Content-Type", "application/json"); - xmlHttp.onload = (res: any) => { - if (xmlHttp.status == 200) { - this.#app?.getPopupsManager().get("infoPopup")?.setText(label + " reset successfully"); - callback(); - } - else { - this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while resetting the " + label); - } - }; - xmlHttp.onerror = (res: any) => { - this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while resetting the " + label) - } - xmlHttp.send(""); - } - - /** Restores a database to its previously saved values on the server. NOTE: this only restores the database on the server, it will not reload it in the app. - * - * @param name The name of the database as it is saved on the server - * @param label A label used in the info popup - * @param callback Called when the operation is completed - */ - #restoreToPreviousDatabase(name: string, label: string, callback: CallableFunction) { - var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("PUT", "/api/databases/restore/units/" + name); - xmlHttp.setRequestHeader("Content-Type", "application/json"); - xmlHttp.onload = (res: any) => { - if (xmlHttp.status == 200) { - this.#app?.getPopupsManager().get("infoPopup")?.setText(label + " restored successfully"); - callback(); - } - else { - this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while restoring the " + label); - } - }; - xmlHttp.onerror = (res: any) => { - this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while restoring the " + label) - } - xmlHttp.send(""); - } -} \ No newline at end of file diff --git a/frontend/website/plugins/databasemanager/src/grounduniteditor.ts b/frontend/website/plugins/databasemanager/src/grounduniteditor.ts deleted file mode 100644 index 41d68345..00000000 --- a/frontend/website/plugins/databasemanager/src/grounduniteditor.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { UnitBlueprint } from "interfaces"; -import { UnitEditor } from "./uniteditor"; -import { addCheckboxInput, addDropdownInput, addStringInput } from "./utils"; - -/** Database editor for ground units - * - */ -export class GroundUnitEditor extends UnitEditor { - #blueprint: UnitBlueprint | null = null; - - constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) { - super(contentDiv1, contentDiv2, contentDiv3); - } - - /** Sets a unit blueprint as the currently active one - * - * @param blueprint The blueprint to edit - */ - setBlueprint(blueprint: UnitBlueprint) { - this.#blueprint = blueprint; - - if (this.#blueprint !== null) { - this.contentDiv2.replaceChildren(); - - var title = document.createElement("label"); - title.innerText = "Unit properties"; - this.contentDiv2.appendChild(title); - - addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => {blueprint.name = value; }, true); - addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => {blueprint.label = value; }); - addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => {blueprint.shortLabel = value; }); - addStringInput(this.contentDiv2, "Type", blueprint.type?? "", "text", (value: string) => {blueprint.type = value; }); - addStringInput(this.contentDiv2, "Unit when grouped", blueprint.unitWhenGrouped?? "", "text", (value: string) => {blueprint.unitWhenGrouped = value; }); - addDropdownInput(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value: string) => {blueprint.coalition = value; }); - addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value: string) => {blueprint.era = value; }); - //addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; }); - addStringInput(this.contentDiv2, "Cost", String(blueprint.cost)?? "", "number", (value: string) => {blueprint.cost = parseFloat(value); }); - addStringInput(this.contentDiv2, "Acquisition range [m]", String(blueprint.acquisitionRange)?? "", "number", (value: string) => {blueprint.acquisitionRange = parseFloat(value); }); - addStringInput(this.contentDiv2, "Engagement range [m]", String(blueprint.engagementRange)?? "", "number", (value: string) => {blueprint.engagementRange = parseFloat(value); }); - addStringInput(this.contentDiv2, "Targeting range [m]", String(blueprint.targetingRange)?? "", "number", (value: string) => {blueprint.targetingRange = parseFloat(value); }); - addStringInput(this.contentDiv2, "Aim method range [m]", String(blueprint.aimMethodRange)?? "", "number", (value: string) => {blueprint.aimMethodRange = parseFloat(value); }); - addStringInput(this.contentDiv2, "Barrel height [m]", String(blueprint.barrelHeight)?? "", "number", (value: string) => {blueprint.barrelHeight = parseFloat(value); }); - addStringInput(this.contentDiv2, "Muzzle velocity [m/s]", String(blueprint.muzzleVelocity)?? "", "number", (value: string) => {blueprint.muzzleVelocity = parseFloat(value); }); - addStringInput(this.contentDiv2, "Aim time [s]", String(blueprint.aimTime)?? "", "number", (value: string) => {blueprint.aimTime = parseFloat(value); }); - addStringInput(this.contentDiv2, "Shots to fire", String(blueprint.shotsToFire)?? "", "number", (value: string) => {blueprint.shotsToFire = Math.round(parseFloat(value)); }); - addStringInput(this.contentDiv2, "Shots base interval [s]", String(blueprint.shotsBaseInterval)?? "", "number", (value: string) => {blueprint.shotsBaseInterval = Math.round(parseFloat(value)); }); - addStringInput(this.contentDiv2, "Shots base scatter [°]", String(blueprint.shotsBaseScatter)?? "", "number", (value: string) => {blueprint.shotsBaseScatter = Math.round(parseFloat(value)); }); - addStringInput(this.contentDiv2, "Alertness time constant [s]", String(blueprint.alertnessTimeConstant)?? "", "number", (value: string) => {blueprint.alertnessTimeConstant = Math.round(parseFloat(value)); }); - addCheckboxInput(this.contentDiv2, "Can target point", blueprint.canTargetPoint ?? false, (value: boolean) => {blueprint.canTargetPoint = value;}) - addCheckboxInput(this.contentDiv2, "Can rearm", blueprint.canRearm ?? false, (value: boolean) => {blueprint.canRearm = value;}) - addCheckboxInput(this.contentDiv2, "Can operate as AAA", blueprint.canAAA ?? false, (value: boolean) => {blueprint.canAAA = value;}) - addCheckboxInput(this.contentDiv2, "Indirect fire (e.g. mortar)", blueprint.indirectFire ?? false, (value: boolean) => {blueprint.indirectFire = value;}) - addStringInput(this.contentDiv2, "Description", blueprint.description ?? "", "text", (value: string) => {blueprint.description = value; }); - addStringInput(this.contentDiv2, "Tags", blueprint.tags ?? "", "text", (value: string) => {blueprint.tags = value; }); - addStringInput(this.contentDiv2, "Marker file", blueprint.markerFile ?? "", "text", (value: string) => {blueprint.markerFile = value; }); - } - } - - /** Add a new empty blueprint - * - * @param key Blueprint key - */ - addBlueprint(key: string) { - if (this.database != null) { - this.database.blueprints[key] = { - name: key, - coalition: "", - label: "", - shortLabel: "", - era: "", - enabled: true - } - this.show(); - this.setBlueprint(this.database.blueprints[key]); - } - } -} diff --git a/frontend/website/plugins/databasemanager/src/index.ts b/frontend/website/plugins/databasemanager/src/index.ts deleted file mode 100644 index 71c7bbce..00000000 --- a/frontend/website/plugins/databasemanager/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { DatabaseManagerPlugin } from "./databasemanagerplugin"; - -globalThis.getOlympusPlugin = () => { - return new DatabaseManagerPlugin(); -} \ No newline at end of file diff --git a/frontend/website/plugins/databasemanager/src/loadouteditor.ts b/frontend/website/plugins/databasemanager/src/loadouteditor.ts deleted file mode 100644 index 5f07c605..00000000 --- a/frontend/website/plugins/databasemanager/src/loadouteditor.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { LoadoutBlueprint } from "interfaces"; -import { addLoadoutItemsEditor, addStringInput, arrayToString, stringToArray } from "./utils"; - -/** The LoadoutEditor allows the user to edit a loadout - * - */ -export class LoadoutEditor { - #contentDiv: HTMLElement; - #loadout: LoadoutBlueprint | null = null; - #visible: boolean = false; - - constructor(contentDiv: HTMLElement) { - this.#contentDiv = contentDiv; - this.#contentDiv.addEventListener("refresh", () => { - if (this.#visible) - this.show(); - }) - } - - /** Set the loadout to edit - * - * @param loadout The loadout to edit - */ - setLoadout(loadout: LoadoutBlueprint) { - this.#loadout = loadout; - } - - /** Show the editor - * - */ - show() { - this.#visible = true; - this.#contentDiv.replaceChildren(); - - var title = document.createElement("label"); - title.innerText = "Loadout properties"; - this.#contentDiv.appendChild(title); - - if (this.#loadout) { - var loadout = this.#loadout; - addStringInput(this.#contentDiv, "Name", loadout.name, "text", (value: string) => {loadout.name = value; this.#contentDiv.dispatchEvent(new Event("refresh"));}); - addStringInput(this.#contentDiv, "Code", loadout.code, "text", (value: string) => {loadout.code = value; }); - addStringInput(this.#contentDiv, "Roles", arrayToString(loadout.roles), "text", (value: string) => {loadout.roles = stringToArray(value);}); - addLoadoutItemsEditor(this.#contentDiv, this.#loadout); - } - } - - /** Hide the editor - * - */ - hide() { - this.#visible = false; - this.#contentDiv.replaceChildren(); - } -} \ No newline at end of file diff --git a/frontend/website/plugins/databasemanager/src/navyuniteditor.ts b/frontend/website/plugins/databasemanager/src/navyuniteditor.ts deleted file mode 100644 index 0b8ddfa6..00000000 --- a/frontend/website/plugins/databasemanager/src/navyuniteditor.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { UnitBlueprint } from "interfaces"; -import { UnitEditor } from "./uniteditor"; -import { addDropdownInput, addStringInput } from "./utils"; - -/** Database editor for navy units - * - */ -export class NavyUnitEditor extends UnitEditor { - #blueprint: UnitBlueprint | null = null; - - constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) { - super(contentDiv1, contentDiv2, contentDiv3); - } - - /** Sets a unit blueprint as the currently active one - * - * @param blueprint The blueprint to edit - */ - setBlueprint(blueprint: UnitBlueprint) { - this.#blueprint = blueprint; - - if (this.#blueprint !== null) { - this.contentDiv2.replaceChildren(); - - var title = document.createElement("label"); - title.innerText = "Unit properties"; - this.contentDiv2.appendChild(title); - - addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => {blueprint.name = value; }, true); - addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => {blueprint.label = value; }); - addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => {blueprint.shortLabel = value; }); - addStringInput(this.contentDiv2, "Type", blueprint.type?? "", "text", (value: string) => {blueprint.type = value; }); - addDropdownInput(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value: string) => {blueprint.coalition = value; }); - addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value: string) => {blueprint.era = value; }); - //addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; }); - addStringInput(this.contentDiv2, "Cost", String(blueprint.cost)?? "", "number", (value: string) => {blueprint.cost = parseFloat(value); }); - addStringInput(this.contentDiv2, "Barrel height [m]", String(blueprint.barrelHeight)?? "", "number", (value: string) => {blueprint.barrelHeight = parseFloat(value); }); - addStringInput(this.contentDiv2, "Muzzle velocity [m/s]", String(blueprint.muzzleVelocity)?? "", "number", (value: string) => {blueprint.muzzleVelocity = parseFloat(value); }); - } - } - - /** Add a new empty blueprint - * - * @param key Blueprint key - */ - addBlueprint(key: string) { - if (this.database != null) { - this.database.blueprints[key] = { - name: key, - coalition: "", - label: "", - shortLabel: "", - era: "", - enabled: true - } - this.show(); - this.setBlueprint(this.database.blueprints[key]); - } - } -} diff --git a/frontend/website/plugins/databasemanager/src/uniteditor.ts b/frontend/website/plugins/databasemanager/src/uniteditor.ts deleted file mode 100644 index 00c59fc9..00000000 --- a/frontend/website/plugins/databasemanager/src/uniteditor.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { UnitBlueprint } from "interfaces"; -import { UnitDatabase } from "unit/databases/unitdatabase"; -import { addBlueprintsScroll, addNewElementInput } from "./utils"; - -/** Base abstract class of Unit database editors - * - */ -export abstract class UnitEditor { - blueprint: UnitBlueprint | null = null; - database: {blueprints: {[key: string]: UnitBlueprint}} | null = null; - visible: boolean = false; - contentDiv1: HTMLElement; - contentDiv2: HTMLElement; - contentDiv3: HTMLElement; - - constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) { - this.contentDiv1 = contentDiv1; - this.contentDiv2 = contentDiv2; - this.contentDiv3 = contentDiv3; - - /* Refresh the list of units if it changes */ - this.contentDiv1.addEventListener("refresh", () => { - if (this.visible) - this.show(); - }) - - /* If the unit properties or loadout are edited, reload the editor */ - this.contentDiv2.addEventListener("refresh", () => { - if (this.visible) { - if (this.blueprint !== null) - this.setBlueprint(this.blueprint); - } - }); - - this.contentDiv3.addEventListener("refresh", () => { - if (this.visible) { - if (this.blueprint !== null) - this.setBlueprint(this.blueprint); - } - }); - } - - /** - * - * @param database The database that the editor will operate on - */ - setDatabase(database: UnitDatabase) { - this.database = JSON.parse(JSON.stringify({blueprints: database.getBlueprints(true)})); - } - - /** Show the editor - * @param filter String filter - */ - show(filter: string = "") { - this.visible = true; - this.contentDiv1.replaceChildren(); - this.contentDiv2.replaceChildren(); - this.contentDiv3.replaceChildren(); - - /* Create the list of units. Each unit is clickable to activate the editor on it */ - if (this.database != null) { - var title = document.createElement("label"); - title.innerText = "Units list"; - this.contentDiv1.appendChild(title); - - var filterInput = document.createElement("input"); - filterInput.value = filter; - this.contentDiv1.appendChild(filterInput); - - filterInput.onchange = (e: Event) => { - this.show((e.target as HTMLInputElement).value); - } - - this.addBlueprints(filter); - } - } - - /** Hide the editor - * - */ - hide() { - this.visible = false; - this.contentDiv1.replaceChildren(); - this.contentDiv2.replaceChildren(); - this.contentDiv3.replaceChildren(); - } - - /** - * - * @returns The edited database - */ - getDatabase() { - return this.database; - } - - /** - * - * @param filter String filter - */ - addBlueprints(filter: string = "") { - if (this.database) { - addBlueprintsScroll(this.contentDiv1, this.database, filter, (key: string) => { - if (this.database != null) - this.setBlueprint(this.database.blueprints[key]) - }); - - addNewElementInput(this.contentDiv1, (ev: MouseEvent, input: HTMLInputElement) => { - if (input.value != "") - this.addBlueprint((input).value); - }); - } - } - - /* Abstract methods which will depend on the specific type of units */ - abstract setBlueprint(blueprint: UnitBlueprint): void; - abstract addBlueprint(key: string): void; -} \ No newline at end of file diff --git a/frontend/website/plugins/databasemanager/src/utils.ts b/frontend/website/plugins/databasemanager/src/utils.ts deleted file mode 100644 index ba166dec..00000000 --- a/frontend/website/plugins/databasemanager/src/utils.ts +++ /dev/null @@ -1,297 +0,0 @@ -import { LoadoutBlueprint, LoadoutItemBlueprint, UnitBlueprint } from "interfaces"; - -/** This file contains a set of utility functions that are reused in the various editors and allows to declutter the classes - * - */ - -/** Add a string input in the form of String: [ value ] - * - * @param div The HTMLElement that will contain the input - * @param key The key of the input, which will be used as label - * @param value The initial value of the input - * @param type The type of the input, e.g. "Text" or "Number" as per html standard - * @param callback Callback called when the user enters a new value - * @param disabled If true, the input will be disabled and read only - */ -export function addStringInput(div: HTMLElement, key: string, value: string, type: string, callback: CallableFunction, disabled?: boolean) { - var row = document.createElement("div"); - var dt = document.createElement("dt"); - var dd = document.createElement("dd"); - dt.innerText = key; - var input = document.createElement("input"); - input.value = value; - input.textContent = value; - input.type = type?? "text"; - input.disabled = disabled?? false; - input.onchange = () => callback(input.value); - dd.appendChild(input); - row.appendChild(dt); - row.appendChild(dd); - row.classList.add("input-row"); - div.appendChild(row); -} - -/** Add a dropdown (select) input - * - * @param div The HTMLElement that will contain the input - * @param key The key of the input, which will be used as label - * @param value The initial value of the input - * @param options The dropdown options - */ -export function addDropdownInput(div: HTMLElement, key: string, value: string, options: string[], callback: CallableFunction, disabled?: boolean) { - var row = document.createElement("div"); - var dt = document.createElement("dt"); - var dd = document.createElement("dd"); - dt.innerText = key; - var select = document.createElement("select"); - options.forEach((option: string) => { - var el = document.createElement("option"); - el.value = option; - el.innerText = option; - select.appendChild(el); - }); - select.value = value; - select.disabled = disabled?? false; - select.onchange = () => callback(select.value); - dd.appendChild(select); - row.appendChild(dt); - row.appendChild(dd); - row.classList.add("input-row"); - div.appendChild(row); -} - -/** Add a checkbox input in the form of String: [ value ] - * - * @param div The HTMLElement that will contain the input - * @param key The key of the input, which will be used as label - * @param value The initial value of the input - * @param callback Callback called when the user enters a new value - * @param disabled If true, the input will be disabled and read only - */ -export function addCheckboxInput(div: HTMLElement, key: string, value: boolean, callback: CallableFunction, disabled?: boolean) { - var row = document.createElement("div"); - var dt = document.createElement("dt"); - var dd = document.createElement("dd"); - dt.innerText = key; - var input = document.createElement("input"); - input.checked = value; - input.type = "checkbox"; - input.disabled = disabled?? false; - input.onchange = () => callback(input.checked); - dd.appendChild(input); - row.appendChild(dt); - row.appendChild(dd); - row.classList.add("input-row"); - div.appendChild(row); -} - -/** Create a loadout items editor. This editor allows to add or remove loadout items, as well as changing their name and quantity - * - * @param div The HTMLElement that will contain the editor - * @param loadout The loadout to edit - */ -export function addLoadoutItemsEditor(div: HTMLElement, loadout: LoadoutBlueprint) { - var itemsEl = document.createElement("div"); - itemsEl.classList.add("dm-scroll-container", "dm-items-container"); - - /* Create a row for each loadout item to allow and change the name and quantity of the item itself */ - loadout.items.sort((a: LoadoutItemBlueprint, b: LoadoutItemBlueprint) => a.name.localeCompare(b.name, undefined, {sensitivity: 'base'})); - loadout.items.forEach((item: LoadoutItemBlueprint, index: number) => { - var rowDiv = document.createElement("div"); - - var nameLabel = document.createElement("label"); - nameLabel.innerText = "Name" - rowDiv.appendChild(nameLabel); - - var nameInput = document.createElement("input"); - rowDiv.appendChild(nameInput); - nameInput.textContent = item.name; - nameInput.value = item.name - nameInput.onchange = () => { loadout.items[index].name = nameInput.value; } - - var quantityLabel = document.createElement("label"); - quantityLabel.innerText = "Quantity" - rowDiv.appendChild(quantityLabel); - - var quantityInput = document.createElement("input"); - rowDiv.appendChild(quantityInput); - quantityInput.textContent = String(item.quantity); - quantityInput.value = String(item.quantity); - quantityInput.type = "number"; - quantityInput.step = "1"; - quantityInput.onchange = () => { loadout.items[index].quantity = parseInt(quantityInput.value); } - - /* This button allows to remove the item */ - var button = document.createElement("button"); - button.innerText = "X"; - button.onclick = () => { - loadout.items.splice(index, 1); - div.dispatchEvent(new Event("refresh")); - } - rowDiv.appendChild(button); - - itemsEl.appendChild(rowDiv); - }) - div.appendChild(itemsEl); - - /* Button to add a new item to the loadout */ - var inputDiv = document.createElement("div"); - inputDiv.classList.add("dm-new-item-input"); - var button = document.createElement("button"); - button.innerText = "Add"; - inputDiv.appendChild(button); - div.appendChild(inputDiv); - - button.addEventListener("click", (ev: MouseEvent) => { - loadout?.items.push({ - name: "", - quantity: 1 - }) - div.dispatchEvent(new Event("refresh")); - }); -} - -/** Add a input and button to create a new element in a list. It uses a generic callback to actually add the element. - * - * @param div The HTMLElement that will contain the input and button - * @param callback Callback called when the user clicks on "Add" - */ -export function addNewElementInput(div: HTMLElement, callback: CallableFunction) { - var inputDiv = document.createElement("div"); - inputDiv.classList.add("dm-new-element-input"); - - var input = document.createElement("input"); - inputDiv.appendChild(input); - - var button = document.createElement("button"); - button.innerText = "Add"; - button.addEventListener("click", (ev: MouseEvent) => callback(ev, input)); - inputDiv.appendChild(button); - div.appendChild(inputDiv); -} - -/** Add a scrollable list of blueprints - * - * @param div The HTMLElement that will contain the list - * @param database The database that will be used to fill the list of blueprints - * @param filter A string filter that will be executed to filter the blueprints to add - * @param callback Callback called when the user clicks on one of the elements - */ -export function addBlueprintsScroll(div: HTMLElement, database: {blueprints: {[key: string]: UnitBlueprint}}, filter: string, callback: CallableFunction) { - var scrollDiv = document.createElement("div"); - scrollDiv.classList.add("dm-scroll-container"); - if (database !== null) { - var blueprints: {[key: string]: UnitBlueprint} = database.blueprints; - - for (let key of Object.keys(blueprints).sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'}))) { - var addKey = true; - if (filter !== "") { - try { - var blueprint = blueprints[key]; - addKey = eval(filter); - } catch { - console.error("An error has occurred evaluating the blueprint filter") - } - } - - if (addKey) { - var rowDiv = document.createElement("div"); - scrollDiv.appendChild(rowDiv); - - let text = document.createElement("div"); - text.innerHTML = `
${key}
${blueprints[key].label}
`; - text.onclick = () => { - callback(key); - const collection = document.getElementsByClassName("blueprint-selected"); - for (let i = 0; i < collection.length; i++) { - collection[i].classList.remove("blueprint-selected"); - } - text.classList.add("blueprint-selected"); - } - rowDiv.appendChild(text); - - let checkbox = document.createElement("input"); - checkbox.type = "checkbox"; - checkbox.checked = blueprints[key].enabled; - checkbox.onclick = () => { - console.log(checkbox.checked); - blueprints[key].enabled = checkbox.checked; - } - rowDiv.appendChild(checkbox); - - /* This button allows to remove an element from the list. It requires a refresh. */ - var button = document.createElement("button"); - button.innerText = "X"; - button.onclick = () => { - delete blueprints[key]; - div.dispatchEvent(new Event("refresh")); - } - rowDiv.appendChild(button); - } - } - } - div.appendChild(scrollDiv); -} - -/** Add a scrollable list of loadouts - * - * @param div The HTMLElement that will contain the list - * @param loadouts The loadouts that will be used to fill the list - * @param callback Callback called when the user clicks on one of the elements - */ -export function addLoadoutsScroll(div: HTMLElement, loadouts: LoadoutBlueprint[], callback: CallableFunction) { - var loadoutsEl = document.createElement("div"); - loadoutsEl.classList.add("dm-scroll-container", "dm-loadout-container") - - loadouts.sort((a: LoadoutBlueprint, b: LoadoutBlueprint) => a.name.localeCompare(b.name, undefined, {sensitivity: 'base'})); - loadouts.forEach((loadout: LoadoutBlueprint, index: number) => { - var rowDiv = document.createElement("div"); - loadoutsEl.appendChild(rowDiv); - - var text = document.createElement("label"); - text.textContent = loadout.name; - text.onclick = () => { callback(loadout) }; - rowDiv.appendChild(text); - - /* The "Empty loadout" can not be removed */ - if (loadout.name !== "Empty loadout") { - let checkbox = document.createElement("input"); - checkbox.type = "checkbox"; - checkbox.checked = loadout.enabled; - checkbox.onclick = () => { - console.log(checkbox.checked); - loadout.enabled = checkbox.checked; - } - rowDiv.appendChild(checkbox); - - /* This button allows to remove an element from the list. It requires a refresh. */ - var button = document.createElement("button"); - button.innerText = "X"; - button.onclick = () => { - loadouts.splice(index, 1); - div.dispatchEvent(new Event("refresh")); - } - rowDiv.appendChild(button); - } - }); - - div.appendChild(loadoutsEl); -} - -/** Converts an array of string into a single string like [val1, val2, val3] - * - * @param array The input array of strings - * @returns The string - */ -export function arrayToString(array: string[]) { - return "[" + array.join( ", " ) + "]"; -} - -/** Converts an a single string like [val1, val2, val3] into an array - * - * @param input The input string - * @returns The array - */ -export function stringToArray(input: string) { - return input.match( /(\w)+/g ) ?? []; -} \ No newline at end of file diff --git a/frontend/website/plugins/databasemanager/style.css b/frontend/website/plugins/databasemanager/style.css deleted file mode 100644 index f8c80213..00000000 --- a/frontend/website/plugins/databasemanager/style.css +++ /dev/null @@ -1,295 +0,0 @@ -#database-manager-panel { - flex-direction: column; - display: flex; - width: 80%; - height: 80%; - padding: 10px; - border-radius: 5px; - background-color: var(--background-steel) !important; - z-index: 9999999; -} - -@media (min-width: 1200px) { - .dm-container { - flex-direction: row; - } -} - -@media (max-width: 1200px) { - .dm-container { - flex-direction: column; - overflow-y: auto; - } -} - -#database-manager-panel * { - font-size: 13; - font-family: 'Open Sans', sans-serif !important; - user-select: none; -} - -#database-manager-panel>div:first-child { - display: flex; - align-items: center; -} - -#database-manager-panel>div:last-child { - display: flex; - column-gap: 5px; - align-items: center; - justify-content: end; - justify-items: end; - margin-top: 5px; -} - -#database-manager-panel>div:last-child>button { - border: 1px solid white; -} - -.dm-container { - background-color: var(--background-grey); - border: 2px solid #777777; - position: relative; - display: flex; - width: 100%; - padding: 5px; - height: calc(100% - 64px - 5px); - border-radius: 0px 5px 5px 5px; -} - -.dm-content-container { - position: relative; - margin: 10px; - display: flex; - flex-direction: column; - row-gap: 5px; - max-height: 100%; - padding: 10px; -} - -@media (min-width: 1200px) { - .dm-content-container { - height: calc(100% - 20px); - } - - .dm-content-container:nth-of-type(1) { - width: 400px; - } - - .dm-content-container:nth-of-type(2) { - width: 500px; - } - - .dm-content-container:nth-of-type(3) { - flex: 1; - } -} - -@media (max-width: 1200px) { - .dm-content-container { - width: calc(100% - 20px); - } - - .dm-content-container:nth-of-type(1) { - height: 30%; - } - - .dm-content-container:nth-of-type(2) { - height: 50%; - } - - .dm-content-container:nth-of-type(3) { - flex: 1; - } -} - -.dm-content-container>label { - font-size: 18px !important; - font-weight: bold; -} - -.dm-scroll-container { - display: flex; - flex-direction: column; - overflow-y: scroll; - max-height: 100%; - color: black; - font-weight: bold; -} - -#database-manager-panel input { - font-weight: bold; -} - -.dm-scroll-container>div:nth-child(even) { - background-color: gainsboro; -} - -.dm-scroll-container>div:nth-child(odd) { - background-color: white; -} - -.dm-scroll-container>div *:nth-child(1) { - height: 100%; - width: calc(100% - 25px); - padding: 2px; - word-wrap: break-word; -} - -.dm-scroll-container>div *:nth-child(1):hover { - background-color: var(--accent-dark-blue); - color: white; - cursor: pointer; -} - -.blueprint-selected { - background-color: var(--accent-light-blue) !important; - color: white; -} - -.dm-scroll-container>div { - display: flex; - align-items: center; - justify-content: space-between; -} - -.dm-scroll-container>div>div { - display: flex; - align-items: center; - justify-content: space-between; -} - -.dm-scroll-container>div>button { - height: 20px; - width: 20px; - padding: 0px; -} - -.dm-scroll-container>div>div>div:nth-child(1) { - width: fit-content; -} - -.dm-scroll-container>div>div>div:nth-child(2) { - overflow: hidden; - text-wrap: nowrap; - text-overflow: ellipsis; - font-weight: normal; -} - -.input-row { - width: 100%; - display: flex; - flex-direction: row; -} - -@media (max-width: 1200px) { - .dm-content-container label { - width: 100%; - } - .input-row { - width: 50%; - } -} - -.input-row>dt { - width: 250px; -} - -.input-row>dd { - width: 100%; - text-align: right; -} - -.input-row>dd>* { - width: 100%; - font-weight: bold; -} - -.input-row>dd>*[type="checkbox"] { - width: 20px; - font-weight: bold; -} - -.dm-loadout-container { - max-height: 100%; - max-width: 500px; - width: 100%; -} - -.dm-items-container { - max-height: 100%; - height: fit-content; -} - -.dm-items-container>div { - display: flex; - align-items: center; - column-gap: 2px; -} - -.dm-items-container>div>label { - width: 80px !important; -} - -.dm-items-container div>input:nth-of-type(1) { - flex: 1; - font-weight: bold; -} - -.dm-items-container div>input:nth-of-type(2) { - width: 40px; - font-weight: bold; -} - -.dm-new-element-input { - display: flex; - flex-direction: row; - column-gap: 2px; - width: 100%; - align-items: center; -} - -.dm-new-element-input>input { - width: 100%; -} - -.dm-new-element-input>button { - width: 60px; -} - -.dm-new-item-input { - display: flex; - justify-content: end; -} - -.dm-new-item-input>button { - width: 60px; -} - -.tab-button { - transform: translateY(+3px); - background-color: var(--background-steel); - border-radius: 0; - border-bottom: 2px solid transparent !important; - border-top: 2px solid #777777 !important; - border-left: 2px solid #777777 !important; - border-right: 0px solid #777777 !important; -} - -.tab-button.selected { - background-color: var(--background-grey); - z-index: 10; -} - -.tab-button:first-of-type { - border-top-left-radius: 5px; -} - -.tab-button:last-of-type { - border-top-right-radius: 5px; - border-right: 2px solid #777777 !important; -} - -#database-manager-panel button :not(.dm-scroll-container>div) { - border: 1px solid white; -} \ No newline at end of file diff --git a/frontend/website/plugins/databasemanager/tsconfig.json b/frontend/website/plugins/databasemanager/tsconfig.json deleted file mode 100644 index 5fbef0a9..00000000 --- a/frontend/website/plugins/databasemanager/tsconfig.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - "rootDirs": ["./src"], /* Specify the root folder within your source files. */ - // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - "typeRoots": [ - "./node_modules/@types", - "../../@types" - ], /* Specify multiple folders that act like './node_modules/@types'. */ - "types": [ - "olympus" - ], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - }, - "include": [ - "src/*.ts" - ] -} \ No newline at end of file diff --git a/frontend/website/sample.png b/frontend/website/sample.png deleted file mode 100644 index 4534d728..00000000 Binary files a/frontend/website/sample.png and /dev/null differ diff --git a/frontend/website/scripts/build-debug.bat b/frontend/website/scripts/build-debug.bat deleted file mode 100644 index 8d5328b7..00000000 --- a/frontend/website/scripts/build-debug.bat +++ /dev/null @@ -1 +0,0 @@ -call browserify --debug .\src\index.ts -o ..\server\public\javascripts\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] diff --git a/frontend/website/scripts/build-release.bat b/frontend/website/scripts/build-release.bat deleted file mode 100644 index dcd7830d..00000000 --- a/frontend/website/scripts/build-release.bat +++ /dev/null @@ -1 +0,0 @@ -call browserify .\src\index.ts -o ..\..\build\frontend\public\javascripts\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] diff --git a/frontend/website/scripts/emit-declarations.bat b/frontend/website/scripts/emit-declarations.bat deleted file mode 100644 index a324c677..00000000 --- a/frontend/website/scripts/emit-declarations.bat +++ /dev/null @@ -1 +0,0 @@ -tsc --project tsconfig.json --declaration --emitDeclarationOnly --outfile .\@types\olympus\index.d.ts \ No newline at end of file diff --git a/frontend/website/scripts/watch.bat b/frontend/website/scripts/watch.bat deleted file mode 100644 index d62a3599..00000000 --- a/frontend/website/scripts/watch.bat +++ /dev/null @@ -1 +0,0 @@ -watchify .\src\index.ts --debug -o ..\server\public\javascripts\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] -v \ No newline at end of file diff --git a/frontend/website/src/constants/constants.ts b/frontend/website/src/constants/constants.ts deleted file mode 100644 index 7d411761..00000000 --- a/frontend/website/src/constants/constants.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { LatLng, LatLngBounds } from "leaflet"; -import { MapMarkerVisibilityControl } from "../map/map"; - -export const UNITS_URI = "units"; -export const WEAPONS_URI = "weapons"; -export const LOGS_URI = "logs"; -export const AIRBASES_URI = "airbases"; -export const BULLSEYE_URI = "bullseyes"; -export const MISSION_URI = "mission"; -export const COMMANDS_URI = "commands"; - -export const NONE = "None"; -export const GAME_MASTER = "Game master"; -export const BLUE_COMMANDER = "Blue commander"; -export const RED_COMMANDER = "Red commander"; - -export const VISUAL = 1; -export const OPTIC = 2; -export const RADAR = 4; -export const IRST = 8; -export const RWR = 16; -export const DLINK = 32; - -export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area", "simulate-fire-fight", "scenic-aaa", "miss-on-purpose", "land-at-point"]; -export const ROEs: string[] = ["free", "designated", "", "return", "hold"]; -export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"]; -export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"]; - -export const ERAS = [{ - "name": "Early Cold War", - "chronologicalOrder": 2 -}, { - "name": "Late Cold War", - "chronologicalOrder": 4 -}, { - "name": "Mid Cold War", - "chronologicalOrder": 3 -}, { - "name": "Modern", - "chronologicalOrder": 5 -}, { - "name": "WW2", - "chronologicalOrder": 1 -}]; - -export const ROEDescriptions: string[] = [ - "Free (Attack anyone)", - "Designated (Attack the designated target only) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.", - "", - "Return (Only fire if fired upon) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.", - "Hold (Never fire)" -]; - -export const reactionsToThreatDescriptions: string[] = [ - "None (No reaction)", - "Manoeuvre (no countermeasures)", - "Passive (Countermeasures only, no manoeuvre)", - "Evade (Countermeasures and manoeuvers)" -]; - -export const emissionsCountermeasuresDescriptions: string[] = [ - "Silent (Radar OFF, no ECM)", - "Attack (Radar only for targeting, ECM only if locked)", - "Defend (Radar for searching, ECM if locked)", - "Always on (Radar and ECM always on)" -]; - -export const shotsScatterDescriptions: string[] = [ - "When performing scenic shooting tasks like simulated firefights, will shoot with a large scatter", - "When performing scenic shooting tasks like simulated firefights, will shoot with a medium scatter", - "When performing scenic shooting tasks like simulated firefights, will shoot with a small scatter (Radar guided units will track shots when the enemy unit is close)" -]; - -export const shotsIntensityDescriptions: string[] = [ - "When performing scenic shooting tasks like simulated firefights, will shoot with a low rate of fire", - "When performing scenic shooting tasks like simulated firefights, will shoot with a medium rate of fire", - "When performing scenic shooting tasks like simulated firefights, will shoot with a high rate of fire" -]; - -export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 }; -export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 }; -export const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 }; -export const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helicopter: 0 }; -export const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 }; -export const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 }; - -export const minimapBoundaries = { - "Nevada": [ // NTTR - new LatLng(39.7982463, -119.985425), - new LatLng(34.4037128, -119.7806729), - new LatLng(34.3483316, -112.4529351), - new LatLng(39.7372411, -112.1130805), - new LatLng(39.7982463, -119.985425) - ], - "Syria": [ // Syria - new LatLng(37.3630556, 29.2686111), - new LatLng(31.8472222, 29.8975), - new LatLng(32.1358333, 42.1502778), - new LatLng(37.7177778, 42.3716667), - new LatLng(37.3630556, 29.2686111) - ], - "Caucasus": [ // Caucasus - new LatLng(39.6170191, 27.634935), - new LatLng(38.8735863, 47.1423108), - new LatLng(47.3907982, 49.3101946), - new LatLng(48.3955879, 26.7753625), - new LatLng(39.6170191, 27.634935) - ], - "PersianGulf": [ // Persian Gulf - new LatLng(32.9355285, 46.5623682), - new LatLng(21.729393, 47.572675), - new LatLng(21.8501348, 63.9734737), - new LatLng(33.131584, 64.7313594), - new LatLng(32.9355285, 46.5623682) - ], - "MarianaIslands": [ // Marianas - new LatLng(22.09, 135.0572222), - new LatLng(10.5777778, 135.7477778), - new LatLng(10.7725, 149.3918333), - new LatLng(22.5127778, 149.5427778), - new LatLng(22.09, 135.0572222) - ], - "Falklands": [ // South Atlantic - new LatLng(-49.097217, -79.418267), - new LatLng(-56.874517, -79.418267), - new LatLng(-56.874517, -43.316433), - new LatLng(-49.097217, -43.316433), - new LatLng(-49.097217, -79.418267) - ], - "Normandy": [ // Normandy - new LatLng(50.44, -3.29), - new LatLng(48.12, -3.29), - new LatLng(48.12, 3.70), - new LatLng(50.44, 3.70), - new LatLng(50.44, -3.29) - ], - "SinaiMap": [ // Sinai - new LatLng(34.312222, 28.523333), - new LatLng(25.946944, 28.523333), - new LatLng(25.946944, 36.897778), - new LatLng(34.312222, 36.897778), - new LatLng(34.312222, 28.523333) - ], - "Kola": [ // Kola - new LatLng(72.055300, 4.0140000), - new LatLng(64.421145, 10.353076), - new LatLng(63.570300, 39.364000), - new LatLng(71.48000, 48.091100), - new LatLng(72.055300, 4.01400003) - ], - "Afghanistan": [ // Afghanistan - new LatLng(39.27472222, 59.81138889), - new LatLng(29.41166667, 60.04305556), - new LatLng(28.97722222, 73.87666667), - new LatLng(38.40055556, 75.07638889), - new LatLng(39.27472222, 59.81138889) - ] -}; - -export const mapBounds = { - "Syria": { bounds: new LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]), zoom: 5 }, - "MarianaIslands": { bounds: new LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]), zoom: 5 }, - "Nevada": { bounds: new LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805]), zoom: 5 }, - "PersianGulf": { bounds: new LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]), zoom: 4 }, - "Caucasus": { bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]), zoom: 4 }, - "Falklands": { bounds: new LatLngBounds([-49.097217, -79.418267], [-56.874517, -43.316433]), zoom: 3 }, - "Normandy": { bounds: new LatLngBounds([50.44, -3.29], [48.12, 3.70]), zoom: 5 }, - "SinaiMap": { bounds: new LatLngBounds([34.312222, 28.523333], [25.946944, 36.897778]), zoom: 4 }, - "Kola": { bounds: new LatLngBounds([61.59999, 4.29982], [75.05179, 44.29982]), zoom: 3}, - "Afghanistan": { bounds: new LatLngBounds([29.41166667, 60.04305556], [38.40055556, 75.07638889]), zoom: 3} -} - -export const defaultMapMirrors = {} - -export const defaultMapLayers = {} - -/* Map constants */ -export const IDLE = "Idle"; -export const MOVE_UNIT = "Move unit"; -export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area"; -export const visibilityControls: string[] = ["human", "dcs", "aircraft", "helicopter", "groundunit-sam", "groundunit", "navyunit", "airbase"]; -export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["helicopter"], ["groundunit-sam"], ["groundunit"], ["navyunit"], ["airbase"]]; -export const visibilityControlsTooltips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle helicopter visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; - -export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{ - "name": "Human", - "image": "visibility/human.svg", - "toggles": ["human"], - "tooltip": "Toggle human players' visibility" -}, { - "image": "visibility/olympus.svg", - "isProtected": false, - "name": "Olympus", - "protectable": false, - "toggles": ["olympus"], - "tooltip": "Toggle Olympus-controlled units' visibility" -}, { - "image": "visibility/dcs.svg", - "isProtected": true, - "name": "DCS", - "protectable": true, - "toggles": ["dcs"], - "tooltip": "Toggle DCS-controlled units' visibility" -}, { - "category": "Aircraft", - "image": "visibility/aircraft.svg", - "name": "Aircraft", - "toggles": ["aircraft"], - "tooltip": "Toggle aircraft's visibility" -}, { - "category": "Helicopter", - "image": "visibility/helicopter.svg", - "name": "Helicopter", - "toggles": ["helicopter"], - "tooltip": "Toggle helicopters' visibility" -}, { - "category": "AirDefence", - "image": "visibility/groundunit-sam.svg", - "name": "Air defence", - "toggles": ["groundunit-sam"], - "tooltip": "Toggle air defence units' visibility" -}, { - "image": "visibility/groundunit.svg", - "name": "Ground units", - "toggles": ["groundunit"], - "tooltip": "Toggle ground units' visibility" -}, { - "category": "GroundUnit", - "image": "visibility/navyunit.svg", - "name": "Naval", - "toggles": ["navyunit"], - "tooltip": "Toggle naval units' visibility" -}, { - "image": "visibility/airbase.svg", - "name": "Airbase", - "toggles": ["airbase"], - "tooltip": "Toggle airbase' visibility" -}]; - -export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"]; -export const IADSDensities: { [key: string]: number } = { "AAA": 0.8, "SAM Site": 0.1, "Radar (EWR)": 0.05 }; -export const GROUND_UNIT_AIR_DEFENCE_REGEX: RegExp = /(\b(AAA|SAM|MANPADS?|[mM]anpads?)|[sS]tinger\b)/; -export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out"; -export const SHOW_UNIT_LABELS = "Show unit labels (L)"; -export const SHOW_UNITS_ENGAGEMENT_RINGS = "Show units threat range rings (Q)"; -export const HIDE_UNITS_SHORT_RANGE_RINGS = "Hide short range units threat range rings (R)"; -export const SHOW_UNITS_ACQUISITION_RINGS = "Show units detection range rings (E)"; -export const FILL_SELECTED_RING = "Fill the threat range rings of selected units (F)"; -export const SHOW_UNIT_CONTACTS = "Show selected units contact lines"; -export const SHOW_UNIT_PATHS = "Show selected unit paths"; -export const SHOW_UNIT_TARGETS = "Show selected unit targets"; -export const DCS_LINK_PORT = "DCS Camera link port"; -export const DCS_LINK_RATIO = "DCS Camera zoom"; - -export enum DataIndexes { - startOfData = 0, - category, - alive, - human, - controlled, - coalition, - country, - name, - unitName, - groupName, - state, - task, - hasTask, - position, - speed, - horizontalVelocity, - verticalVelocity, - heading, - track, - isActiveTanker, - isActiveAWACS, - onOff, - followRoads, - fuel, - desiredSpeed, - desiredSpeedType, - desiredAltitude, - desiredAltitudeType, - leaderID, - formationOffset, - targetID, - targetPosition, - ROE, - reactionToThreat, - emissionsCountermeasures, - TACAN, - radio, - generalSettings, - ammo, - contacts, - activePath, - isLeader, - operateAs, - shotsScatter, - shotsIntensity, - health, - endOfData = 255 -}; - -export const MGRS_PRECISION_10KM = 2; -export const MGRS_PRECISION_1KM = 3; -export const MGRS_PRECISION_100M = 4; -export const MGRS_PRECISION_10M = 5; -export const MGRS_PRECISION_1M = 6; - -export const DELETE_CYCLE_TIME = 0.05; -export const DELETE_SLOW_THRESHOLD = 50; - -export const GROUPING_ZOOM_TRANSITION = 13; - -export const MAX_SHOTS_SCATTER = 3; -export const MAX_SHOTS_INTENSITY = 3; -export const SHOTS_SCATTER_DEGREES = 10; \ No newline at end of file diff --git a/frontend/website/src/constants/constants.ts.bak b/frontend/website/src/constants/constants.ts.bak deleted file mode 100644 index d845cbe9..00000000 --- a/frontend/website/src/constants/constants.ts.bak +++ /dev/null @@ -1,310 +0,0 @@ -import { LatLng, LatLngBounds } from "leaflet"; -import { MapMarkerVisibilityControl } from "../map/map"; - -export const UNITS_URI = "units"; -export const WEAPONS_URI = "weapons"; -export const LOGS_URI = "logs"; -export const AIRBASES_URI = "airbases"; -export const BULLSEYE_URI = "bullseyes"; -export const MISSION_URI = "mission"; -export const COMMANDS_URI = "commands"; - -export const NONE = "None"; -export const GAME_MASTER = "Game master"; -export const BLUE_COMMANDER = "Blue commander"; -export const RED_COMMANDER = "Red commander"; - -export const VISUAL = 1; -export const OPTIC = 2; -export const RADAR = 4; -export const IRST = 8; -export const RWR = 16; -export const DLINK = 32; - -export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area", "simulate-fire-fight", "scenic-aaa", "miss-on-purpose", "land-at-point"]; -export const ROEs: string[] = ["free", "designated", "", "return", "hold"]; -export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"]; -export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"]; - -export const ERAS = [{ - "name": "Early Cold War", - "chronologicalOrder": 2 -}, { - "name": "Late Cold War", - "chronologicalOrder": 4 -}, { - "name": "Mid Cold War", - "chronologicalOrder": 3 -}, { - "name": "Modern", - "chronologicalOrder": 5 -}, { - "name": "WW2", - "chronologicalOrder": 1 -}]; - -export const ROEDescriptions: string[] = [ - "Free (Attack anyone)", - "Designated (Attack the designated target only) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.", - "", - "Return (Only fire if fired upon) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.", - "Hold (Never fire)" -]; - -export const reactionsToThreatDescriptions: string[] = [ - "None (No reaction)", - "Manoeuvre (no countermeasures)", - "Passive (Countermeasures only, no manoeuvre)", - "Evade (Countermeasures and manoeuvers)" -]; - -export const emissionsCountermeasuresDescriptions: string[] = [ - "Silent (Radar OFF, no ECM)", - "Attack (Radar only for targeting, ECM only if locked)", - "Defend (Radar for searching, ECM if locked)", - "Always on (Radar and ECM always on)" -]; - -export const shotsScatterDescriptions: string[] = [ - "When performing scenic shooting tasks like simulated firefights, will shoot with a large scatter", - "When performing scenic shooting tasks like simulated firefights, will shoot with a medium scatter", - "When performing scenic shooting tasks like simulated firefights, will shoot with a small scatter (Radar guided units will track shots when the enemy unit is close)" -]; - -export const shotsIntensityDescriptions: string[] = [ - "When performing scenic shooting tasks like simulated firefights, will shoot with a low rate of fire", - "When performing scenic shooting tasks like simulated firefights, will shoot with a medium rate of fire", - "When performing scenic shooting tasks like simulated firefights, will shoot with a high rate of fire" -]; - -export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 }; -export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 }; -export const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 }; -export const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helicopter: 0 }; -export const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 }; -export const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 }; - -export const minimapBoundaries = { - "Nevada": [ // NTTR - new LatLng(39.7982463, -119.985425), - new LatLng(34.4037128, -119.7806729), - new LatLng(34.3483316, -112.4529351), - new LatLng(39.7372411, -112.1130805), - new LatLng(39.7982463, -119.985425) - ], - "Syria": [ // Syria - new LatLng(37.3630556, 29.2686111), - new LatLng(31.8472222, 29.8975), - new LatLng(32.1358333, 42.1502778), - new LatLng(37.7177778, 42.3716667), - new LatLng(37.3630556, 29.2686111) - ], - "Caucasus": [ // Caucasus - new LatLng(39.6170191, 27.634935), - new LatLng(38.8735863, 47.1423108), - new LatLng(47.3907982, 49.3101946), - new LatLng(48.3955879, 26.7753625), - new LatLng(39.6170191, 27.634935) - ], - "PersianGulf": [ // Persian Gulf - new LatLng(32.9355285, 46.5623682), - new LatLng(21.729393, 47.572675), - new LatLng(21.8501348, 63.9734737), - new LatLng(33.131584, 64.7313594), - new LatLng(32.9355285, 46.5623682) - ], - "MarianaIslands": [ // Marianas - new LatLng(22.09, 135.0572222), - new LatLng(10.5777778, 135.7477778), - new LatLng(10.7725, 149.3918333), - new LatLng(22.5127778, 149.5427778), - new LatLng(22.09, 135.0572222) - ], - "Falklands": [ // South Atlantic - new LatLng(-49.097217, -79.418267), - new LatLng(-56.874517, -79.418267), - new LatLng(-56.874517, -43.316433), - new LatLng(-49.097217, -43.316433), - new LatLng(-49.097217, -79.418267) - ], - "Normandy": [ // Normandy - new LatLng(50.44, -3.29), - new LatLng(48.12, -3.29), - new LatLng(48.12, 3.70), - new LatLng(50.44, 3.70), - new LatLng(50.44, -3.29) - ], - "SinaiMap": [ // Sinai - new LatLng(34.312222, 28.523333), - new LatLng(25.946944, 28.523333), - new LatLng(25.946944, 36.897778), - new LatLng(34.312222, 36.897778), - new LatLng(34.312222, 28.523333) - ], - "Kola": [ // Kola - new LatLng(72.055300, 4.0140000), - new LatLng(64.421145, 10.353076), - new LatLng(63.570300, 39.364000), - new LatLng(71.48000, 48.091100), - new LatLng(72.055300, 4.01400003) - ] -}; - -export const mapBounds = { - "Syria": { bounds: new LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]), zoom: 5 }, - "MarianaIslands": { bounds: new LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]), zoom: 5 }, - "Nevada": { bounds: new LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805]), zoom: 5 }, - "PersianGulf": { bounds: new LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]), zoom: 4 }, - "Caucasus": { bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]), zoom: 4 }, - "Falklands": { bounds: new LatLngBounds([-49.097217, -79.418267], [-56.874517, -43.316433]), zoom: 3 }, - "Normandy": { bounds: new LatLngBounds([50.44, -3.29], [48.12, 3.70]), zoom: 5 }, - "SinaiMap": { bounds: new LatLngBounds([34.312222, 28.523333], [25.946944, 36.897778]), zoom: 4 }, - "Kola": { bounds: new LatLngBounds([61.59999, 4.29982], [75.05179, 44.29982]), zoom: 3} -} - -export const defaultMapMirrors = {} - -export const defaultMapLayers = {} - -/* Map constants */ -export const IDLE = "Idle"; -export const MOVE_UNIT = "Move unit"; -export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area"; -export const visibilityControls: string[] = ["human", "dcs", "aircraft", "helicopter", "groundunit-sam", "groundunit", "navyunit", "airbase"]; -export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["helicopter"], ["groundunit-sam"], ["groundunit"], ["navyunit"], ["airbase"]]; -export const visibilityControlsTooltips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle helicopter visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; - -export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{ - "name": "Human", - "image": "visibility/human.svg", - "toggles": ["human"], - "tooltip": "Toggle human players' visibility" -}, { - "image": "visibility/olympus.svg", - "isProtected": false, - "name": "Olympus", - "protectable": false, - "toggles": ["olympus"], - "tooltip": "Toggle Olympus-controlled units' visibility" -}, { - "image": "visibility/dcs.svg", - "isProtected": true, - "name": "DCS", - "protectable": true, - "toggles": ["dcs"], - "tooltip": "Toggle DCS-controlled units' visibility" -}, { - "category": "Aircraft", - "image": "visibility/aircraft.svg", - "name": "Aircraft", - "toggles": ["aircraft"], - "tooltip": "Toggle aircraft's visibility" -}, { - "category": "Helicopter", - "image": "visibility/helicopter.svg", - "name": "Helicopter", - "toggles": ["helicopter"], - "tooltip": "Toggle helicopters' visibility" -}, { - "category": "AirDefence", - "image": "visibility/groundunit-sam.svg", - "name": "Air defence", - "toggles": ["groundunit-sam"], - "tooltip": "Toggle air defence units' visibility" -}, { - "image": "visibility/groundunit.svg", - "name": "Ground units", - "toggles": ["groundunit"], - "tooltip": "Toggle ground units' visibility" -}, { - "category": "GroundUnit", - "image": "visibility/navyunit.svg", - "name": "Naval", - "toggles": ["navyunit"], - "tooltip": "Toggle naval units' visibility" -}, { - "image": "visibility/airbase.svg", - "name": "Airbase", - "toggles": ["airbase"], - "tooltip": "Toggle airbase' visibility" -}]; - -export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"]; -export const IADSDensities: { [key: string]: number } = { "AAA": 0.8, "SAM Site": 0.1, "Radar (EWR)": 0.05 }; -export const GROUND_UNIT_AIR_DEFENCE_REGEX: RegExp = /(\b(AAA|SAM|MANPADS?|[mM]anpads?)|[sS]tinger\b)/; -export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out"; -export const SHOW_UNIT_LABELS = "Show unit labels (L)"; -export const SHOW_UNITS_ENGAGEMENT_RINGS = "Show units threat range rings (Q)"; -export const HIDE_UNITS_SHORT_RANGE_RINGS = "Hide short range units threat range rings (R)"; -export const SHOW_UNITS_ACQUISITION_RINGS = "Show units detection range rings (E)"; -export const FILL_SELECTED_RING = "Fill the threat range rings of selected units (F)"; -export const SHOW_UNIT_CONTACTS = "Show selected units contact lines"; -export const SHOW_UNIT_PATHS = "Show selected unit paths"; -export const SHOW_UNIT_TARGETS = "Show selected unit targets"; -export const DCS_LINK_PORT = "DCS Camera link port"; -export const DCS_LINK_RATIO = "DCS Camera zoom"; - -export enum DataIndexes { - startOfData = 0, - category, - alive, - human, - controlled, - coalition, - country, - name, - unitName, - groupName, - state, - task, - hasTask, - position, - speed, - horizontalVelocity, - verticalVelocity, - heading, - track, - isActiveTanker, - isActiveAWACS, - onOff, - followRoads, - fuel, - desiredSpeed, - desiredSpeedType, - desiredAltitude, - desiredAltitudeType, - leaderID, - formationOffset, - targetID, - targetPosition, - ROE, - reactionToThreat, - emissionsCountermeasures, - TACAN, - radio, - generalSettings, - ammo, - contacts, - activePath, - isLeader, - operateAs, - shotsScatter, - shotsIntensity, - health, - endOfData = 255 -}; - -export const MGRS_PRECISION_10KM = 2; -export const MGRS_PRECISION_1KM = 3; -export const MGRS_PRECISION_100M = 4; -export const MGRS_PRECISION_10M = 5; -export const MGRS_PRECISION_1M = 6; - -export const DELETE_CYCLE_TIME = 0.05; -export const DELETE_SLOW_THRESHOLD = 50; - -export const GROUPING_ZOOM_TRANSITION = 13; - -export const MAX_SHOTS_SCATTER = 3; -export const MAX_SHOTS_INTENSITY = 3; -export const SHOTS_SCATTER_DEGREES = 10; \ No newline at end of file diff --git a/frontend/website/src/context/context.ts b/frontend/website/src/context/context.ts deleted file mode 100644 index a75cbb8e..00000000 --- a/frontend/website/src/context/context.ts +++ /dev/null @@ -1,44 +0,0 @@ -export interface ContextInterface { - allowUnitCopying?: boolean; - allowUnitPasting?: boolean; - useSpawnMenu?: boolean; - useUnitControlPanel?: boolean; - useUnitInfoPanel?: boolean; -} - -export class Context { - #allowUnitCopying: boolean; - #allowUnitPasting: boolean; - #useSpawnMenu: boolean; - #useUnitControlPanel: boolean; - #useUnitInfoPanel: boolean; - - constructor(config: ContextInterface) { - this.#allowUnitCopying = (config.allowUnitCopying !== false); - this.#allowUnitPasting = (config.allowUnitPasting !== false); - this.#useSpawnMenu = (config.useSpawnMenu !== false); - this.#useUnitControlPanel = (config.useUnitControlPanel !== false); - this.#useUnitInfoPanel = (config.useUnitInfoPanel !== false); - } - - getAllowUnitCopying() { - return this.#allowUnitCopying; - } - - getAllowUnitPasting() { - return this.#allowUnitPasting; - } - - getUseSpawnMenu() { - return this.#useSpawnMenu; - } - - getUseUnitControlPanel() { - return this.#useUnitControlPanel; - } - - getUseUnitInfoPanel() { - return this.#useUnitInfoPanel; - } - -} \ No newline at end of file diff --git a/frontend/website/src/context/contextmanager.ts b/frontend/website/src/context/contextmanager.ts deleted file mode 100644 index 55b701c6..00000000 --- a/frontend/website/src/context/contextmanager.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Manager } from "../other/manager"; -import { Context, ContextInterface } from "./context"; - -export class ContextManager extends Manager { - #currentContext!: string; - - constructor() { - super(); - } - - add(name: string, contextConfig: ContextInterface) { - super.add(name, new Context(contextConfig)); - - if (Object.values(this.getAll()).length === 1) { - this.#currentContext = name; - } - - return this; - } - - currentContextIs(contextName: string) { - return contextName === this.#currentContext; - } - - getCurrentContext() { - const contexts = this.getAll(); - - return (contexts.hasOwnProperty(this.#currentContext)) ? contexts[this.#currentContext] : false; - } - - setContext(contextName: string) { - if (!this.get(contextName)) { - console.error(`setContext(): context name "${contextName}" does not exist.`); - return false; - } - this.#currentContext = contextName; - - console.log(`Setting context to "${this.#currentContext}".`); - } -} \ No newline at end of file diff --git a/frontend/website/src/context/contextmenumanager.ts b/frontend/website/src/context/contextmenumanager.ts deleted file mode 100644 index 296fc7ec..00000000 --- a/frontend/website/src/context/contextmenumanager.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ContextMenu } from "../contextmenus/contextmenu"; -import { Manager } from "../other/manager"; - -export class ContextMenuManager extends Manager { - - constructor() { - super(); - } - - add( name:string, contextMenu:ContextMenu ) { - super.add( name, contextMenu ); - return this; - } - -} \ No newline at end of file diff --git a/frontend/website/src/contextmenus/airbasecontextmenu.ts b/frontend/website/src/contextmenus/airbasecontextmenu.ts deleted file mode 100644 index 85738197..00000000 --- a/frontend/website/src/contextmenus/airbasecontextmenu.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { getApp } from ".."; -import { GAME_MASTER } from "../constants/constants"; -import { AirbaseChartRunwayData, AirbaseChartRunwayHeadingData } from "../interfaces"; -import { Airbase } from "../mission/airbase"; -import { dataPointMap } from "../other/utils"; -import { Unit } from "../unit/unit"; -import { ContextMenu } from "./contextmenu"; - -/** This context menu is shown to the user when the airbase marker is right clicked on the map. - * It allows the user to inspect information about the airbase, as well as allowing to spawn units from the airbase itself and land units on it. */ -export class AirbaseContextMenu extends ContextMenu { - #airbase: Airbase | null = null; - - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID); - - document.addEventListener("contextMenuSpawnAirbase", (e: any) => { - this.#showSpawnMenu(); - }) - - document.addEventListener("contextMenuLandAirbase", (e: any) => { - if (this.#airbase) - getApp().getUnitsManager().landAt(this.#airbase.getLatLng()); - this.hide(); - }) - } - - /** Sets the airbase for which data will be shown in the context menu - * - * @param airbase The airbase for which data will be shown in the context menu. Note: the airbase must be present in the public/databases/airbases/.json database. - */ - setAirbase(airbase: Airbase) { - this.#airbase = airbase; - - this.#setName(this.#airbase.getName()); - this.#setProperties(this.#airbase.getProperties()); - this.#setParkings(this.#airbase.getParkings()); - this.#setCoalition(this.#airbase.getCoalition()); - this.#showLandButton(getApp().getUnitsManager().getSelectedUnitsCategories().length == 1 && ["Aircraft", "Helicopter"].includes(getApp().getUnitsManager().getSelectedUnitsCategories()[0]) && (getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral")) - this.#showSpawnButton(getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getApp().getMissionManager().getCommandedCoalition()); - this.#setAirbaseData(); - - this.clip(); - } - - /** - * - * @param airbaseName The name of the airbase - */ - #setName(airbaseName: string) { - var nameDiv = this.getContainer()?.querySelector("#airbase-name"); - if (nameDiv != null) - nameDiv.innerText = airbaseName; - } - - /** - * - * @param airbaseProperties The properties of the airbase - */ - #setProperties(airbaseProperties: string[]) { - this.getContainer()?.querySelector("#airbase-properties")?.replaceChildren(...airbaseProperties.map((property: string) => { - var div = document.createElement("div"); - div.innerText = property; - return div; - }),); - } - - /** - * - * @param airbaseParkings List of available parkings at the airbase - */ - #setParkings(airbaseParkings: string[]) { - this.getContainer()?.querySelector("#airbase-parking")?.replaceChildren(...airbaseParkings.map((parking: string) => { - var div = document.createElement("div"); - div.innerText = parking; - return div; - })); - } - - /** - * - * @param coalition Coalition to which the airbase belongs - */ - #setCoalition(coalition: string) { - (this.getContainer()?.querySelector("#spawn-airbase-aircraft-button") as HTMLElement).dataset.coalition = coalition; - } - - /** - * - * @param showSpawnButton If true, the spawn button will be visibile - */ - #showSpawnButton(showSpawnButton: boolean) { - this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")?.classList.toggle("hide", !showSpawnButton); - } - - /** - * - * @param showLandButton If true, the land button will be visible - */ - #showLandButton(showLandButton: boolean) { - this.getContainer()?.querySelector("#land-here-button")?.classList.toggle("hide", !showLandButton); - } - - /** Shows the spawn context menu which allows the user to select a unit to ground spawn at the airbase - * - */ - #showSpawnMenu() { - if (this.#airbase != null) { - getApp().setActiveCoalition(this.#airbase.getCoalition()); - getApp().getMap().showAirbaseSpawnMenu(this.#airbase, this.getX(), this.getY(), this.getLatLng()); - } - } - - /** @todo needs commenting - * - */ - #setAirbaseData() { - if (this.#airbase && this.getContainer()) { - dataPointMap(this.getContainer() as HTMLElement, { - "coalition": this.#airbase.getCoalition(), - "airbaseName": this.#airbase.getName() - }); - - dataPointMap( this.getContainer() as HTMLElement, this.#airbase.getChartData() ); - - const runwaysContainer = this.getContainer()?.querySelector( "#airbase-runways" ) as HTMLElement; - runwaysContainer.innerHTML = ""; - - if ( runwaysContainer instanceof HTMLElement ) { - const runways = this.#airbase.getChartData().runways; - - if ( runways.length === 0 ) { - runwaysContainer.innerText = "No data"; - } else { - runways.forEach( (runway: AirbaseChartRunwayData) => { - let runwayDiv = document.createElement( "div" ); - runwayDiv.classList.add( "runway" ); - - runway.headings.forEach( (headings: AirbaseChartRunwayHeadingData) => { - for ( const [ heading, data ] of Object.entries( headings ) ) { - - let headingDiv = document.createElement( "div" ); - headingDiv.classList.add( "heading" ); - - let abbr = document.createElement( "abbr" ); - abbr.title = `Mag heading: ${data.magHeading}`; - abbr.innerText = heading; - - headingDiv.appendChild( abbr ); - runwayDiv.appendChild( headingDiv ); - - if ( data.ILS ) { - let ilsDiv = document.createElement( "div" ); - ilsDiv.classList.add( "ils" ); - - abbr = document.createElement( "abbr" ); - abbr.title = data.ILS; - abbr.innerText = "ILS"; - - ilsDiv.appendChild( abbr ); - headingDiv.appendChild( ilsDiv ); - } - } - }); - - runwaysContainer.appendChild( runwayDiv ); - }); - } - } - } - } -} \ No newline at end of file diff --git a/frontend/website/src/contextmenus/airbasespawnmenu.ts b/frontend/website/src/contextmenus/airbasespawnmenu.ts deleted file mode 100644 index ff753c6e..00000000 --- a/frontend/website/src/contextmenus/airbasespawnmenu.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { LatLng } from "leaflet"; -import { ContextMenu } from "./contextmenu"; -import { AircraftSpawnMenu, HelicopterSpawnMenu } from "../controls/unitspawnmenu"; -import { Airbase } from "../mission/airbase"; -import { getApp } from ".."; - -/** This context menu is shown when the user wants to spawn a new aircraft or helicopter from the ground at an airbase. - * It is shown by clicking on the "spawn" button of a AirbaseContextMenu. */ -export class AirbaseSpawnContextMenu extends ContextMenu { - #aircraftSpawnMenu: AircraftSpawnMenu; - #helicopterSpawnMenu: HelicopterSpawnMenu; - - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID); - - /* Create the spawn menus for the different unit types */ - this.#aircraftSpawnMenu = new AircraftSpawnMenu("airbase-aircraft-spawn-menu"); - this.#helicopterSpawnMenu = new HelicopterSpawnMenu("airbase-helicopter-spawn-menu"); - - this.#aircraftSpawnMenu.getAltitudeSlider().hide(); - this.#helicopterSpawnMenu.getAltitudeSlider().hide(); - - /* Event listeners */ - document.addEventListener("mapContextMenuShow", (e: any) => { - if (this.getVisibleSubMenu() !== e.detail.type) - this.#showSubMenu(e.detail.type); - else - this.#hideSubMenus(); - }); - - this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); - this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); - - this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); - this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); - - this.hide(); - } - - /** Show the context menu - * - * @param x X screen coordinate of the top left corner of the context menu - * @param y Y screen coordinate of the top left corner of the context menu - */ - show(x: number | undefined, y: number | undefined) { - super.show(x, y, new LatLng(0, 0)); - - this.#aircraftSpawnMenu.setAirbase(undefined); - this.#helicopterSpawnMenu.setAirbase(undefined); - this.#aircraftSpawnMenu.setCountries(); - this.#helicopterSpawnMenu.setCountries(); - - this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) }); - } - - /** Sets the airbase at which the new unit will be spawned - * - * @param airbase The airbase at which the new unit will be spawned. Note: if the airbase has no suitable parking spots, the airplane may be spawned on the runway, or spawning may fail. - */ - setAirbase(airbase: Airbase) { - this.#aircraftSpawnMenu.setAirbase(airbase); - this.#helicopterSpawnMenu.setAirbase(airbase); - } - - /** Shows the submenu depending on unit selection - * - * @param type Submenu type, either "aircraft" or "helicopter" - */ - #showSubMenu(type: string) { - this.getContainer()?.querySelector("#airbase-aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft"); - this.getContainer()?.querySelector("#airbase-aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft"); - this.getContainer()?.querySelector("#airbase-helicopter-spawn-menu")?.classList.toggle("hide", type !== "helicopter"); - this.getContainer()?.querySelector("#airbase-helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter"); - - (this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => { (element as HTMLButtonElement).disabled = true; }) - - this.#aircraftSpawnMenu.reset(); - this.#aircraftSpawnMenu.setCountries(); - this.#helicopterSpawnMenu.reset(); - this.#helicopterSpawnMenu.setCountries(); - - this.setVisibleSubMenu(type); - this.clip(); - } - - /** Hide all the open submenus - * - */ - #hideSubMenus() { - this.getContainer()?.querySelector("#airbase-aircraft-spawn-menu")?.classList.toggle("hide", true); - this.getContainer()?.querySelector("#airbase-aircraft-spawn-button")?.classList.toggle("is-open", false); - this.getContainer()?.querySelector("#airbase-helicopter-spawn-menu")?.classList.toggle("hide", true); - this.getContainer()?.querySelector("#airbase-helicopter-spawn-button")?.classList.toggle("is-open", false); - - this.#aircraftSpawnMenu.reset(); - this.#helicopterSpawnMenu.reset(); - - this.setVisibleSubMenu(null); - this.clip(); - } -} \ No newline at end of file diff --git a/frontend/website/src/contextmenus/coalitionareacontextmenu.ts b/frontend/website/src/contextmenus/coalitionareacontextmenu.ts deleted file mode 100644 index f5bceafe..00000000 --- a/frontend/website/src/contextmenus/coalitionareacontextmenu.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { LatLng } from "leaflet"; -import { getApp } from ".."; -import { GAME_MASTER, IADSTypes } from "../constants/constants"; -import { CoalitionArea } from "../map/coalitionarea/coalitionarea"; -import { ContextMenu } from "./contextmenu"; -import { Dropdown } from "../controls/dropdown"; -import { Slider } from "../controls/slider"; -import { Switch } from "../controls/switch"; -import { groundUnitDatabase } from "../unit/databases/groundunitdatabase"; -import { createCheckboxOption, getCheckboxOptions } from "../other/utils"; - -/** This context menu allows the user to edit or delete a CoalitionArea. Moreover, it allows the user to create a IADS automatically using the CoalitionArea as bounds. */ -export class CoalitionAreaContextMenu extends ContextMenu { - #coalitionSwitch: Switch; - #coalitionArea: CoalitionArea | null = null; - #iadsDensitySlider: Slider; - #iadsDistributionSlider: Slider; - #iadsTypesDropdown: Dropdown; - #iadsErasDropdown: Dropdown; - #iadsRangesDropdown: Dropdown; - - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID); - - /* Create the coalition switch */ - this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value), true); - this.#coalitionSwitch.setValue(true); - - /* Create the controls of the IADS creation submenu */ - this.#iadsTypesDropdown = new Dropdown("iads-units-type-options", () => { }); - this.#iadsErasDropdown = new Dropdown("iads-era-options", () => {}); - this.#iadsRangesDropdown = new Dropdown("iads-range-options", () => {}); - this.#iadsDensitySlider = new Slider("iads-density-slider", 5, 100, "%", (value: number) => { }); - this.#iadsDistributionSlider = new Slider("iads-distribution-slider", 5, 100, "%", (value: number) => { }); - - /* Set the default parameters of the sliders */ - this.#iadsDensitySlider.setIncrement(5); - this.#iadsDensitySlider.setValue(50); - this.#iadsDensitySlider.setActive(true); - this.#iadsDistributionSlider.setIncrement(5); - this.#iadsDistributionSlider.setValue(50); - this.#iadsDistributionSlider.setActive(true); - - document.addEventListener("coalitionAreaContextMenuShow", (e: any) => { - if (this.getVisibleSubMenu() !== e.detail.type) - this.#showSubMenu(e.detail.type); - else - this.#hideSubMenus(); - }); - - document.addEventListener("coalitionAreaBringToBack", (e: any) => { - if (this.#coalitionArea) - getApp().getMap().bringCoalitionAreaToBack(this.#coalitionArea); - getApp().getMap().hideCoalitionAreaContextMenu(); - }); - - document.addEventListener("coalitionAreaDelete", (e: any) => { - if (this.#coalitionArea) - getApp().getMap().deleteCoalitionArea(this.#coalitionArea); - getApp().getMap().hideCoalitionAreaContextMenu(); - }); - - document.addEventListener("contextMenuCreateIads", (e: any) => { - const area = this.getCoalitionArea(); - const forceCoalition = (this.getContainer()?.querySelector("#force-coalition")?.querySelector("input") as HTMLInputElement).checked; - if (area) - getApp().getUnitsManager().createIADS(area, getCheckboxOptions(this.#iadsTypesDropdown), getCheckboxOptions(this.#iadsErasDropdown), getCheckboxOptions(this.#iadsRangesDropdown), this.#iadsDensitySlider.getValue(), this.#iadsDistributionSlider.getValue(), forceCoalition); - this.hide(); - }); - this.hide(); - } - - /** - * - * @param x X screen coordinate of the top left corner of the context menu - * @param y Y screen coordinate of the top left corner of the context menu - * @param latlng Leaflet latlng object of the mouse click - */ - show(x: number, y: number, latlng: LatLng) { - super.show(x, y, latlng); - - /* Create the checkboxes to select the unit roles */ - this.#iadsTypesDropdown.setOptionsElements(IADSTypes.map((role: string) => { - return createCheckboxOption(role, `Add ${role}s to the IADS` ); - })); - - - /* Create the checkboxes to select the unit periods */ - this.#iadsErasDropdown.setOptionsElements(groundUnitDatabase.getEras().map((era: string) => { - return createCheckboxOption(era, `Add ${era} era units to the IADS`); - })); - - /* Create the checkboxes to select the unit ranges */ - this.#iadsRangesDropdown.setOptionsElements(["Short range", "Medium range", "Long range"].map((range: string) => { - return createCheckboxOption(range, `Add ${range} units to the IADS`); - })); - - if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER) - this.#coalitionSwitch.hide() - } - - /** Set the CoalitionArea object the user will be able to edit using this menu - * - * @param coalitionArea The CoalitionArea object to edit - */ - setCoalitionArea(coalitionArea: CoalitionArea) { - this.#coalitionArea = coalitionArea; - this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { - element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition()) - }); - this.#coalitionSwitch.setValue(this.getCoalitionArea()?.getCoalition() === "blue"); - } - - /** Get the CoalitionArea object the contextmenu is editing - * - * @returns The CoalitionArea the contextmenu is editing - */ - getCoalitionArea() { - return this.#coalitionArea; - } - - /** Show a submenu of the contextmenu - * - * @param type The submenu type, currently only "iads" - */ - #showSubMenu(type: string) { - this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", type !== "iads"); - this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", type === "iads"); - this.clip(); - - this.setVisibleSubMenu(type); - } - - /** Hide all submenus - * - */ - #hideSubMenus() { - this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", true); - this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", false); - this.clip(); - - this.setVisibleSubMenu(null); - } - - /** Callback event called when the coalition switch is clicked to change the coalition of the CoalitionArea - * - * @param value Switch position (false: red, true: blue) - */ - #onSwitchClick(value: boolean) { - if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER) { - this.getCoalitionArea()?.setCoalition(value ? "blue" : "red"); - this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { - element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition()) - }); - } - } -} \ No newline at end of file diff --git a/frontend/website/src/contextmenus/contextmenu.ts b/frontend/website/src/contextmenus/contextmenu.ts deleted file mode 100644 index 78d91713..00000000 --- a/frontend/website/src/contextmenus/contextmenu.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { LatLng } from "leaflet"; - -/** Base class for map contextmenus. By default it is empty and requires to be extended. */ -export class ContextMenu { - #container: HTMLElement | null; - #latlng: LatLng = new LatLng(0, 0); - #x: number = 0; - #y: number = 0; - #visibleSubMenu: string | null = null; - - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - this.#container = document.getElementById(ID); - this.hide(); - } - - /** Show the contextmenu on top of the map, usually at the location where the user has clicked on it. - * - * @param x X screen coordinate of the top left corner of the context menu. If undefined, use the old value - * @param y Y screen coordinate of the top left corner of the context menu. If undefined, use the old value - * @param latlng Leaflet latlng object of the mouse click. If undefined, use the old value - */ - show(x: number | undefined = undefined, y: number | undefined = undefined, latlng: LatLng | undefined = undefined) { - this.#latlng = latlng ?? this.#latlng; - this.#container?.classList.toggle("hide", false); - this.#x = x ?? this.#x; - this.#y = y ?? this.#y; - this.clip(); - this.getContainer()?.dispatchEvent(new Event("show")); - } - - /** Hide the contextmenu - * - */ - hide() { - this.#container?.classList.toggle("hide", true); - this.getContainer()?.dispatchEvent(new Event("hide")); - } - - /** - * - * @returns The HTMLElement that contains the contextmenu - */ - getContainer() { - return this.#container; - } - - /** - * - * @returns The Leaflet latlng object associated to the click that caused the contextmenu to be shown - */ - getLatLng() { - return this.#latlng; - } - - /** - * - * @returns The x coordinate of the top left corner of the menu - */ - getX() { - return this.#x; - } - - /** - * - * @returns The y coordinate of the top left corner of the menu - */ - getY() { - return this.#y; - } - - /** Clips the contextmenu, meaning it moves it on the screen to make sure it does not overflow the window. - * - */ - clip() { - if (this.#container != null) { - if (this.#x + this.#container.offsetWidth < window.innerWidth) - this.#container.style.left = this.#x + "px"; - else - this.#container.style.left = window.innerWidth - this.#container.offsetWidth - 10 + "px"; - - if (this.#y + this.#container.offsetHeight < window.innerHeight) - this.#container.style.top = this.#y + "px"; - else - this.#container.style.top = window.innerHeight - this.#container.offsetHeight - 10 + "px"; - } - } - - /** Sets the currently visible submenu - * - * @param menu The name of the currently visibile submenu, or null if no submenu is visible - */ - setVisibleSubMenu(menu: string | null) { - this.#visibleSubMenu = menu; - } - - /** - * - * @returns The name of the currently visible submenu - */ - getVisibleSubMenu() { - return this.#visibleSubMenu; - } -} \ No newline at end of file diff --git a/frontend/website/src/contextmenus/mapcontextmenu.ts b/frontend/website/src/contextmenus/mapcontextmenu.ts deleted file mode 100644 index bff15bc3..00000000 --- a/frontend/website/src/contextmenus/mapcontextmenu.ts +++ /dev/null @@ -1,351 +0,0 @@ -import { LatLng } from "leaflet"; -import { getApp } from ".."; -import { ContextMenu } from "./contextmenu"; -import { Switch } from "../controls/switch"; -import { GAME_MASTER } from "../constants/constants"; -import { CoalitionArea } from "../map/coalitionarea/coalitionarea"; -import { AirDefenceUnitSpawnMenu, AircraftSpawnMenu, GroundUnitSpawnMenu, HelicopterSpawnMenu, NavyUnitSpawnMenu } from "../controls/unitspawnmenu"; -import { SmokeMarker } from "../map/markers/smokemarker"; -import { UnitSpawnTable } from "../interfaces"; -import { getCategoryBlueprintIconSVG, getUnitDatabaseByCategory } from "../other/utils"; -import { SVGInjector } from "@tanem/svg-injector"; - -/** The MapContextMenu is the main contextmenu shown to the user whenever it rightclicks on the map. It is the primary interaction method for the user. - * It allows to spawn units, create explosions and smoke, and edit CoalitionAreas. - */ -export class MapContextMenu extends ContextMenu { - #coalitionSwitch: Switch; - #aircraftSpawnMenu: AircraftSpawnMenu; - #helicopterSpawnMenu: HelicopterSpawnMenu; - #airDefenceUnitSpawnMenu: AirDefenceUnitSpawnMenu; - #groundUnitSpawnMenu: GroundUnitSpawnMenu; - #navyUnitSpawnMenu: NavyUnitSpawnMenu; - #coalitionArea: CoalitionArea | null = null; - #selectedUnitCategory: string = ""; - - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID); - - /* Create the coalition switch */ - this.#coalitionSwitch = new Switch("coalition-switch", (value: boolean) => this.#onSwitchClick(value)); - this.#coalitionSwitch.setValue(true); - this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick()); - - /* Create the spawn menus for the different unit types */ - this.#aircraftSpawnMenu = new AircraftSpawnMenu("aircraft-spawn-menu"); - this.#helicopterSpawnMenu = new HelicopterSpawnMenu("helicopter-spawn-menu"); - this.#airDefenceUnitSpawnMenu = new AirDefenceUnitSpawnMenu("air-defence-spawn-menu"); - this.#groundUnitSpawnMenu = new GroundUnitSpawnMenu("groundunit-spawn-menu"); - this.#navyUnitSpawnMenu = new NavyUnitSpawnMenu("navyunit-spawn-menu"); - - /* Event listeners */ - document.addEventListener("mapContextMenuShow", (e: any) => { - if (this.getVisibleSubMenu() !== e.detail.type) { - this.#showSubMenu(e.detail.type); - this.#selectedUnitCategory = e.detail.type; - } else - this.#hideSubMenus(e.detail.type); - }); - - document.addEventListener("contextMenuDeploySmoke", (e: any) => { - this.hide(); - getApp().getServerManager().spawnSmoke(e.detail.color, this.getLatLng()); - var marker = new SmokeMarker(this.getLatLng(), e.detail.color); - marker.addTo(getApp().getMap()); - }); - - document.addEventListener("contextMenuExplosion", (e: any) => { - this.hide(); - getApp().getServerManager().spawnExplosion(e.detail.strength ?? 0, e.detail.explosionType, this.getLatLng()); - }); - - document.addEventListener("editCoalitionArea", (e: any) => { - this.hide(); - if (this.#coalitionArea) { - getApp().getMap().deselectAllCoalitionAreas(); - this.#coalitionArea.setSelected(true); - } - }); - - // document.addEventListener("commandModeOptionsChanged", (e: any) => { - // //this.#refreshOptions(); - // }); - - this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); - this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); - this.#airDefenceUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); - this.#groundUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); - this.#navyUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); - - this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); - this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); - this.#airDefenceUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); - this.#groundUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); - this.#navyUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); - - this.getContainer()?.addEventListener("show", () => this.#aircraftSpawnMenu.showCirclesPreviews()); - this.getContainer()?.addEventListener("show", () => this.#helicopterSpawnMenu.showCirclesPreviews()); - this.getContainer()?.addEventListener("show", () => this.#airDefenceUnitSpawnMenu.showCirclesPreviews()); - this.getContainer()?.addEventListener("show", () => this.#groundUnitSpawnMenu.showCirclesPreviews()); - this.getContainer()?.addEventListener("show", () => this.#navyUnitSpawnMenu.showCirclesPreviews()); - - this.getContainer()?.addEventListener("hide", () => this.#aircraftSpawnMenu.clearCirclesPreviews()); - this.getContainer()?.addEventListener("hide", () => this.#helicopterSpawnMenu.clearCirclesPreviews()); - this.getContainer()?.addEventListener("hide", () => this.#airDefenceUnitSpawnMenu.clearCirclesPreviews()); - this.getContainer()?.addEventListener("hide", () => this.#groundUnitSpawnMenu.clearCirclesPreviews()); - this.getContainer()?.addEventListener("hide", () => this.#navyUnitSpawnMenu.clearCirclesPreviews()); - - this.#setupHistory(); - } - - /** Show the contextmenu on top of the map, usually at the location where the user has clicked on it. - * - * @param x X screen coordinate of the top left corner of the context menu - * @param y Y screen coordinate of the top left corner of the context menu - * @param latlng Leaflet latlng object of the mouse click - */ - show(x: number, y: number, latlng: LatLng) { - if (!getApp().getCurrentContext().getUseSpawnMenu()) - return false; - - super.show(x, y, latlng); - - this.#aircraftSpawnMenu.setLatLng(latlng); - this.#helicopterSpawnMenu.setLatLng(latlng); - this.#airDefenceUnitSpawnMenu.setLatLng(latlng); - this.#groundUnitSpawnMenu.setLatLng(latlng); - this.#navyUnitSpawnMenu.setLatLng(latlng); - - this.#aircraftSpawnMenu.setCountries(); - this.#helicopterSpawnMenu.setCountries(); - this.#airDefenceUnitSpawnMenu.setCountries(); - this.#groundUnitSpawnMenu.setCountries(); - this.#navyUnitSpawnMenu.setCountries(); - - /* Only a Game Master can choose the coalition of a new unit */ - if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER) - this.#coalitionSwitch.hide() - - this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) }); - if (getApp().getActiveCoalition() == "blue") - this.#coalitionSwitch.setValue(true); - else if (getApp().getActiveCoalition() == "red") - this.#coalitionSwitch.setValue(false); - else - this.#coalitionSwitch.setValue(undefined); - - /* Hide the coalition area button. It will be visible if a coalition area is set */ - this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", true); - } - - /** If the user rightclicked on a CoalitionArea, it will be given the ability to edit it. - * - * @param coalitionArea The CoalitionArea the user can edit - */ - setCoalitionArea(coalitionArea: CoalitionArea) { - this.#coalitionArea = coalitionArea; - this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", false); - } - - /** Shows the submenu depending on unit selection - * - * @param type Submenu type, either "aircraft", "helicopter", "groundunit", "navyunit", "smoke", or "explosion" - */ - #showSubMenu(type: string) { - if (type === "more") - this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide"); - else if (["aircraft", "helicopter", "air-defence", "groundunit"].includes(type)) - this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", true); - - this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft"); - this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft"); - this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", type !== "helicopter"); - this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter"); - this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", type !== "groundunit"); - this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", type === "groundunit"); - this.getContainer()?.querySelector("#air-defence-spawn-menu")?.classList.toggle("hide", type !== "air-defence"); - this.getContainer()?.querySelector("#air-defence-spawn-button")?.classList.toggle("is-open", type === "air-defence"); - this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", type !== "navyunit"); - this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", type === "navyunit"); - this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke"); - this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", type === "smoke"); - this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion"); - this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", type === "explosion"); - - (this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => { (element as HTMLButtonElement).disabled = true; }) - - this.#aircraftSpawnMenu.reset(); - this.#aircraftSpawnMenu.setCountries(); - this.#aircraftSpawnMenu.clearCirclesPreviews(); - this.#helicopterSpawnMenu.reset(); - this.#helicopterSpawnMenu.setCountries(); - this.#helicopterSpawnMenu.clearCirclesPreviews(); - this.#airDefenceUnitSpawnMenu.reset(); - this.#airDefenceUnitSpawnMenu.setCountries(); - this.#airDefenceUnitSpawnMenu.clearCirclesPreviews(); - this.#groundUnitSpawnMenu.reset(); - this.#groundUnitSpawnMenu.setCountries(); - this.#groundUnitSpawnMenu.clearCirclesPreviews(); - this.#navyUnitSpawnMenu.reset(); - this.#navyUnitSpawnMenu.setCountries(); - this.#navyUnitSpawnMenu.clearCirclesPreviews(); - - this.setVisibleSubMenu(type); - this.clip(); - } - - /** Hide all the submenus - * - * @param type The type of the currenlt open submenu. - */ - #hideSubMenus(type: string) { - /* Close the lower options bar if the currently open submenu does not required it */ - this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", ["aircraft", "helicopter", "groundunit"].includes(type)); - this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", true); - this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", false); - this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", true); - this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", false); - this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", true); - this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", false); - this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", true); - this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", false); - this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", true); - this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", false); - this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", true); - this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", false); - - this.#aircraftSpawnMenu.reset(); - this.#helicopterSpawnMenu.reset(); - this.#airDefenceUnitSpawnMenu.reset(); - this.#groundUnitSpawnMenu.reset(); - this.#navyUnitSpawnMenu.reset(); - - this.#aircraftSpawnMenu.clearCirclesPreviews(); - this.#helicopterSpawnMenu.clearCirclesPreviews(); - this.#airDefenceUnitSpawnMenu.clearCirclesPreviews(); - this.#groundUnitSpawnMenu.clearCirclesPreviews(); - this.#navyUnitSpawnMenu.clearCirclesPreviews(); - - this.setVisibleSubMenu(null); - this.clip(); - } - - /** Callback called when the user left clicks on the coalition switch - * - * @param value Switch position (true: "blue", false: "red") - */ - #onSwitchClick(value: boolean) { - value ? getApp().setActiveCoalition("blue") : getApp().setActiveCoalition("red"); - this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) }); - this.#aircraftSpawnMenu.setCountries(); - this.#helicopterSpawnMenu.setCountries(); - this.#airDefenceUnitSpawnMenu.setCountries(); - this.#groundUnitSpawnMenu.setCountries(); - this.#navyUnitSpawnMenu.setCountries(); - } - - /** Callback called when the user rightclicks on the coalition switch. This will select the "neutral" coalition. - * - */ - #onSwitchRightClick() { - this.#coalitionSwitch.setValue(undefined); - getApp().setActiveCoalition("neutral"); - this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) }); - this.#aircraftSpawnMenu.setCountries(); - this.#helicopterSpawnMenu.setCountries(); - this.#airDefenceUnitSpawnMenu.setCountries(); - this.#groundUnitSpawnMenu.setCountries(); - this.#navyUnitSpawnMenu.setCountries(); - } - - /** Handles all of the logic for historal logging. - * - */ - #setupHistory() { - /* Set up the tab clicks */ - const spawnModes = this.getContainer()?.querySelectorAll(".spawn-mode"); - const activeCoalitionLabel = document.getElementById("active-coalition-label"); - const tabs = this.getContainer()?.querySelectorAll(".spawn-mode-tab"); - - // Default selected tab to the "spawn now" option - if (tabs) tabs[tabs.length-1].classList.add("selected"); - - tabs?.forEach((btn:Element) => { - btn.addEventListener("click", (ev:MouseEventInit) => { - // Highlight tab - tabs.forEach(tab => tab.classList.remove("selected")); - btn.classList.add("selected"); - - // Hide/reset - spawnModes?.forEach(div => div.classList.add("hide")); - - const prevSiblings = []; - let prev = btn.previousElementSibling; - - /* Tabs and content windows are assumed to be in the same order */ - // Count previous - while ( prev ) { - prevSiblings.push(prev); - prev = prev.previousElementSibling; - } - - // Show content - if (spawnModes && spawnModes[prevSiblings.length]) { - spawnModes[prevSiblings.length].classList.remove("hide"); - } - - // We don't want to see the "Spawn [coalition] unit" label - if (activeCoalitionLabel) activeCoalitionLabel.classList.toggle("hide", !btn.hasAttribute("data-show-label")); - }); - }); - - const history = document.getElementById("spawn-history-menu"); - const maxEntries = 20; - - /** Listen for unit spawned **/ - document.addEventListener( "unitSpawned", (ev:CustomEventInit) => { - const buttons = history.querySelectorAll("button"); - const detail:any = ev.detail; - if (buttons.length === 0) history.innerHTML = ""; // Take out any "no data" messages - const button = document.createElement("button"); - button.title = "Click to spawn"; - button.setAttribute("data-spawned-coalition", detail.coalition); - button.setAttribute("data-unit-type", detail.unitSpawnTable[0].unitType); - button.setAttribute("data-unit-qty", detail.unitSpawnTable.length); - - const db = getUnitDatabaseByCategory(detail.category); - button.innerHTML = `${db?.getByName(detail.unitSpawnTable[0].unitType)?.label} (${detail.unitSpawnTable.length})`; - - // Remove a previous instance to save clogging up the list - const previous:any = [].slice.call(buttons).find( (button:Element) => ( - detail.coalition === button.getAttribute("data-spawned-coalition") && - detail.unitSpawnTable[0].unitType === button.getAttribute("data-unit-type") && - detail.unitSpawnTable.length === parseInt(button.getAttribute("data-unit-qty") || "-1"))); - - if (previous instanceof HTMLElement) previous.remove(); - - /* Click to do the spawn */ - button.addEventListener("click", (ev:MouseEventInit) => { - detail.unitSpawnTable.forEach((table:UnitSpawnTable, i:number) => { - table.location = this.getLatLng(); // Set to new menu location - table.location.lat += 0.00015 * i; - }); - getApp().getUnitsManager().spawnUnits(detail.category, detail.unitSpawnTable, detail.coalition, detail.immediate, detail.airbase, detail.country); - this.hide(); - }); - - /* Insert into DOM */ - history.prepend(button); - SVGInjector(button.querySelectorAll("img")); - - /* Trim down to max number of entries */ - while (history.querySelectorAll("button").length > maxEntries) { - history.childNodes[maxEntries].remove(); - } - }); - } -} \ No newline at end of file diff --git a/frontend/website/src/contextmenus/unitcontextmenu.ts b/frontend/website/src/contextmenus/unitcontextmenu.ts deleted file mode 100644 index 57502abb..00000000 --- a/frontend/website/src/contextmenus/unitcontextmenu.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ContextActionSet } from "../unit/contextactionset"; -import { ContextMenu } from "./contextmenu"; - -/** The UnitContextMenu is shown when the user rightclicks on a unit. It dynamically presents the user with possible actions to perform on the unit. */ -export class UnitContextMenu extends ContextMenu { - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID); - } - - /** Set the options that will be presented to the user in the contextmenu - * - * @param options Dictionary element containing the text and tooltip of the options shown in the menu - * @param callback Callback that will be called when the user clicks on one of the options - */ - setContextActions(contextActionSet: ContextActionSet) { - this.getContainer()?.replaceChildren(...Object.keys(contextActionSet.getContextActions()).map((key: string, idx: number) => { - const contextAction = contextActionSet.getContextActions()[key]; - var button = document.createElement("button"); - var el = document.createElement("div"); - el.title = contextAction.getDescription(); - el.innerText = contextAction.getLabel(); - el.id = key; - button.addEventListener("click", () => { - contextAction.executeCallback(); - if (contextAction.getHideContextAfterExecution()) - this.hide(); - }); - button.appendChild(el); - return (button); - })); - } -} \ No newline at end of file diff --git a/frontend/website/src/controls/control.ts b/frontend/website/src/controls/control.ts deleted file mode 100644 index 37bae2fe..00000000 --- a/frontend/website/src/controls/control.ts +++ /dev/null @@ -1,41 +0,0 @@ -export class Control { - #container: HTMLElement | null; - expectedValue: any = null; - - constructor(container: string | null, options?: any) { - if (typeof container === "string") - this.#container = document.getElementById(container); - else - this.#container = this.createElement(options); - } - - show() { - if (this.#container != null) - this.#container.classList.remove("hide"); - } - - hide() { - if (this.#container != null) - this.#container.classList.add("hide"); - } - - getContainer() { - return this.#container; - } - - setExpectedValue(expectedValue: any) { - this.expectedValue = expectedValue; - } - - resetExpectedValue() { - this.expectedValue = null; - } - - checkExpectedValue(value: any) { - return this.expectedValue === null || value === this.expectedValue; - } - - createElement(options?: any): HTMLElement | null { - return null; - } -} \ No newline at end of file diff --git a/frontend/website/src/controls/dropdown.ts b/frontend/website/src/controls/dropdown.ts deleted file mode 100644 index 99694154..00000000 --- a/frontend/website/src/controls/dropdown.ts +++ /dev/null @@ -1,345 +0,0 @@ -export class Dropdown { - #container: HTMLElement; - #options: HTMLElement; - #value: HTMLElement; - #callback: CallableFunction; - #defaultValue: string; - #optionsList: string[] = []; - #labelsList: string[] | undefined = []; - #index: number = 0; - #hidden: boolean = false; - #text!: HTMLElement; - - constructor(ID: string | null, callback: CallableFunction, options: string[] | null = null, defaultText?: string) { - if (ID === null) - this.#container = this.#createElement(defaultText); - else - this.#container = document.getElementById(ID) as HTMLElement; - - this.#options = this.#container.querySelector(".ol-select-options") as HTMLElement; - - const text = this.#container.querySelector(".ol-select-value-text"); - this.#value = (text instanceof HTMLElement) ? text : this.#container.querySelector(".ol-select-value") as HTMLElement; - - this.#defaultValue = this.#value.innerText; - this.#callback = callback; - - if (options != null) this.setOptions(options); - - (this.#container.querySelector(".ol-select-value") as HTMLElement)?.addEventListener("click", (ev) => { - if (!this.#container.hasAttribute("disabled") && !this.#container.closest("disabled")) this.#toggle(); - }); - - document.addEventListener("click", (ev) => { - if (!(this.#value.contains(ev.target as Node) || this.#options.contains(ev.target as Node) || this.#container.contains(ev.target as Node))) { - this.close(); - } - }); - - this.#options.classList.add("ol-scrollable"); - } - - getContainer() { - return this.#container; - } - - /** Set the dropdown options strings - * - * @param optionsList List of options. These are the keys that will always be returned on selection - * @param sort Sort method. null means no sorting. "string" performs js default sort. "number" sorts purely by numeric value. - * "string+number" sorts by string, unless two elements are lexicographically identical up to a numeric value (e.g. "SA-2" and "SA-3"), in which case it sorts by number. - * @param labelsList (Optional) List of labels to be shown instead of the keys directly. If provided, the options will be sorted by label. - */ - setOptions(optionsList: string[], sort: null | "string" | "number" | "string+number" = "string", labelsList: string[] | undefined = undefined) { - if (sort != null) { - /* If labels are provided, sort by labels, else by options */ - if (labelsList && labelsList.length == optionsList.length) - this.#sortByLabels(optionsList, sort, labelsList); - else - this.#sortByOptions(optionsList, sort); - } else { - this.#optionsList = optionsList; - } - - /* If no options are provided, return */ - if (this.#optionsList.length == 0) { - optionsList = ["No options available"] - this.#value.innerText = "No options available"; - return; - } - - /* Create the buttons containing the options or the labels */ - this.#options.replaceChildren(...this.#optionsList.map((option: string, idx: number) => { - var div = document.createElement("div"); - var button = document.createElement("button"); - - /* If the labels are provided use them for the options */ - if (this.#labelsList && this.#labelsList.length === optionsList.length) - button.textContent = this.#labelsList[idx]; - else - button.textContent = option; - div.appendChild(button); - - if (option === this.#defaultValue) - this.#index = idx; - - button.addEventListener("click", (e: MouseEvent) => { - e.stopPropagation(); - this.selectValue(idx); - }); - return div; - })); - } - - getOptionsList() { - return this.#optionsList; - } - - getLabelsList() { - return this.#labelsList; - } - - /** Manually set the HTMLElements of the dropdown values. Handling of the selection must be performed externally. - * - * @param optionsElements List of elements to be added to the dropdown - */ - setOptionsElements(optionsElements: HTMLElement[]) { - this.#optionsList = []; - this.#labelsList = []; - this.#options.replaceChildren(...optionsElements); - } - - getOptionElements() { - return this.#options.children; - } - - addOptionElement(optionElement: HTMLElement) { - this.#options.appendChild(optionElement); - } - - addHorizontalDivider() { - let div = document.createElement("div"); - let hr = document.createElement('hr'); - div.appendChild(hr); - this.#options.appendChild(div); - } - - /** Select the active value of the dropdown - * - * @param idx The index of the element to select - * @returns True if the index is valid, false otherwise - */ - selectValue(idx: number) { - if (idx < this.#optionsList.length) { - var option = this.#optionsList[idx]; - var el = document.createElement("div"); - el.classList.add("ol-ellipsed"); - - if (this.#labelsList && this.#labelsList.length == this.#optionsList.length) - el.innerText = this.#labelsList[idx]; - else - el.innerText = option; - - this.#value.replaceChildren(); - this.#value.appendChild(el); - this.#index = idx; - this.close(); - this.#callback(option); - return true; - } - else - return false; - } - - reset() { - this.#options.replaceChildren(); - this.#value.innerText = this.#defaultValue; - } - - /** Manually set the selected value of the dropdown - * - * @param value The value to select. Must be one of the valid options - */ - setValue(value: string) { - var index = this.#optionsList.findIndex((option) => { return option === value }); - if (index > -1) - this.selectValue(index); - } - - getValue() { - return this.#value.innerText; - } - - /** Force the selected value of the dropdown. - * - * @param value Any string. Will be shown as selected value even if not one of the options. - */ - forceValue(value: string) { - var el = document.createElement("div"); - el.classList.add("ol-ellipsed"); - el.innerText = value; - this.#value.replaceChildren(); - this.#value.appendChild(el); - this.close(); - } - - getIndex() { - return this.#index; - } - - clip() { - const options = this.#options; - const bounds = options.getBoundingClientRect(); - this.#container.dataset.position = (bounds.bottom > window.innerHeight) ? "top" : ""; - } - - close() { - this.#container.classList.remove("is-open"); - this.#container.dataset.position = ""; - } - - open() { - this.#container.classList.add("is-open"); - this.#options.classList.toggle("scrollbar-visible", this.#options.scrollHeight > this.#options.clientHeight); - this.clip(); - } - - show() { - this.#container.classList.remove("hide"); - this.#hidden = false; - } - - hide() { - this.#container.classList.add("hide"); - this.#hidden = true; - } - - isHidden() { - return this.#hidden; - } - - #toggle() { - this.#container.classList.contains("is-open") ? this.close() : this.open(); - } - - #createElement(defaultText: string | undefined) { - var div = document.createElement("div"); - div.classList.add("ol-select"); - - var value = document.createElement("div"); - value.classList.add("ol-select-value"); - value.innerText = defaultText ? defaultText : ""; - - var options = document.createElement("div"); - options.classList.add("ol-select-options"); - - div.append(value, options); - return div; - } - - /** Sort the elements by their option keys - * - * @param optionsList The unsorted list of options - * @param sort The sorting method - */ - #sortByOptions(optionsList: string[], sort: string) { - if (sort === "number") { - this.#optionsList = JSON.parse(JSON.stringify(this.#numberSort(optionsList))); - } else if (sort === "string+number") { - this.#optionsList = JSON.parse(JSON.stringify(this.#stringNumberSort(optionsList))); - } else if (sort === "string") { - this.#optionsList = JSON.parse(JSON.stringify(this.#stringSort(optionsList))); - } - } - - /** Sort the elements by their labels - * - * @param optionsList The unsorted list of options - * @param sort The sorting method - * @param labelsList The unsorted list of labels. The elements will be sorted according to these values - */ - #sortByLabels(optionsList: string[], sort: string, labelsList: string[]) { - /* Create a temporary deepcopied list. This is necessary because unlike options, labels can be repeated. - Once matched, labels are removed from the temporary array to avoid repeating the same key multiple times */ - var tempLabelsList: (string | undefined)[] = JSON.parse(JSON.stringify(labelsList)); - - if (sort === "number") { - this.#labelsList = JSON.parse(JSON.stringify(this.#numberSort(labelsList))); - } else if (sort === "string+number") { - this.#labelsList = JSON.parse(JSON.stringify(this.#stringNumberSort(labelsList))); - } else if (sort === "string") { - this.#labelsList = JSON.parse(JSON.stringify(this.#stringSort(labelsList))); - } - - /* Remap the options list to match their labels */ - this.#optionsList = optionsList?.map((option: string, idx: number) => { - let originalIdx = tempLabelsList.indexOf(this.#labelsList? this.#labelsList[idx]: ""); - /* After a match has been completed, set the label to undefined so it won't be matched again. This allows to have repeated labels */ - tempLabelsList[originalIdx] = undefined; - return optionsList[originalIdx]; - }) - } - - /** Sort elements by number. All elements must be parsable as numbers. - * - * @param elements List of strings - * @returns Sorted list - */ - #numberSort(elements: string[]) { - return elements.sort((elementA: string, elementB: string) => { - const a = parseFloat(elementA); - const b = parseFloat(elementB); - if (a > b) - return 1; - else - return (b > a) ? -1 : 0; - }); - } - - /** Sort elements by string, unless two elements are lexicographically identical up to a numeric value (e.g. "SA-2" and "SA-3"), in which case sort by number - * - * @param elements List of strings - * @returns Sorted list - */ - #stringNumberSort(elements: string[]) { - return elements.sort((elementA: string, elementB: string) => { - /* Check if there is a number in both strings */ - var regex = /\d+/g; - var matchesA = elementA.match(regex); - var matchesB = elementB.match(regex); - - /* Get the position of the number in the string */ - var indexA = -1; - var indexB = -1; - if (matchesA != null && matchesA?.length > 0) - indexA = elementA.search(matchesA[0]); - - if (matchesB != null && matchesB?.length > 0) - indexB = elementB.search(matchesB[0]); - - /* If the two strings are the same up to the number, sort them using the number value, else sort them according to the string */ - if ((matchesA != null && matchesA?.length > 0) && (matchesB != null && matchesB?.length > 0) && elementA.substring(0, indexA) === elementB.substring(0, indexB)) { - const a = parseInt(matchesA[0] ?? 0); - const b = parseInt(matchesB[0] ?? 0); - if (a > b) - return 1; - else - return (b > a) ? -1 : 0; - } else { - if (elementA > elementB) - return 1; - else - return (elementB > elementA) ? -1 : 0; - } - }); - } - - /** Sort by string. Just a wrapper for consistency. - * - * @param elements List of strings - * @returns Sorted list - */ - #stringSort(elements: string[]) { - return elements.sort(); - } -} \ No newline at end of file diff --git a/frontend/website/src/controls/slider.ts b/frontend/website/src/controls/slider.ts deleted file mode 100644 index f3ab89e2..00000000 --- a/frontend/website/src/controls/slider.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { zeroPad } from "../other/utils"; -import { Control } from "./control"; - -export class Slider extends Control { - #callback: CallableFunction | null = null; - #slider: HTMLInputElement | null = null; - #valueText: HTMLElement | null = null; - #minValue: number = 0; - #maxValue: number = 0; - #increment: number = 0; - #minMaxValueDiv: HTMLElement | null = null; - #unitOfMeasure: string; - #dragged: boolean = false; - #value: number = 0; - - constructor(ID: string | null, minValue: number, maxValue: number, unitOfMeasure: string, callback: CallableFunction, options?: any) { - super(ID, options); - - this.#callback = callback; - this.#unitOfMeasure = unitOfMeasure; - this.#slider = this.getContainer()?.querySelector("input") as HTMLInputElement; - - if (this.#slider != null) { - this.#slider.addEventListener("input", (e: any) => this.#update()); - this.#slider.addEventListener("mousedown", (e: any) => this.#onStart()); - this.#slider.addEventListener("mouseup", (e: any) => this.#onFinalize()); - } - - this.#valueText = this.getContainer()?.querySelector(".ol-slider-value") as HTMLElement; - this.#minMaxValueDiv = this.getContainer()?.querySelector(".ol-slider-min-max") as HTMLElement; - - this.setIncrement(1); - this.setMinMax(minValue, maxValue); - } - - setActive(newActive: boolean) { - if (!this.getDragged()) { - this.getContainer()?.classList.toggle("active", newActive); - if (!newActive && this.#valueText != null) - this.#valueText.innerText = "Mixed values"; - } - } - - setMinMax(newMinValue: number, newMaxValue: number) { - if (this.#minValue != newMinValue || this.#maxValue != newMaxValue) { - this.#minValue = newMinValue; - this.#maxValue = newMaxValue; - this.#updateMaxValue(); - - if (this.#minMaxValueDiv != null) { - this.#minMaxValueDiv.setAttribute('data-min-value', `${this.#minValue}${this.#unitOfMeasure}`); - this.#minMaxValueDiv.setAttribute('data-max-value', `${this.#maxValue}${this.#unitOfMeasure}`); - } - } - } - - setIncrement(newIncrement: number) { - if (this.#increment != newIncrement) { - this.#increment = newIncrement; - this.#updateMaxValue(); - } - } - - setValue(newValue: number, ignoreExpectedValue: boolean = true) { - if (!this.getDragged() && (ignoreExpectedValue || this.checkExpectedValue(newValue))) { - if (this.#value !== newValue) - this.#value = newValue; - - if (this.#slider != null) - this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max)); - - this.#update(); - } - } - - getValue() { - return this.#value; - } - - setDragged(newDragged: boolean) { - this.#dragged = newDragged; - } - - getDragged() { - return this.#dragged; - } - - #updateMaxValue() { - var oldValue = this.getValue(); - if (this.#slider != null) - this.#slider.max = String((this.#maxValue - this.#minValue) / this.#increment); - this.setValue(oldValue); - } - - #update() { - if (this.#valueText != null && this.#slider != null) - { - /* Update the text value */ - var value = this.#minValue + Math.round(parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue)); - var strValue = String(value); - if (value > 1000) - strValue = String(Math.floor(value / 1000)) + "," + zeroPad(value - Math.floor(value / 1000) * 1000, 3); - this.#valueText.innerText = `${strValue} ${this.#unitOfMeasure.toUpperCase()}`; - - /* Update the position of the slider */ - var percentValue = parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * 90 + 5; - this.#slider.style.background = `linear-gradient(to right, var(--accent-light-blue) 5%, var(--accent-light-blue) ${percentValue}%, var(--background-dark-grey) ${percentValue}%, var(--background-dark-grey) 100%)` - } - this.setActive(true); - } - - #onStart() { - this.setDragged(true); - } - - #onFinalize() { - this.setDragged(false); - if (this.#slider != null) { - this.resetExpectedValue(); - this.setValue(this.#minValue + parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue)); - if (this.#callback) { - this.#callback(this.getValue()); - this.setExpectedValue(this.getValue()); - } - } - } - - createElement(options?: any): HTMLElement | null { - var containerEl = document.createElement("div"); - containerEl.classList.add("ol-slider-container", "flight-control-ol-slider"); - - var dl = document.createElement("dl"); - dl.classList.add("ol-data-grid"); - - var dt = document.createElement("dt"); - dt.innerText = (options !== undefined && options.title !== undefined)? options.title: ""; - - var dd = document.createElement("dd"); - var sliderEl = document.createElement("div"); - sliderEl.classList.add("ol-slider-value"); - dd.append(sliderEl); - dl.append(dt, dd); - - var input = document.createElement("input") as HTMLInputElement; - input.type = "range"; - input.min = "0"; - input.max = "100"; - input.value = "0" - input.classList.add("ol-slider"); - containerEl.append(dl, input); - - return containerEl; - } -} \ No newline at end of file diff --git a/frontend/website/src/controls/switch.ts b/frontend/website/src/controls/switch.ts deleted file mode 100644 index c9f12634..00000000 --- a/frontend/website/src/controls/switch.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Control } from "./control"; - -export class Switch extends Control { - #value: boolean | undefined = false; - #callback: CallableFunction | null = null; - - // TODO: allow for null ID so that the element is created automatically - constructor(ID: string, callback: CallableFunction, initialValue?: boolean) { - super(ID); - this.getContainer()?.addEventListener('click', (e) => this.#onToggle()); - this.setValue(initialValue !== undefined? initialValue: true); - - this.#callback = callback; - - /* Add the toggle itself to the document */ - const container = this.getContainer(); - if (container != undefined){ - const width = getComputedStyle(container).width; - const height = getComputedStyle(container).height; - var el = document.createElement("div"); - el.classList.add("ol-switch-fill"); - el.style.setProperty("--width", width? width: "0"); - el.style.setProperty("--height", height? height: "0"); - this.getContainer()?.appendChild(el); - } - } - - setValue(newValue: boolean | undefined, ignoreExpectedValue: boolean = true) { - if (ignoreExpectedValue || this.checkExpectedValue(newValue)) { - this.#value = newValue; - this.getContainer()?.setAttribute("data-value", String(newValue)); - } - } - - getValue() { - return this.#value; - } - - #onToggle() { - this.resetExpectedValue(); - this.setValue(!this.getValue()); - if (this.#callback) { - this.#callback(this.getValue()); - this.setExpectedValue(this.getValue()); - } - } -} \ No newline at end of file diff --git a/frontend/website/src/controls/unitspawnmenu.ts b/frontend/website/src/controls/unitspawnmenu.ts deleted file mode 100644 index f2cea1cd..00000000 --- a/frontend/website/src/controls/unitspawnmenu.ts +++ /dev/null @@ -1,836 +0,0 @@ -import { Circle, LatLng } from "leaflet"; -import { Dropdown } from "./dropdown"; -import { Slider } from "./slider"; -import { UnitDatabase } from "../unit/databases/unitdatabase"; -import { getApp } from ".."; -import { GAME_MASTER, GROUND_UNIT_AIR_DEFENCE_REGEX } from "../constants/constants"; -import { Airbase } from "../mission/airbase"; -import { ftToM } from "../other/utils"; -import { aircraftDatabase } from "../unit/databases/aircraftdatabase"; -import { helicopterDatabase } from "../unit/databases/helicopterdatabase"; -import { groundUnitDatabase } from "../unit/databases/groundunitdatabase"; -import { navyUnitDatabase } from "../unit/databases/navyunitdatabase"; -import { UnitBlueprint, UnitSpawnOptions, UnitSpawnTable } from "../interfaces"; - -/** This is the common code for all the unit spawn menus. It is shown both when right clicking on the map and when spawning from airbase. - * - */ - -export abstract class UnitSpawnMenu { - protected showRangeCircles: boolean = false; - protected unitTypeFilter = (unit:any) => { return true; }; - /* Default options */ - protected spawnOptions: UnitSpawnOptions = { - roleType: "", - name: "", - latlng: new LatLng(0, 0), - coalition: "blue", - count: 1, - country: "", - skill: "Excellent", - loadout: undefined, - airbase: undefined, - liveryID: undefined, - altitude: undefined - }; - - #container: HTMLElement; - #unitDatabase: UnitDatabase; - #countryCodes: any; - #orderByRole: boolean; - #showLoadout: boolean = true; - #showSkill: boolean = true; - #showAltitudeSlider: boolean = true; - - /* Controls */ - #unitRoleTypeDropdown: Dropdown; - #unitLabelDropdown: Dropdown; - #unitCountDropdown: Dropdown; - #unitLoadoutDropdown: Dropdown; - #unitSkillDropdown: Dropdown; - #unitCountryDropdown: Dropdown; - #unitLiveryDropdown: Dropdown; - #unitSpawnAltitudeSlider: Slider; - - /* HTML Elements */ - #deployUnitButtonEl: HTMLButtonElement; - #unitCountDivider: HTMLDivElement; - #unitLoadoutPreviewEl: HTMLDivElement; - #unitImageEl: HTMLImageElement; - #unitLoadoutListEl: HTMLDivElement; - #descriptionDiv: HTMLDivElement; - #abilitiesDiv: HTMLDivElement; - #advancedOptionsDiv: HTMLDivElement; - #unitInfoDiv: HTMLDivElement; - #advancedOptionsToggle: HTMLDivElement; - #advancedOptionsText: HTMLDivElement; - #unitInfoToggle: HTMLDivElement; - #unitInfoText: HTMLDivElement; - - /* Range circle previews */ - #engagementCircle: Circle; - #acquisitionCircle: Circle; - - constructor(ID: string, unitDatabase: UnitDatabase, orderByRole: boolean) { - this.#container = document.getElementById(ID) as HTMLElement; - this.#unitDatabase = unitDatabase; - this.#orderByRole = orderByRole; - - /* Create the dropdowns and the altitude slider */ - this.#unitRoleTypeDropdown = new Dropdown(null, (roleType: string) => this.#setUnitRoleType(roleType), undefined, "Role"); - this.#unitLabelDropdown = new Dropdown(null, (name: string) => this.#setUnitName(name), undefined, "Type"); - this.#unitLoadoutDropdown = new Dropdown(null, (loadout: string) => this.#setUnitLoadout(loadout), undefined, "Loadout"); - this.#unitSkillDropdown = new Dropdown(null, (skill: string) => this.#setUnitSkill(skill), undefined, "Skill"); - this.#unitCountDropdown = new Dropdown(null, (count: string) => this.#setUnitCount(count), undefined, "Count"); - this.#unitCountryDropdown = new Dropdown(null, () => { /* Custom button implementation */ }, undefined, "Country"); - this.#unitLiveryDropdown = new Dropdown(null, (livery: string) => this.#setUnitLivery(livery), undefined, "Livery"); - this.#unitSpawnAltitudeSlider = new Slider(null, 0, 1000, "ft", (value: number) => { this.spawnOptions.altitude = ftToM(value); }, { title: "Spawn altitude" }); - - /* The unit label and unit count are in the same "row" for clarity and compactness */ - var unitLabelCountContainerEl = document.createElement("div"); - unitLabelCountContainerEl.classList.add("unit-label-count-container"); - this.#unitCountDivider = document.createElement("div"); - this.#unitCountDivider.innerText = "x"; - unitLabelCountContainerEl.append(this.#unitLabelDropdown.getContainer(), this.#unitCountDivider, this.#unitCountDropdown.getContainer()); - - /* Create the unit image and loadout elements */ - this.#unitImageEl = document.createElement("img"); - this.#unitImageEl.classList.add("unit-image", "hide"); - this.#unitLoadoutPreviewEl = document.createElement("div"); - this.#unitLoadoutPreviewEl.classList.add("unit-loadout-preview"); - this.#unitLoadoutListEl = document.createElement("div"); - this.#unitLoadoutListEl.classList.add("unit-loadout-list"); - this.#unitLoadoutPreviewEl.append(this.#unitImageEl, this.#unitLoadoutListEl); - - /* Create the advanced options collapsible div */ - this.#advancedOptionsDiv = document.createElement("div"); - this.#advancedOptionsDiv.classList.add("contextmenu-advanced-options", "hide"); - this.#advancedOptionsToggle = document.createElement("div"); - this.#advancedOptionsToggle.classList.add("contextmenu-advanced-options-toggle"); - this.#advancedOptionsText = document.createElement("div"); - this.#advancedOptionsText.innerText = "Faction / Liveries / Skill Level"; - this.#advancedOptionsToggle.append(this.#advancedOptionsText); - this.#advancedOptionsToggle.addEventListener("click", () => { - this.#advancedOptionsToggle.classList.toggle("is-open"); - this.#advancedOptionsDiv.classList.toggle("hide"); - this.#container.dispatchEvent(new Event("resize")); - }); - this.#advancedOptionsDiv.append(this.#unitCountryDropdown.getContainer(), this.#unitLiveryDropdown.getContainer(), this.#unitSkillDropdown.getContainer()); - - /* Create the unit info collapsible div */ - this.#unitInfoDiv = document.createElement("div"); - this.#unitInfoDiv.classList.add("contextmenu-metadata", "hide"); - this.#unitInfoToggle = document.createElement("div"); - this.#unitInfoToggle.classList.add("contextmenu-metadata-toggle"); - this.#unitInfoText = document.createElement("div"); - this.#unitInfoText.innerText = "Unit information"; - this.#unitInfoToggle.append(this.#unitInfoText); - this.#unitInfoToggle.addEventListener("click", () => { - this.#unitInfoToggle.classList.toggle("is-open"); - this.#unitInfoDiv.classList.toggle("hide"); - this.#container.dispatchEvent(new Event("resize")); - }); - this.#descriptionDiv = document.createElement("div"); - this.#abilitiesDiv = document.createElement("div"); - this.#unitInfoDiv.append(this.#descriptionDiv, this.#abilitiesDiv); - - /* Create the unit deploy button */ - this.#deployUnitButtonEl = document.createElement("button"); - this.#deployUnitButtonEl.classList.add("deploy-unit-button"); - this.#deployUnitButtonEl.disabled = true; - this.#deployUnitButtonEl.innerText = "Deploy unit"; - this.#deployUnitButtonEl.setAttribute("data-coalition", "blue"); - this.#deployUnitButtonEl.addEventListener("click", () => { - this.deployUnits(this.spawnOptions, parseInt(this.#unitCountDropdown.getValue())); - }); - - /* Assemble all components */ - this.#container.append(this.#unitRoleTypeDropdown.getContainer(), unitLabelCountContainerEl, this.#unitLoadoutDropdown.getContainer(), this.#unitSpawnAltitudeSlider.getContainer() as HTMLElement, - this.#unitLoadoutPreviewEl, this.#advancedOptionsToggle, this.#advancedOptionsDiv, this.#unitInfoToggle, this.#unitInfoDiv, this.#deployUnitButtonEl); - - /* Load the country codes from the public folder */ - var xhr = new XMLHttpRequest(); - xhr.open('GET', 'images/countries/codes.json', true); - xhr.responseType = 'json'; - xhr.onload = () => { - var status = xhr.status; - if (status === 200) - this.#countryCodes = xhr.response; - else - console.error(`Error retrieving country codes`) - }; - xhr.send(); - - /* Create the range circle previews */ - this.#engagementCircle = new Circle(this.spawnOptions.latlng, { radius: 0, weight: 4, opacity: 0.8, fillOpacity: 0, dashArray: "4 8", interactive: false, bubblingMouseEvents: false }); - this.#acquisitionCircle = new Circle(this.spawnOptions.latlng, { radius: 0, weight: 2, opacity: 0.8, fillOpacity: 0, dashArray: "8 12", interactive: false, bubblingMouseEvents: false }); - - /* Event listeners */ - this.#container.addEventListener("unitRoleTypeChanged", () => { - /* Shown the unit label and the unit count dropdowns */ - this.#unitLabelDropdown.show(); - this.#unitCountDivider.classList.remove("hide"); - this.#unitCountDropdown.show(); - - /* Hide all the other components */ - this.#unitLoadoutDropdown.hide(); - this.#unitSkillDropdown.hide(); - this.#unitSpawnAltitudeSlider.hide(); - this.#unitLoadoutPreviewEl.classList.add("hide"); - this.#advancedOptionsDiv.classList.add("hide"); - this.#unitInfoDiv.classList.add("hide"); - this.#advancedOptionsText.classList.add("hide"); - this.#advancedOptionsToggle.classList.add("hide"); - this.#unitInfoText.classList.add("hide"); - this.#unitInfoToggle.classList.add("hide"); - - /* Disable the spawn button */ - this.#deployUnitButtonEl.disabled = true; - this.#unitLabelDropdown.reset(); - this.#unitLoadoutListEl.replaceChildren(); - this.#unitLoadoutDropdown.reset(); - this.#unitImageEl.classList.toggle("hide", true); - this.#unitLiveryDropdown.reset(); - - /* Populate the labels dropdown from the database */ - var blueprints: UnitBlueprint[] = []; - if (this.#orderByRole) - blueprints = this.#unitDatabase.getByRole(this.spawnOptions.roleType); - else - blueprints = this.#unitDatabase.getByType(this.spawnOptions.roleType); - - /* Presort the elements by name in case any have equal labels */ - blueprints = blueprints.sort((blueprintA: UnitBlueprint, blueprintB: UnitBlueprint) => { - if (blueprintA.name > blueprintA.name) - return 1; - else - return (blueprintB.name > blueprintA.name) ? -1 : 0; - }); - - this.#unitLabelDropdown.setOptions(blueprints.map((blueprint) => { return blueprint.name }), "string+number", blueprints.map((blueprint) => { return blueprint.label })); - - /* Add the tags to the options */ - var elements: HTMLElement[] = []; - for (let idx = 0; idx < this.#unitLabelDropdown.getOptionElements().length; idx++) { - let name = this.#unitLabelDropdown.getOptionsList()[idx]; - let element = this.#unitLabelDropdown.getOptionElements()[idx] as HTMLElement; - let entry = this.#unitDatabase.getByName(name); - if (entry && entry.tags?.trim() !== "") { - element.querySelectorAll("button")[0]?.append(...(entry.tags?.split(",").map((tag: string) => { - tag = tag.trim(); - let el = document.createElement("div"); - el.classList.add("pill", `ol-tag`, `ol-tag-${tag.replace(/[\W_]+/g,"-")}`); - el.textContent = tag; - element.appendChild(el); - return el; - }) ?? [])); - elements.push(element); - } - } - - /* Request resizing */ - this.#container.dispatchEvent(new Event("resize")); - - /* Reset the spawn options */ - this.spawnOptions.name = ""; - this.spawnOptions.loadout = undefined; - this.spawnOptions.skill = "Excellent"; - this.spawnOptions.liveryID = undefined; - - this.#computeSpawnPoints(); - }) - - this.#container.addEventListener("unitLabelChanged", () => { - /* If enabled, show the altitude slideer and loadouts section */ - if (this.#showAltitudeSlider) - this.#unitSpawnAltitudeSlider.show(); - - if (this.#showLoadout) { - this.#unitLoadoutDropdown.show(); - this.#unitLoadoutPreviewEl.classList.remove("hide"); - } - - if (this.#showSkill) { - this.#unitSkillDropdown.show(); - } - - /* Show the advanced options and unit info sections */ - this.#advancedOptionsText.classList.remove("hide"); - this.#advancedOptionsToggle.classList.remove("hide"); - this.#advancedOptionsToggle.classList.remove("is-open"); - this.#unitInfoText.classList.remove("hide"); - this.#unitInfoToggle.classList.remove("hide"); - this.#unitInfoToggle.classList.remove("is-open"); - - /* Enable the spawn button */ - this.#deployUnitButtonEl.disabled = false; - - /* If enabled, populate the loadout dropdown */ - if (!this.#unitLoadoutDropdown.isHidden()) { - this.#unitLoadoutDropdown.setOptions(this.#unitDatabase.getLoadoutNamesByRole(this.spawnOptions.name, this.spawnOptions.roleType)); - this.#unitLoadoutDropdown.selectValue(0); - } - - if (!this.#unitSkillDropdown.isHidden()) { - const sortedOptions = ["Average", "Good", "High", "Excellent"]; - this.#unitSkillDropdown.setOptions(sortedOptions, null); - this.#unitSkillDropdown.selectValue(4); - } - - - /* Get the unit data from the db */ - var blueprint = this.#unitDatabase.getByName(this.spawnOptions.name); - - /* Shown the unit silhouette */ - this.#unitImageEl.src = `images/units/${blueprint?.filename}`; - this.#unitImageEl.classList.toggle("hide", !(blueprint?.filename !== undefined && blueprint?.filename !== '')); - - /* Set the livery options */ - this.#setUnitLiveryOptions(); - - /* Populate the description and abilities sections */ - this.#descriptionDiv.replaceChildren(); - this.#abilitiesDiv.replaceChildren(); - - if (blueprint?.description) - this.#descriptionDiv.textContent = blueprint.description; - - if (blueprint?.abilities) { - var abilities = blueprint.abilities.split(","); - this.#abilitiesDiv.replaceChildren(); - for (let ability of abilities) { - if (ability !== "") { - ability = ability.trimStart(); - var div = document.createElement("div"); - div.textContent = ability.charAt(0).toUpperCase() + ability.slice(1); - this.#abilitiesDiv.append(div); - div.classList.add("pill-light"); - } - } - } - - /* Show the range circles */ - this.showCirclesPreviews(); - - /* Request resizing */ - this.#container.dispatchEvent(new Event("resize")); - this.#computeSpawnPoints(); - }) - - this.#container.addEventListener("unitLoadoutChanged", () => { - /* Update the loadout information */ - var items = this.spawnOptions.loadout?.items.map((item: any) => { return `${item.quantity}x ${item.name}`; }); - if (items != undefined) { - items.length == 0 ? items.push("Empty loadout") : ""; - this.#unitLoadoutListEl.replaceChildren( - ...items.map((item: any) => { - var div = document.createElement('div'); - div.innerText = item; - return div; - }) - ); - } - - this.#container.dispatchEvent(new Event("resize")); - }) - - this.#container.addEventListener("unitSkillChanged", () => { - - }) - - this.#container.addEventListener("unitCountChanged", () => { - /* Recompute the spawn points */ - this.#computeSpawnPoints(); - }) - - this.#container.addEventListener("unitCountryChanged", () => { - /* Get the unit liveries by country */ - this.#setUnitLiveryOptions(); - }) - - this.#container.addEventListener("unitLiveryChanged", () => { - - }) - - document.addEventListener('activeCoalitionChanged', () => { - /* If the coalition changed, update the circle previews to set the colours */ - this.showCirclesPreviews(); - }); - } - - abstract deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number): void; - - getContainer() { - return this.#container; - } - - getVisible() { - return !this.getContainer().classList.contains( "hide" ); - } - - reset() { - /* Disable the spawn button */ - this.#deployUnitButtonEl.disabled = true; - - /* Reset all the dropdowns */ - this.#unitRoleTypeDropdown.reset(); - this.#unitLabelDropdown.reset(); - this.#unitLiveryDropdown.reset(); - if (this.#orderByRole) - this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getRoles()); - else - this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getTypes(this.unitTypeFilter)); - - /* Reset the contents of the div elements */ - this.#unitLoadoutListEl.replaceChildren(); - this.#unitLoadoutDropdown.reset(); - this.#unitImageEl.classList.toggle("hide", true); - this.#descriptionDiv.replaceChildren(); - this.#abilitiesDiv.replaceChildren(); - - /* Hide everything but the unit type dropdown */ - this.#unitLabelDropdown.hide(); - this.#unitCountDivider.classList.add("hide"); - this.#unitCountDropdown.hide(); - this.#unitLoadoutDropdown.hide(); - this.#unitSkillDropdown.hide(); - this.#unitSpawnAltitudeSlider.hide(); - this.#unitLoadoutPreviewEl.classList.add("hide"); - this.#advancedOptionsDiv.classList.add("hide"); - this.#unitInfoDiv.classList.add("hide"); - this.#advancedOptionsText.classList.add("hide"); - this.#advancedOptionsToggle.classList.add("hide"); - this.#unitInfoText.classList.add("hide"); - this.#unitInfoToggle.classList.add("hide"); - - /* Get the countries and clear the circle previews */ - this.setCountries(); - this.clearCirclesPreviews(); - - /* Request resizing */ - this.#container.dispatchEvent(new Event("resize")); - } - - setCountries() { - /* Create the countries dropdown elements (with the little flags) */ - var coalitions = getApp().getMissionManager().getCoalitions(); - var countries = Object.values(coalitions[getApp().getActiveCoalition() as keyof typeof coalitions]); - this.#unitCountryDropdown.setOptionsElements(this.#createCountryButtons(this.#unitCountryDropdown, countries, (country: string) => { this.#setUnitCountry(country) })); - - if (countries.length > 0 && !countries.includes(this.spawnOptions.country)) { - this.#unitCountryDropdown.forceValue(this.#getFormattedCountry(countries[0])); - this.#setUnitCountry(countries[0]); - } - } - - showCirclesPreviews() { - this.clearCirclesPreviews(); - - if ( !this.showRangeCircles || this.spawnOptions.name === "" || !this.getVisible() ) { - return; - } - - let acquisitionRange = this.#unitDatabase.getByName(this.spawnOptions.name)?.acquisitionRange ?? 0; - let engagementRange = this.#unitDatabase.getByName(this.spawnOptions.name)?.engagementRange ?? 0; - - if ( acquisitionRange === 0 && engagementRange === 0 ) { - return; - } - - this.#acquisitionCircle.setRadius(acquisitionRange); - this.#engagementCircle.setRadius(engagementRange); - this.#acquisitionCircle.setLatLng(this.spawnOptions.latlng); - this.#engagementCircle.setLatLng(this.spawnOptions.latlng); - - switch (getApp().getActiveCoalition()) { - case "red": - this.#acquisitionCircle.options.color = "#D42121"; - break; - case "blue": - this.#acquisitionCircle.options.color = "#017DC1"; - break; - default: - this.#acquisitionCircle.options.color = "#111111" - break; - } - - switch (getApp().getActiveCoalition()) { - case "red": - this.#engagementCircle.options.color = "#FF5858"; - break; - case "blue": - this.#engagementCircle.options.color = "#3BB9FF"; - break; - default: - this.#engagementCircle.options.color = "#CFD9E8" - break; - } - - if (engagementRange > 0) - this.#engagementCircle.addTo(getApp().getMap()); - - if (acquisitionRange > 0) - this.#acquisitionCircle.addTo(getApp().getMap()); - } - - clearCirclesPreviews() { - this.#engagementCircle.removeFrom(getApp().getMap()); - this.#acquisitionCircle.removeFrom(getApp().getMap()); - } - - setAirbase(airbase: Airbase | undefined) { - this.spawnOptions.airbase = airbase; - } - - setLatLng(latlng: LatLng) { - this.spawnOptions.latlng = latlng; - - this.showCirclesPreviews(); - } - - setMaxUnitCount(maxUnitCount: number) { - /* Create the unit count options */ - this.#unitCountDropdown.setOptions( [...Array(maxUnitCount).keys()].map( n => (n+1).toString() ), "number"); - this.#unitCountDropdown.selectValue(0); - } - - getRoleTypeDrodown() { - return this.#unitRoleTypeDropdown; - } - - getLabelDropdown() { - return this.#unitLabelDropdown; - } - - getCountDropdown() { - return this.#unitCountDropdown; - } - - getLoadoutDropdown() { - return this.#unitLoadoutDropdown; - } - - getSkillDropdown() { - return this.#unitSkillDropdown; - } - - getCountryDropdown() { - return this.#unitCountDropdown; - } - - getLiveryDropdown() { - return this.#unitLiveryDropdown; - } - - getLoadoutPreview() { - return this.#unitLoadoutPreviewEl; - } - - getAltitudeSlider() { - return this.#unitSpawnAltitudeSlider; - } - - setShowLoadout(showLoadout: boolean) { - this.#showLoadout = showLoadout; - } - - setShowSkill(showSkill: boolean) { - this.#showSkill = showSkill; - } - - setShowAltitudeSlider(showAltitudeSlider: boolean) { - this.#showAltitudeSlider = showAltitudeSlider; - } - - #setUnitRoleType(roleType: string) { - this.spawnOptions.roleType = roleType; - this.#container.dispatchEvent(new Event("unitRoleTypeChanged")); - } - - #setUnitName(name: string) { - if (name != null) - this.spawnOptions.name = name; - this.#container.dispatchEvent(new Event("unitLabelChanged")); - } - - #setUnitLoadout(loadoutName: string) { - var loadout = this.#unitDatabase.getLoadoutByName(this.spawnOptions.name, loadoutName); - if (loadout) - this.spawnOptions.loadout = loadout; - this.#container.dispatchEvent(new Event("unitLoadoutChanged")); - } - - #setUnitSkill(skill: string) { - this.spawnOptions.skill = skill; - this.#container.dispatchEvent(new Event("unitSkillChanged")); - } - - #setUnitCount(count: string) { - this.spawnOptions.count = parseInt(count); - this.#container.dispatchEvent(new Event("unitCountChanged")); - } - - #setUnitCountry(country: string) { - this.spawnOptions.country = country; - this.#container.dispatchEvent(new Event("unitCountryChanged")); - } - - #setUnitLivery(liveryName: string) { - var liveries = this.#unitDatabase.getByName(this.spawnOptions.name)?.liveries; - if (liveryName === "Default Livery") { - this.spawnOptions.liveryID = ""; - } - else { - if (liveries !== undefined) { - for (let liveryID in liveries) - if (liveries[liveryID].name === liveryName) - this.spawnOptions.liveryID = liveryID; - } - } - this.#container.dispatchEvent(new Event("unitLiveryChanged")); - } - - #setUnitLiveryOptions() { - if (this.spawnOptions.name !== "" && this.spawnOptions.country !== "") { - var liveries = this.#unitDatabase.getLiveryNamesByName(this.spawnOptions.name); - var countryLiveries: string[] = ["Default Livery"]; - liveries.forEach((livery: any) => { - var nationLiveryCodes = this.#countryCodes[this.spawnOptions.country].liveryCodes; - if (livery.countries === "All" || livery.countries.some((country: string) => { return nationLiveryCodes.includes(country) })) - countryLiveries.push(livery.name); - }); - this.#unitLiveryDropdown.setOptions(countryLiveries); - this.#unitLiveryDropdown.selectValue(0); - } - } - - #createCountryButtons(parent: Dropdown, countries: string[], callback: CallableFunction) { - return Object.values(countries).map((country: string) => { - var el = document.createElement("div"); - - var button = document.createElement("button"); - button.classList.add("country-dropdown-element"); - el.appendChild(button); - button.addEventListener("click", () => { - callback(country); - parent.forceValue(this.#getFormattedCountry(country)); - parent.close(); - }); - if (this.#countryCodes[country] !== undefined) { - var code = this.#countryCodes[country].flagCode; - if (code !== undefined) { - var img = document.createElement("img"); - img.src = `images/countries/${code.toLowerCase()}.svg`; - button.appendChild(img); - } - } - else { - console.log("Unknown country " + country); - } - var text = document.createElement("div"); - text.innerText = this.#getFormattedCountry(country); - button.appendChild(text); - return el; - }); - } - - #getFormattedCountry(country: string) { - var formattedCountry = ""; - if (this.#countryCodes[country] !== undefined && this.#countryCodes[country].displayName !== undefined) - formattedCountry = this.#countryCodes[country].displayName; - else - formattedCountry = country.charAt(0).toUpperCase() + country.slice(1).toLowerCase(); - return formattedCountry; - } - - #computeSpawnPoints() { - if (getApp().getMissionManager() && getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER) { - var unitCount = parseInt(this.#unitCountDropdown.getValue()); - var unitSpawnPoints = unitCount * this.#unitDatabase.getSpawnPointsByLabel(this.#unitLabelDropdown.getValue()); - this.#deployUnitButtonEl.dataset.points = `${unitSpawnPoints}`; - this.#deployUnitButtonEl.disabled = unitSpawnPoints >= getApp().getMissionManager().getAvailableSpawnPoints(); - } - } -} - -export class AircraftSpawnMenu extends UnitSpawnMenu { - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID, aircraftDatabase, true); - this.setMaxUnitCount(4); - this.getAltitudeSlider().setMinMax(0, 50000); - this.getAltitudeSlider().setIncrement(500); - this.getAltitudeSlider().setValue(20000); - this.spawnOptions.altitude = ftToM(20000); - } - - deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) { - spawnOptions.coalition = getApp().getActiveCoalition(); - if (spawnOptions) { - var unitTable: UnitSpawnTable = { - unitType: spawnOptions.name, - location: spawnOptions.latlng, - altitude: spawnOptions.altitude ? spawnOptions.altitude : 0, - loadout: spawnOptions.loadout ? spawnOptions.loadout.name : "", - liveryID: spawnOptions.liveryID ? spawnOptions.liveryID : "", - skill: spawnOptions.skill ? spawnOptions.skill : "Excellent" // Default to "Excellent" if skill is not set - }; - var units = []; - for (let i = 1; i <= unitsCount; i++) { - units.push(unitTable); - } - - getApp().getUnitsManager().spawnUnits("Aircraft", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { - if (res.commandHash !== undefined) - getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash); - }); - - this.getContainer().dispatchEvent(new Event("hide")); - } - } - -} - -export class HelicopterSpawnMenu extends UnitSpawnMenu { - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID, helicopterDatabase, true); - this.setMaxUnitCount(4); - this.getAltitudeSlider().setMinMax(0, 10000); - this.getAltitudeSlider().setIncrement(100); - this.getAltitudeSlider().setValue(5000); - this.spawnOptions.altitude = ftToM(5000); - } - - deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) { - spawnOptions.coalition = getApp().getActiveCoalition(); - if (spawnOptions) { - var unitTable: UnitSpawnTable = { - unitType: spawnOptions.name, - location: spawnOptions.latlng, - altitude: spawnOptions.altitude? spawnOptions.altitude: 0, - loadout: spawnOptions.loadout? spawnOptions.loadout.name: "", - liveryID: spawnOptions.liveryID? spawnOptions.liveryID: "", - skill: spawnOptions.skill ? spawnOptions.skill : "Excellent" // Default to "Excellent" if skill is not set - }; - var units = []; - for (let i = 1; i < unitsCount + 1; i++) { - units.push(unitTable); - } - - getApp().getUnitsManager().spawnUnits("Helicopter", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { - if (res.commandHash !== undefined) - getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash); - }); - - this.getContainer().dispatchEvent(new Event("hide")); - } - } -} - -export class GroundUnitSpawnMenu extends UnitSpawnMenu { - protected showRangeCircles: boolean = true; - protected unitTypeFilter = (unit:any) => {return !(GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.type))}; - - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID, groundUnitDatabase, false); - this.setMaxUnitCount(20); - this.setShowAltitudeSlider(false); - this.setShowLoadout(false); - this.getLoadoutPreview().classList.add("hide"); - } - - deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) { - spawnOptions.coalition = getApp().getActiveCoalition(); - if (spawnOptions) { - var unitTable: UnitSpawnTable = { - unitType: spawnOptions.name, - location: spawnOptions.latlng, - liveryID: spawnOptions.liveryID? spawnOptions.liveryID: "", - skill: spawnOptions.skill ? spawnOptions.skill : "High" - }; - - var units = []; - let initialLat = unitTable.location.lat; - let initialLng = unitTable.location.lng; - let rows = Math.floor(Math.sqrt(unitsCount)) - for (let i = 0; i < unitsCount; i++) { - unitTable.location.lat = initialLat + i % rows * 0.0001; - unitTable.location.lng = initialLng + Math.floor(i / rows) * 0.0001; - units.push(JSON.parse(JSON.stringify(unitTable))); - } - - getApp().getUnitsManager().spawnUnits("GroundUnit", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { - if (res.commandHash !== undefined) - getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash); - }); - - this.getContainer().dispatchEvent(new Event("hide")); - } - } -} - -export class AirDefenceUnitSpawnMenu extends GroundUnitSpawnMenu { - protected unitTypeFilter = (unit:any) => {return GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.type)}; - - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID); - this.setMaxUnitCount(4); - } -} - -export class NavyUnitSpawnMenu extends UnitSpawnMenu { - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID, navyUnitDatabase, false); - this.setMaxUnitCount(4); - this.setShowAltitudeSlider(false); - this.setShowLoadout(false); - } - - deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) { - spawnOptions.coalition = getApp().getActiveCoalition(); - if (spawnOptions) { - var unitTable: UnitSpawnTable = { - unitType: spawnOptions.name, - location: spawnOptions.latlng, - liveryID: spawnOptions.liveryID? spawnOptions.liveryID: "", - skill: spawnOptions.skill ? spawnOptions.skill : "High" - }; - - var units = []; - let initialLat = unitTable.location.lat; - let initialLng = unitTable.location.lng; - let rows = Math.floor(Math.sqrt(unitsCount)) - for (let i = 0; i < unitsCount; i++) { - unitTable.location.lat = initialLat + i % rows * 0.005; - unitTable.location.lng = initialLng + Math.floor(i / rows) * 0.005; - units.push(JSON.parse(JSON.stringify(unitTable))); - } - - getApp().getUnitsManager().spawnUnits("NavyUnit", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { - if (res.commandHash !== undefined) - getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash); - }); - - this.getContainer().dispatchEvent(new Event("hide")); - } - } -} \ No newline at end of file diff --git a/frontend/website/src/dialog/dialog.ts b/frontend/website/src/dialog/dialog.ts deleted file mode 100644 index 78b4cf94..00000000 --- a/frontend/website/src/dialog/dialog.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Panel } from "../panels/panel"; - -export class Dialog extends Panel { - - constructor( element:string ) { - super( element ); - } - - hide() { - super.hide(); - document.getElementById( "gray-out" )?.classList.toggle("hide", true); - } - - show() { - super.show(); - document.getElementById( "gray-out" )?.classList.toggle("hide", false); - } - -} \ No newline at end of file diff --git a/frontend/website/src/dom.d.ts b/frontend/website/src/dom.d.ts deleted file mode 100644 index 0508b6b2..00000000 --- a/frontend/website/src/dom.d.ts +++ /dev/null @@ -1,37 +0,0 @@ -interface CustomEventMap { - "unitSelection": CustomEvent, - "unitDeselection": CustomEvent, - "unitsSelection": CustomEvent, - "unitsDeselection": CustomEvent, - "clearSelection": CustomEvent<>, - "unitCreation": CustomEvent, - "unitDeletion": CustomEvent, - "unitDeath": CustomEvent, - "unitUpdated": CustomEvent, - "unitMoveCommand": CustomEvent, - "unitAttackCommand": CustomEvent, - "unitLandCommand": CustomEvent, - "unitSetAltitudeCommand": CustomEvent, - "unitSetSpeedCommand": CustomEvent, - "unitSetOption": CustomEvent, - "groupCreation": CustomEvent, - "groupDeletion": CustomEvent, - "mapStateChanged": CustomEvent, - "mapContextMenu": CustomEvent<>, - "mapOptionsChanged": CustomEvent<>, - "commandModeOptionsChanged": CustomEvent<>, - "contactsUpdated": CustomEvent, - "activeCoalitionChanged": CustomEvent<> -} - -declare global { - interface Document { - addEventListener(type: K, - listener: (this: Document, ev: CustomEventMap[K]) => void): void; - dispatchEvent(ev: CustomEventMap[K]): void; - } - - function getOlympusPlugin(): OlympusPlugin; -} - -export { }; diff --git a/frontend/website/src/index.ts b/frontend/website/src/index.ts deleted file mode 100644 index 6e76a0c2..00000000 --- a/frontend/website/src/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { OlympusApp } from "./olympusapp"; - -var app: OlympusApp; - -function setup() { - app = new OlympusApp(); - app.start(); -} - -export function getApp() { - return app; -} - -window.onload = setup; \ No newline at end of file diff --git a/frontend/website/src/interfaces.ts b/frontend/website/src/interfaces.ts deleted file mode 100644 index ef60bd07..00000000 --- a/frontend/website/src/interfaces.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { LatLng } from "leaflet"; -import { OlympusApp } from "./olympusapp"; -import { Airbase } from "./mission/airbase"; - -export interface OlympusPlugin { - getName: () => string; - initialize: (app: OlympusApp) => boolean; -} - -declare global { - function getOlympusPlugin(): OlympusPlugin; -} - -export interface ContextMenuOption { - tooltip: string; - src: string; - callback: CallableFunction; -} - -export interface AirbasesData { - airbases: { [key: string]: any }, - sessionHash: string; - time: number; -} - -export interface BullseyesData { - bullseyes: { [key: string]: { latitude: number, longitude: number, coalition: string } }, - sessionHash: string; - time: number; -} - -export interface MissionData { - mission: { - theatre: string, - dateAndTime: DateAndTime; - commandModeOptions: CommandModeOptions; - coalitions: { red: string[], blue: string[] }; - } - time: number; - sessionHash: string; -} - -export interface CommandModeOptions { - commandMode: string; - restrictSpawns: boolean; - restrictToCoalition: boolean; - setupTime: number; - spawnPoints: { - red: number, - blue: number - }, - eras: string[] -} - -export interface DateAndTime { - date: { Year: number, Month: number, Day: number }; - time: { h: number, m: number, s: number }; - elapsedTime: number; - startTime: number; -} - -export interface LogData { - logs: { [key: string]: string }, - sessionHash: string; - time: number; -} - -export interface ServerRequestOptions { - time?: number; - commandHash?: string; -} - -export interface UnitSpawnTable { - unitType: string, - location: LatLng, - altitude?: number, - loadout?: string, - skill: string, - liveryID: string -} - -export interface ObjectIconOptions { - showState: boolean, - showVvi: boolean, - showHealth: boolean, - showHotgroup: boolean, - showUnitIcon: boolean, - showShortLabel: boolean, - showFuel: boolean, - showAmmo: boolean, - showSummary: boolean, - showCallsign: boolean, - rotateToHeading: boolean -} - -export interface GeneralSettings { - prohibitJettison: boolean; - prohibitAA: boolean; - prohibitAG: boolean; - prohibitAfterburner: boolean; - prohibitAirWpn: boolean; -} - -export interface TACAN { - isOn: boolean; - channel: number; - XY: string; - callsign: string; -} - -export interface Radio { - frequency: number; - callsign: number; - callsignNumber: number; -} - -export interface Ammo { - quantity: number, - name: string, - guidance: number, - category: number, - missileCategory: number -} - -export interface Contact { - ID: number, - detectionMethod: number -} - -export interface Offset { - x: number, - y: number, - z: number -} - -export interface UnitData { - category: string, - categoryDisplayName: string, - ID: number; - alive: boolean; - human: boolean; - controlled: boolean; - coalition: string; - country: number; - name: string; - unitName: string; - groupName: string; - state: string; - task: string; - hasTask: boolean; - position: LatLng; - speed: number; - horizontalVelocity: number; - verticalVelocity: number; - heading: number; - track: number; - isActiveTanker: boolean; - isActiveAWACS: boolean; - onOff: boolean; - followRoads: boolean; - fuel: number; - desiredSpeed: number; - desiredSpeedType: string; - desiredAltitude: number; - desiredAltitudeType: string; - leaderID: number; - formationOffset: Offset; - targetID: number; - targetPosition: LatLng; - ROE: string; - reactionToThreat: string; - emissionsCountermeasures: string; - TACAN: TACAN; - radio: Radio; - generalSettings: GeneralSettings; - ammo: Ammo[]; - contacts: Contact[]; - activePath: LatLng[]; - isLeader: boolean; - operateAs: string; - shotsScatter: number; - shotsIntensity: number; - health: number; -} - -export interface LoadoutItemBlueprint { - name: string; - quantity: number; - effectiveAgainst?: string; -} - -export interface LoadoutBlueprint { - fuel: number; - items: LoadoutItemBlueprint[]; - roles: string[]; - code: string; - name: string; - enabled: boolean; -} - -export interface UnitBlueprint { - name: string; - enabled: boolean; - coalition: string; - era: string; - label: string; - shortLabel: string; - type?: string; - loadouts?: LoadoutBlueprint[]; - filename?: string; - liveries?: { [key: string]: { name: string, countries: string[] } }; - cost?: number; - barrelHeight?: number; - muzzleVelocity?: number; - aimTime?: number; - shotsToFire?: number; - shotsBaseInterval?: number; - shotsBaseScatter?: number; - description?: string; - abilities?: string; - tags?: string; - acquisitionRange?: number; - engagementRange?: number; - targetingRange?: number; - aimMethodRange?: number; - alertnessTimeConstant?: number; - canTargetPoint?: boolean; - canRearm?: boolean; - canAAA?: boolean; - indirectFire?: boolean; - markerFile?: string; - unitWhenGrouped?: string; -} - -export interface UnitSpawnOptions { - roleType: string; - name: string; - latlng: LatLng; - coalition: string; - count: number; - country: string; - skill: string; - loadout: LoadoutBlueprint | undefined; - airbase: Airbase | undefined; - liveryID: string | undefined; - altitude: number | undefined; -} - -export interface AirbaseOptions { - name: string, - position: L.LatLng -} - -export interface AirbaseChartData { - elevation: string, - ICAO: string, - TACAN: string, - runways: AirbaseChartRunwayData[] -} - -export interface AirbaseChartRunwayHeadingData { - [index: string]: { - magHeading: string, - ILS: string - } -} - -export interface AirbaseChartRunwayData { - headings: AirbaseChartRunwayHeadingData[], - length: string -} - -export interface Listener { - callback: CallableFunction; - name?: string -} - -export interface ShortcutOptions { - altKey?: boolean; - callback: CallableFunction; - context?: string; - ctrlKey?: boolean; - name?: string; - shiftKey?: boolean; -} - -export interface ShortcutKeyboardOptions extends ShortcutOptions { - code: string; - event?: "keydown" | "keyup"; -} - -export interface ShortcutMouseOptions extends ShortcutOptions { - button: number; - event: "mousedown" | "mouseup"; -} - -export interface Manager { - add: CallableFunction; -} \ No newline at end of file diff --git a/frontend/website/src/map/boxselect.ts b/frontend/website/src/map/boxselect.ts deleted file mode 100644 index a37f4ace..00000000 --- a/frontend/website/src/map/boxselect.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { Map } from 'leaflet'; -import { Handler } from 'leaflet'; -import { Util } from 'leaflet'; -import { DomUtil } from 'leaflet'; -import { DomEvent } from 'leaflet'; -import { LatLngBounds } from 'leaflet'; -import { Bounds } from 'leaflet'; - -export var BoxSelect = Handler.extend({ - initialize: function (map: Map) { - this._map = map; - this._container = map.getContainer(); - this._pane = map.getPanes().overlayPane; - this._resetStateTimeout = 0; - map.on('unload', this._destroy, this); - }, - - addHooks: function () { - DomEvent.on(this._container, 'mousedown', this._onMouseDown, this); - }, - - removeHooks: function () { - DomEvent.off(this._container, 'mousedown', this._onMouseDown, this); - }, - - moved: function () { - return this._moved; - }, - - _destroy: function () { - DomUtil.remove(this._pane); - delete this._pane; - }, - - _resetState: function () { - this._resetStateTimeout = 0; - this._moved = false; - }, - - _clearDeferredResetState: function () { - if (this._resetStateTimeout !== 0) { - clearTimeout(this._resetStateTimeout); - this._resetStateTimeout = 0; - } - }, - - _onMouseDown: function (e: any) { - if ((e.which == 1 && e.button == 0 && e.shiftKey)) { - this._map.fire('selectionstart'); - // Clear the deferred resetState if it hasn't executed yet, otherwise it - // will interrupt the interaction and orphan a box element in the container. - this._clearDeferredResetState(); - this._resetState(); - - DomUtil.disableTextSelection(); - DomUtil.disableImageDrag(); - - this._startPoint = this._map.mouseEventToContainerPoint(e); - - //@ts-ignore - DomEvent.on(document, { - contextmenu: DomEvent.stop, - mousemove: this._onMouseMove, - mouseup: this._onMouseUp, - keydown: this._onKeyDown - }, this); - } else { - return false; - } - }, - - _onMouseMove: function (e: any) { - if (!this._moved) { - this._moved = true; - - this._box = DomUtil.create('div', 'leaflet-zoom-box', this._container); - DomUtil.addClass(this._container, 'leaflet-crosshair'); - - this._map.fire('boxzoomstart'); - } - - this._point = this._map.mouseEventToContainerPoint(e); - - var bounds = new Bounds(this._point, this._startPoint), - size = bounds.getSize(); - - if (bounds.min != undefined) - DomUtil.setPosition(this._box, bounds.min); - - this._box.style.width = size.x + 'px'; - this._box.style.height = size.y + 'px'; - }, - - _finish: function () { - if (this._moved) { - DomUtil.remove(this._box); - DomUtil.removeClass(this._container, 'leaflet-crosshair'); - } - - DomUtil.enableTextSelection(); - DomUtil.enableImageDrag(); - - //@ts-ignore - DomEvent.off(document, { - contextmenu: DomEvent.stop, - mousemove: this._onMouseMove, - mouseup: this._onMouseUp, - keydown: this._onKeyDown - }, this); - }, - - _onMouseUp: function (e: any) { - if ((e.which !== 1) && (e.button !== 0)) { return; } - - this._finish(); - - if (!this._moved) { return; } - // Postpone to next JS tick so internal click event handling - // still see it as "moved". - window.setTimeout(Util.bind(this._resetState, this), 0); - var bounds = new LatLngBounds( - this._map.containerPointToLatLng(this._startPoint), - this._map.containerPointToLatLng(this._point)); - - this._map.fire('selectionend', { selectionBounds: bounds }); - }, - - _onKeyDown: function (e: any) { - if (e.keyCode === 27) { - this._finish(); - this._clearDeferredResetState(); - this._resetState(); - } - } -}); - diff --git a/frontend/website/src/map/clickableminimap.ts b/frontend/website/src/map/clickableminimap.ts deleted file mode 100644 index 00104f7e..00000000 --- a/frontend/website/src/map/clickableminimap.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { MiniMap, MiniMapOptions } from "leaflet-control-mini-map"; - -export class ClickableMiniMap extends MiniMap { - constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) { - super(layer, options); - } - - getMap() { - //@ts-ignore needed to access not exported member. A bit of a hack, required to access click events //TODO: fix me - return this._miniMap; - } -} \ No newline at end of file diff --git a/frontend/website/src/map/coalitionarea/coalitionarea.ts b/frontend/website/src/map/coalitionarea/coalitionarea.ts deleted file mode 100644 index e5e71ae0..00000000 --- a/frontend/website/src/map/coalitionarea/coalitionarea.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet"; -import { getApp } from "../.."; -import { CoalitionAreaHandle } from "./coalitionareahandle"; -import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle"; -import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants"; - -export class CoalitionArea extends Polygon { - #coalition: string = "blue"; - #selected: boolean = true; - #editing: boolean = true; - #handles: CoalitionAreaHandle[] = []; - #middleHandles: CoalitionAreaMiddleHandle[] = []; - #activeIndex: number = 0; - - constructor(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][], options?: PolylineOptions) { - if (options === undefined) - options = {}; - - options.bubblingMouseEvents = false; - options.interactive = false; - - super(latlngs, options); - this.#setColors(); - this.#registerCallbacks(); - - if ([BLUE_COMMANDER, RED_COMMANDER].includes(getApp().getMissionManager().getCommandModeOptions().commandMode)) - this.setCoalition(getApp().getMissionManager().getCommandedCoalition()); - } - - setCoalition(coalition: string) { - this.#coalition = coalition; - this.#setColors(); - } - - getCoalition() { - return this.#coalition; - } - - setSelected(selected: boolean) { - this.#selected = selected; - this.#setColors(); - this.#setHandles(); - this.setOpacity(selected? 1: 0.5); - if (!this.getSelected() && this.getEditing()) { - /* Remove the vertex we were working on */ - var latlngs = this.getLatLngs()[0] as LatLng[]; - latlngs.splice(this.#activeIndex, 1); - this.setLatLngs(latlngs); - this.setEditing(false); - } - } - - getSelected() { - return this.#selected; - } - - setEditing(editing: boolean) { - this.#editing = editing; - this.#setHandles(); - var latlngs = this.getLatLngs()[0] as LatLng[]; - - /* Remove areas with less than 2 vertexes */ - if (latlngs.length <= 2) - getApp().getMap().deleteCoalitionArea(this); - } - - getEditing() { - return this.#editing; - } - - addTemporaryLatLng(latlng: LatLng) { - this.#activeIndex++; - var latlngs = this.getLatLngs()[0] as LatLng[]; - latlngs.splice(this.#activeIndex, 0, latlng); - this.setLatLngs(latlngs); - this.#setHandles(); - } - - moveActiveVertex(latlng: LatLng) { - var latlngs = this.getLatLngs()[0] as LatLng[]; - latlngs[this.#activeIndex] = latlng; - this.setLatLngs(latlngs); - this.#setHandles(); - } - - setOpacity(opacity: number) { - this.setStyle({opacity: opacity, fillOpacity: opacity * 0.25}); - } - - onRemove(map: Map): this { - super.onRemove(map); - this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getApp().getMap())); - return this; - } - - #setColors() { - const coalitionColor = this.getCoalition() === "blue" ? "#247be2" : "#ff5858"; - this.setStyle({ color: this.getSelected() ? "white" : coalitionColor, fillColor: coalitionColor }); - } - - #setHandles() { - this.#handles.forEach((handle: CoalitionAreaHandle) => handle.removeFrom(getApp().getMap())); - this.#handles = []; - if (this.getSelected()) { - var latlngs = this.getLatLngs()[0] as LatLng[]; - latlngs.forEach((latlng: LatLng, idx: number) => { - /* Add the polygon vertex handle (for moving the vertex) */ - const handle = new CoalitionAreaHandle(latlng); - handle.addTo(getApp().getMap()); - handle.on("drag", (e: any) => { - var latlngs = this.getLatLngs()[0] as LatLng[]; - latlngs[idx] = e.target.getLatLng(); - this.setLatLngs(latlngs); - this.#setMiddleHandles(); - }); - this.#handles.push(handle); - }); - } - this.#setMiddleHandles(); - } - - #setMiddleHandles() { - this.#middleHandles.forEach((handle: CoalitionAreaMiddleHandle) => handle.removeFrom(getApp().getMap())); - this.#middleHandles = []; - var latlngs = this.getLatLngs()[0] as LatLng[]; - if (this.getSelected() && latlngs.length >= 2) { - var lastLatLng: LatLng | null = null; - latlngs.concat([latlngs[0]]).forEach((latlng: LatLng, idx: number) => { - /* Add the polygon middle point handle (for adding new vertexes) */ - if (lastLatLng != null) { - const handle1Point = getApp().getMap().latLngToLayerPoint(latlng); - const handle2Point = getApp().getMap().latLngToLayerPoint(lastLatLng); - const middlePoint = new Point((handle1Point.x + handle2Point.x) / 2, (handle1Point.y + handle2Point.y) / 2); - const middleLatLng = getApp().getMap().layerPointToLatLng(middlePoint); - - const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng); - middleHandle.addTo(getApp().getMap()); - middleHandle.on("click", (e: any) => { - this.#activeIndex = idx - 1; - this.addTemporaryLatLng(middleLatLng); - }); - this.#middleHandles.push(middleHandle); - } - lastLatLng = latlng; - }); - } - } - - #registerCallbacks() { - this.on("click", (e: any) => { - getApp().getMap().deselectAllCoalitionAreas(); - if (!this.getSelected()) { - this.setSelected(true); - } - }); - - this.on("contextmenu", (e: any) => { - if (!this.getEditing()) { - getApp().getMap().deselectAllCoalitionAreas(); - this.setSelected(true); - } - else - this.setEditing(false); - }); - } -} \ No newline at end of file diff --git a/frontend/website/src/map/coalitionarea/coalitionareahandle.ts b/frontend/website/src/map/coalitionarea/coalitionareahandle.ts deleted file mode 100644 index 26421c85..00000000 --- a/frontend/website/src/map/coalitionarea/coalitionareahandle.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { DivIcon, LatLng } from "leaflet"; -import { CustomMarker } from "../markers/custommarker"; - -export class CoalitionAreaHandle extends CustomMarker { - constructor(latlng: LatLng) { - super(latlng, {interactive: true, draggable: true}); - } - - createIcon() { - this.setIcon(new DivIcon({ - iconSize: [24, 24], - iconAnchor: [12, 12], - className: "leaflet-coalitionarea-handle-marker", - })); - var el = document.createElement("div"); - el.classList.add("ol-coalitionarea-handle-icon"); - this.getElement()?.appendChild(el); - } -} \ No newline at end of file diff --git a/frontend/website/src/map/coalitionarea/coalitionareamiddlehandle.ts b/frontend/website/src/map/coalitionarea/coalitionareamiddlehandle.ts deleted file mode 100644 index 06cd4a57..00000000 --- a/frontend/website/src/map/coalitionarea/coalitionareamiddlehandle.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { DivIcon, LatLng } from "leaflet"; -import { CustomMarker } from "../markers/custommarker"; - -export class CoalitionAreaMiddleHandle extends CustomMarker { - constructor(latlng: LatLng) { - super(latlng, {interactive: true, draggable: false}); - } - - createIcon() { - this.setIcon(new DivIcon({ - iconSize: [16, 16], - iconAnchor: [8, 8], - className: "leaflet-coalitionarea-middle-handle-marker", - })); - var el = document.createElement("div"); - el.classList.add("ol-coalitionarea-middle-handle-icon"); - this.getElement()?.appendChild(el); - } -} \ No newline at end of file diff --git a/frontend/website/src/map/coalitionarea/drawingcursor.ts b/frontend/website/src/map/coalitionarea/drawingcursor.ts deleted file mode 100644 index e4497952..00000000 --- a/frontend/website/src/map/coalitionarea/drawingcursor.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { DivIcon, LatLng } from "leaflet"; -import { CustomMarker } from "../markers/custommarker"; - -export class DrawingCursor extends CustomMarker { - constructor() { - super(new LatLng(0, 0), {interactive: false}) - this.setZIndexOffset(9999); - } - - createIcon() { - this.setIcon(new DivIcon({ - iconSize: [24, 24], - iconAnchor: [0, 24], - className: "leaflet-draw-marker", - })); - var el = document.createElement("div"); - el.classList.add("ol-draw-icon"); - this.getElement()?.appendChild(el); - } -} \ No newline at end of file diff --git a/frontend/website/src/map/dcslayer.ts b/frontend/website/src/map/dcslayer.ts deleted file mode 100644 index 6b59b48f..00000000 --- a/frontend/website/src/map/dcslayer.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as L from "leaflet" - -export class DCSLayer extends L.TileLayer { - createTile(coords: L.Coords, done: L.DoneCallback) { - let newDone = (error?: Error, tile?: HTMLElement) => { - if (error === null && tile !== undefined && !tile.classList.contains('filtered')) { - // Create a canvas and set its width and height. - var canvas = document.createElement('canvas'); - canvas.setAttribute('width', '256px'); - canvas.setAttribute('height', '256px'); - - // Get the canvas drawing context, and draw the image to it. - var context = canvas.getContext('2d'); - if (context) { - context.drawImage(tile as CanvasImageSource, 0, 0, canvas.width, canvas.height); - - // Get the canvas image data. - var imageData = context.getImageData(0, 0, canvas.width, canvas.height); - - // Create a function for preserving a specified colour. - var makeTransparent = function(imageData: ImageData, color: {r: number, g: number, b: number}) { - // Get the pixel data from the source. - var data = imageData.data; - // Iterate through all the pixels. - for (var i = 0; i < data.length; i += 4) { - // Check if the current pixel should have preserved transparency. This simply compares whether the color we passed in is equivalent to our pixel data. - var convert = data[i] > color.r - 5 && data[i] < color.r + 5 - && data[i + 1] > color.g - 5 && data[i + 1] < color.g + 5 - && data[i + 2] > color.b - 5 && data[i + 2] < color.b + 5; - - // Either preserve the initial transparency or set the transparency to 0. - data[i + 3] = convert ? 100: data[i + 3]; - } - return imageData; - }; - - // Get the new pixel data and set it to the canvas context. - var newData = makeTransparent(imageData, {r: 26, g: 109, b: 127}); - context.putImageData(newData, 0, 0); - (tile as HTMLImageElement).src = canvas.toDataURL(); - tile.classList.add('filtered'); - } - } else { - return done(error, tile); - } - } - return super.createTile(coords, newDone); - } -} \ No newline at end of file diff --git a/frontend/website/src/map/map.ts b/frontend/website/src/map/map.ts deleted file mode 100644 index 35c240e8..00000000 --- a/frontend/website/src/map/map.ts +++ /dev/null @@ -1,1119 +0,0 @@ -import * as L from "leaflet" -import { getApp } from ".."; -import { BoxSelect } from "./boxselect"; -import { MapContextMenu } from "../contextmenus/mapcontextmenu"; -import { UnitContextMenu } from "../contextmenus/unitcontextmenu"; -import { AirbaseContextMenu } from "../contextmenus/airbasecontextmenu"; -import { Dropdown } from "../controls/dropdown"; -import { Airbase } from "../mission/airbase"; -import { Unit } from "../unit/unit"; -import { bearing, createCheckboxOption, createSliderInputOption, createTextInputOption, deg2rad, getGroundElevation, polyContains } from "../other/utils"; -import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker"; -import { TemporaryUnitMarker } from "./markers/temporaryunitmarker"; -import { ClickableMiniMap } from "./clickableminimap"; -import { SVGInjector } from '@tanem/svg-injector' -import { defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, MAP_MARKER_CONTROLS, DCS_LINK_PORT, DCS_LINK_RATIO, defaultMapMirrors } from "../constants/constants"; -import { CoalitionArea } from "./coalitionarea/coalitionarea"; -import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu"; -import { DrawingCursor } from "./coalitionarea/drawingcursor"; -import { AirbaseSpawnContextMenu } from "../contextmenus/airbasespawnmenu"; -import { GestureHandling } from "leaflet-gesture-handling"; -import { TouchBoxSelect } from "./touchboxselect"; -import { DestinationPreviewHandle } from "./markers/destinationpreviewHandle"; -import { ContextActionSet } from "../unit/contextactionset"; -import { DCSLayer } from "./dcslayer"; - -var hasTouchScreen = false; -//if ("maxTouchPoints" in navigator) -// hasTouchScreen = navigator.maxTouchPoints > 0; - -if (hasTouchScreen) - L.Map.addInitHook('addHandler', 'boxSelect', TouchBoxSelect); -else - L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); - -L.Map.addInitHook("addHandler", "gestureHandling", GestureHandling); - -// TODO would be nice to convert to ts - yes -require("../../node_modules/leaflet.nauticscale/dist/leaflet.nauticscale.js") -require("../../node_modules/leaflet-path-drag/dist/index.js") - -export type MapMarkerVisibilityControl = { - "category"?: string; - "image": string; - "isProtected"?: boolean, - "name": string, - "protectable"?: boolean, - "toggles": string[], - "tooltip": string -} - -export class Map extends L.Map { - #ID: string; - #state: string; - #layer: L.TileLayer | L.LayerGroup | null = null; - #preventLeftClick: boolean = false; - #leftClickTimer: number = 0; - #deafultPanDelta: number = 100; - #panInterval: number | null = null; - #panLeft: boolean = false; - #panRight: boolean = false; - #panUp: boolean = false; - #panDown: boolean = false; - #lastMousePosition: L.Point = new L.Point(0, 0); - #shiftKey: boolean = false; - #ctrlKey: boolean = false; - #centerUnit: Unit | null = null; - #miniMap: ClickableMiniMap | null = null; - #miniMapLayerGroup: L.LayerGroup; - #miniMapPolyline: L.Polyline; - #temporaryMarkers: TemporaryUnitMarker[] = []; - #selecting: boolean = false; - #isZooming: boolean = false; - #previousZoom: number = 0; - #slaveDCSCamera: boolean = false; - #slaveDCSCameraAvailable: boolean = false; - #cameraControlTimer: number = 0; - #cameraControlPort: number = 3003; - #cameraControlMode: string = 'map'; - - #destinationGroupRotation: number = 0; - #computeDestinationRotation: boolean = false; - #destinationRotationCenter: L.LatLng | null = null; - #coalitionAreas: CoalitionArea[] = []; - - #destinationPreviewCursors: DestinationPreviewMarker[] = []; - #drawingCursor: DrawingCursor = new DrawingCursor(); - #destinationPreviewHandle: DestinationPreviewHandle = new DestinationPreviewHandle(new L.LatLng(0, 0)); - #destinationPreviewHandleLine: L.Polyline = new L.Polyline([], { color: "#000000", weight: 3, opacity: 0.5, smoothFactor: 1, dashArray: "4, 8" }); - #longPressHandled: boolean = false; - #longPressTimer: number = 0; - - #mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu"); - #unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu"); - #airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu"); - #airbaseSpawnMenu: AirbaseSpawnContextMenu = new AirbaseSpawnContextMenu("airbase-spawn-contextmenu"); - #coalitionAreaContextMenu: CoalitionAreaContextMenu = new CoalitionAreaContextMenu("coalition-area-contextmenu"); - - #mapSourceDropdown: Dropdown; - #mapLayers: any = defaultMapLayers; - #mapMirrors: any = defaultMapMirrors; - #mapMarkerVisibilityControls: MapMarkerVisibilityControl[] = MAP_MARKER_CONTROLS; - #mapVisibilityOptionsDropdown: Dropdown; - #optionButtons: { [key: string]: HTMLButtonElement[] } = {} - #visibilityOptions: { [key: string]: boolean | string | number } = {} - #hiddenTypes: string[] = []; - #layerName: string = ""; - #cameraOptionsXmlHttp: XMLHttpRequest | null = null; - #bradcastPositionXmlHttp: XMLHttpRequest | null = null; - #cameraZoomRatio: number = 1.0; - - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string) { - /* Init the leaflet map */ - super(ID, { - preferCanvas: true, - doubleClickZoom: false, - zoomControl: false, - boxZoom: false, - //@ts-ignore Needed because the boxSelect option is non-standard - boxSelect: true, - zoomAnimation: true, - maxBoundsViscosity: 1.0, - minZoom: 7, - keyboard: true, - keyboardPanDelta: 0, - gestureHandling: hasTouchScreen - }); - this.setView([37.23, -115.8], 10); - - this.#ID = ID; - - /* Minimap */ - var minimapLayer = new L.TileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", { minZoom: 0, maxZoom: 13 }); - this.#miniMapLayerGroup = new L.LayerGroup([minimapLayer]); - this.#miniMapPolyline = new L.Polyline([], { color: '#202831' }); - this.#miniMapPolyline.addTo(this.#miniMapLayerGroup); - - /* Scale */ - //@ts-ignore TODO more hacking because the module is provided as a pure javascript module only - //L.control.scalenautic({ position: "topright", maxWidth: 300, nautic: true, metric: true, imperial: false }).addTo(this); - - /* Map source dropdown */ - this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName)); - this.#mapSourceDropdown.setOptions(this.getLayers(), null); - - /* Visibility options dropdown */ - this.#mapVisibilityOptionsDropdown = new Dropdown("map-visibility-options", (value: string) => { }); - - /* Init the state machine */ - this.#state = IDLE; - - /* Register event handles */ - this.on("click", (e: any) => this.#onClick(e)); - this.on("dblclick", (e: any) => this.#onDoubleClick(e)); - this.on("zoomstart", (e: any) => this.#onZoomStart(e)); - this.on("zoom", (e: any) => this.#onZoom(e)); - this.on("zoomend", (e: any) => this.#onZoomEnd(e)); - this.on("drag", (e: any) => this.centerOnUnit(null)); - this.on("contextmenu", (e: any) => this.#onContextMenu(e)); - this.on('selectionstart', (e: any) => this.#onSelectionStart(e)); - this.on('selectionend', (e: any) => this.#onSelectionEnd(e)); - this.on('mousedown', (e: any) => this.#onMouseDown(e)); - this.on('mouseup', (e: any) => this.#onMouseUp(e)); - this.on('mousemove', (e: any) => this.#onMouseMove(e)); - this.on('drag', (e: any) => this.#onMouseMove(e)); - this.on('keydown', (e: any) => this.#onKeyDown(e)); - this.on('keyup', (e: any) => this.#onKeyUp(e)); - this.on('move', (e: any) => { if (this.#slaveDCSCamera) this.#broadcastPosition() }); - - /* Event listeners */ - document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { - const el = ev.detail._element; - el?.classList.toggle("off"); - this.setHiddenType(ev.detail.coalition, !el?.classList.contains("off")); - Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); - }); - - document.addEventListener("toggleMarkerVisibility", (ev: CustomEventInit) => { - const el = ev.detail._element; - el?.classList.toggle("off"); - ev.detail.types.forEach((type: string) => this.setHiddenType(type, !el?.classList.contains("off"))); - Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); - - if (ev.detail.types.includes("airbase")) { - Object.values(getApp().getMissionManager().getAirbases()).forEach((airbase: Airbase) => { - if (el?.classList.contains("off")) - airbase.removeFrom(this); - else - airbase.addTo(this); - }) - } - }); - - document.addEventListener("toggleCoalitionAreaDraw", (ev: CustomEventInit) => { - this.getMapContextMenu().hide(); - this.deselectAllCoalitionAreas(); - if (ev.detail?.type == "polygon") { - if (this.getState() !== COALITIONAREA_DRAW_POLYGON) - this.setState(COALITIONAREA_DRAW_POLYGON); - else - this.setState(IDLE); - } - }); - - document.addEventListener("unitUpdated", (ev: CustomEvent) => { - if (this.#centerUnit != null && ev.detail == this.#centerUnit) - this.#panToUnit(this.#centerUnit); - }); - - document.addEventListener("mapOptionsChanged", () => { - this.getContainer().toggleAttribute("data-hide-labels", !this.getVisibilityOptions()[SHOW_UNIT_LABELS]); - this.#cameraControlPort = this.getVisibilityOptions()[DCS_LINK_PORT] as number; - this.#cameraZoomRatio = 50 / (20 + (this.getVisibilityOptions()[DCS_LINK_RATIO] as number)); - - if (this.#slaveDCSCamera) { - this.#broadcastPosition(); - window.setTimeout(() => { - this.#broadcastPosition(); - }, 500); // DCS does not always apply the altitude correctly at the first set when changing map type - } - }); - - document.addEventListener("configLoaded", () => { - let config = getApp().getConfig(); - let layerSet = false; - - /* First load the map mirrors */ - if (config.mapMirrors) { - let mapMirrors = config.mapMirrors; - this.#mapMirrors = { - ...this.#mapMirrors, - ...mapMirrors - } - this.setLayer(Object.keys(mapMirrors)[0]); - } - - /* Set the options, and if at least one mirror is available, select the first */ - this.#mapSourceDropdown.setOptions(Object.keys(this.#mapMirrors), null); - if (Object.keys(this.#mapMirrors).length > 0) { - this.#mapSourceDropdown.selectValue(0); - this.setLayer(Object.keys(this.#mapMirrors)[0]); - layerSet = true; // Needed because this is async - } - - /* Then load the map layers */ - if (config.mapLayers) { - let mapLayers = config.mapLayers; - this.#mapLayers = { - ...this.#mapLayers, - ...mapLayers - } - } - - /* Append this options, and if no mirror was selected, select the first on (if available). Mirrors have the precedence */ - this.#mapSourceDropdown.setOptions(Object.keys(this.#mapMirrors).concat(Object.keys(this.#mapLayers)), null); - if (!layerSet && Object.keys(this.#mapLayers).length > 0) { - this.#mapSourceDropdown.selectValue(0); - this.setLayer(Object.keys(this.#mapLayers)[0]); - } - }) - - document.addEventListener("toggleCameraLinkStatus", () => { - // if (this.#slaveDCSCameraAvailable) { // Commented to experiment with usability - this.setSlaveDCSCamera(!this.#slaveDCSCamera); - // } - }) - - document.addEventListener("slewCameraToPosition", () => { - // if (this.#slaveDCSCameraAvailable) { // Commented to experiment with usability - this.#broadcastPosition(); - // } - }) - - /* Pan interval */ - this.#panInterval = window.setInterval(() => { - if (this.#panUp || this.#panDown || this.#panRight || this.#panLeft) - this.panBy(new L.Point(((this.#panLeft ? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta * (this.#shiftKey ? 3 : 1), - ((this.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.#deafultPanDelta * (this.#shiftKey ? 3 : 1))); - }, 20); - - /* Periodically check if the camera control endpoint is available */ - this.#cameraControlTimer = window.setInterval(() => { - this.#checkCameraPort(); - }, 10000) - - /* Option buttons */ - this.#createUnitMarkerControlButtons(); - - /* Create the checkboxes to select the advanced visibility options */ - this.addVisibilityOption(DCS_LINK_PORT, 3003, { min: 1024, max: 65535 }); - this.addVisibilityOption(DCS_LINK_RATIO, 50, { min: 0, max: 100, slider: true }); - - this.#mapVisibilityOptionsDropdown.addHorizontalDivider(); - - this.addVisibilityOption(SHOW_UNIT_CONTACTS, false); - this.addVisibilityOption(HIDE_GROUP_MEMBERS, true); - this.addVisibilityOption(SHOW_UNIT_PATHS, true); - this.addVisibilityOption(SHOW_UNIT_TARGETS, true); - this.addVisibilityOption(SHOW_UNIT_LABELS, true); - this.addVisibilityOption(SHOW_UNITS_ENGAGEMENT_RINGS, true); - this.addVisibilityOption(SHOW_UNITS_ACQUISITION_RINGS, true); - this.addVisibilityOption(HIDE_UNITS_SHORT_RANGE_RINGS, true); - /* this.addVisibilityOption(FILL_SELECTED_RING, false); Removed since currently broken: TODO fix!*/ - } - - addVisibilityOption(option: string, defaultValue: boolean | number | string, options?: { [key: string]: any }) { - this.#visibilityOptions[option] = defaultValue; - if (typeof defaultValue === 'boolean') { - this.#mapVisibilityOptionsDropdown.addOptionElement(createCheckboxOption(option, option, defaultValue as boolean, (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); - } else if (typeof defaultValue === 'number') { - if (options !== undefined && options?.slider === true) - this.#mapVisibilityOptionsDropdown.addOptionElement(createSliderInputOption(option, option, defaultValue, (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); - else - this.#mapVisibilityOptionsDropdown.addOptionElement(createTextInputOption(option, option, defaultValue.toString(), 'number', (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); - } else { - this.#mapVisibilityOptionsDropdown.addOptionElement(createTextInputOption(option, option, defaultValue, 'text', (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); - } - } - - setLayer(layerName: string) { - if (this.#layer) - this.removeLayer(this.#layer); - - let theatre = getApp().getMissionManager()?.getTheatre() ?? "Nevada"; - - /* Normal or custom layers are handled here */ - if (layerName in this.#mapLayers) { - const layerData = this.#mapLayers[layerName]; - if (layerData instanceof Array) { - let layers = layerData.map((layer: any) => { - return new L.TileLayer(layer.urlTemplate.replace("{theatre}", theatre.toLowerCase()), layer); - }) - this.#layer = new L.LayerGroup(layers); - this.#layer?.addTo(this); - } else { - this.#layer = new L.TileLayer(layerData.urlTemplate, layerData); - this.#layer?.addTo(this); - } - /* mirrored layers are handled here */ - } else if (Object.keys(this.#mapMirrors).includes(layerName) ) { - let layers: L.TileLayer[] = []; - - layers.push(new L.TileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", { - minZoom: 1, - maxZoom: 19, - })) - - /* Load the configuration file */ - const mirror = this.#mapMirrors[layerName as any]; - const request = new Request(mirror + "/config.json"); - fetch(request).then((response) => { - if (response.status === 200) { - return response.json(); - } else { - return {}; - } - }).then((res: any) => { - if ("alt-" + theatre.toLowerCase() in res) { - let template = `${mirror}/alt-${theatre.toLowerCase()}/{z}/{x}/{y}.png`; - layers.push(...res["alt-" + theatre.toLowerCase()].map((layerConfig: any) => { - return new L.TileLayer(template, {...layerConfig, crossOrigin: ""}); - })); - } - this.#layer = new L.LayerGroup(layers); - this.#layer?.addTo(this); - }).catch(() => { - this.#layer = new L.LayerGroup(layers); - this.#layer?.addTo(this); - }) - } - this.#layerName = layerName; - } - - getLayers() { - return Object.keys(this.#mapLayers); - } - - /* State machine */ - setState(state: string) { - this.#state = state; - this.#updateCursor(); - - /* Operations to perform if you are NOT in a state */ - if (this.#state !== COALITIONAREA_DRAW_POLYGON) { - this.#deselectSelectedCoalitionArea(); - } - - /* Operations to perform if you ARE in a state */ - if (this.#state === COALITIONAREA_DRAW_POLYGON) { - this.#coalitionAreas.push(new CoalitionArea([])); - this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this); - } - document.dispatchEvent(new CustomEvent("mapStateChanged")); - } - - getState() { - return this.#state; - } - - deselectAllCoalitionAreas() { - this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => coalitionArea.setSelected(false)); - } - - deleteCoalitionArea(coalitionArea: CoalitionArea) { - if (this.#coalitionAreas.includes(coalitionArea)) - this.#coalitionAreas.splice(this.#coalitionAreas.indexOf(coalitionArea), 1); - if (this.hasLayer(coalitionArea)) - this.removeLayer(coalitionArea); - } - - setHiddenType(key: string, value: boolean) { - if (value) { - if (this.#hiddenTypes.includes(key)) - delete this.#hiddenTypes[this.#hiddenTypes.indexOf(key)]; - } - else { - this.#hiddenTypes.push(key); - } - } - - getHiddenTypes() { - return this.#hiddenTypes; - } - - /* Context Menus */ - hideAllContextMenus() { - this.hideMapContextMenu(); - this.hideUnitContextMenu(); - this.hideAirbaseContextMenu(); - this.hideAirbaseSpawnMenu(); - this.hideCoalitionAreaContextMenu(); - } - - showMapContextMenu(x: number, y: number, latlng: L.LatLng) { - this.hideAllContextMenus(); - this.#mapContextMenu.show(x, y, latlng); - document.dispatchEvent(new CustomEvent("mapContextMenu")); - } - - hideMapContextMenu() { - this.#mapContextMenu.hide(); - document.dispatchEvent(new CustomEvent("mapContextMenu")); - } - - getMapContextMenu() { - return this.#mapContextMenu; - } - - showUnitContextMenu(x: number | undefined = undefined, y: number | undefined = undefined, latlng: L.LatLng | undefined = undefined) { - this.hideAllContextMenus(); - this.#unitContextMenu.show(x, y, latlng); - } - - getUnitContextMenu() { - return this.#unitContextMenu; - } - - hideUnitContextMenu() { - this.#unitContextMenu.hide(); - } - - showAirbaseContextMenu(airbase: Airbase, x: number | undefined = undefined, y: number | undefined = undefined, latlng: L.LatLng | undefined = undefined) { - this.hideAllContextMenus(); - this.#airbaseContextMenu.show(x, y, latlng); - this.#airbaseContextMenu.setAirbase(airbase); - } - - getAirbaseContextMenu() { - return this.#airbaseContextMenu; - } - - hideAirbaseContextMenu() { - this.#airbaseContextMenu.hide(); - } - - showAirbaseSpawnMenu(airbase: Airbase, x: number | undefined = undefined, y: number | undefined = undefined, latlng: L.LatLng | undefined = undefined) { - this.hideAllContextMenus(); - this.#airbaseSpawnMenu.show(x, y); - this.#airbaseSpawnMenu.setAirbase(airbase); - } - - getAirbaseSpawnMenu() { - return this.#airbaseSpawnMenu; - } - - hideAirbaseSpawnMenu() { - this.#airbaseSpawnMenu.hide(); - } - - showCoalitionAreaContextMenu(x: number, y: number, latlng: L.LatLng, coalitionArea: CoalitionArea) { - this.hideAllContextMenus(); - this.#coalitionAreaContextMenu.show(x, y, latlng); - this.#coalitionAreaContextMenu.setCoalitionArea(coalitionArea); - } - - getCoalitionAreaContextMenu() { - return this.#coalitionAreaContextMenu; - } - - hideCoalitionAreaContextMenu() { - this.#coalitionAreaContextMenu.hide(); - } - - getMousePosition() { - return this.#lastMousePosition; - } - - getMouseCoordinates() { - return this.containerPointToLatLng(this.#lastMousePosition); - } - - centerOnUnit(ID: number | null) { - if (ID != null) { - this.options.scrollWheelZoom = 'center'; - this.#centerUnit = getApp().getUnitsManager().getUnitByID(ID); - } - else { - this.options.scrollWheelZoom = undefined; - this.#centerUnit = null; - } - this.#updateCursor(); - } - - getCenteredOnUnit() { - return this.#centerUnit; - } - - setTheatre(theatre: string) { - var bounds = new L.LatLngBounds([-90, -180], [90, 180]); - var miniMapZoom = 5; - if (theatre in mapBounds) { - bounds = mapBounds[theatre as keyof typeof mapBounds].bounds; - miniMapZoom = mapBounds[theatre as keyof typeof mapBounds].zoom; - } - - this.setView(bounds.getCenter(), 8); - - if (this.#miniMap) - this.#miniMap.remove(); - - //@ts-ignore // Needed because some of the inputs are wrong in the original module interface - this.#miniMap = new ClickableMiniMap(this.#miniMapLayerGroup, { position: "topright", width: 192 * 1.5, height: 108 * 1.5, zoomLevelFixed: miniMapZoom, centerFixed: bounds.getCenter() }).addTo(this); - this.#miniMap.disableInteractivity(); - this.#miniMap.getMap().on("click", (e: any) => { - if (this.#miniMap) - this.setView(e.latlng); - }) - - const boundaries = this.#getMinimapBoundaries(); - this.#miniMapPolyline.setLatLngs(boundaries[theatre as keyof typeof boundaries]); - - this.setLayer(this.#layerName); - } - - getMiniMapLayerGroup() { - return this.#miniMapLayerGroup; - } - - handleMapPanning(e: any) { - if (e.type === "keyup") { - switch (e.code) { - case "KeyA": - case "ArrowLeft": - this.#panLeft = false; - break; - case "KeyD": - case "ArrowRight": - this.#panRight = false; - break; - case "KeyW": - case "ArrowUp": - this.#panUp = false; - break; - case "KeyS": - case "ArrowDown": - this.#panDown = false; - break; - } - } - else { - switch (e.code) { - case 'KeyA': - case 'ArrowLeft': - this.#panLeft = true; - break; - case 'KeyD': - case 'ArrowRight': - this.#panRight = true; - break; - case 'KeyW': - case 'ArrowUp': - this.#panUp = true; - break; - case 'KeyS': - case 'ArrowDown': - this.#panDown = true; - break; - } - } - } - - addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string, commandHash?: string) { - var marker = new TemporaryUnitMarker(latlng, name, coalition, commandHash); - marker.addTo(this); - this.#temporaryMarkers.push(marker); - return marker; - } - - getSelectedCoalitionArea() { - return this.#coalitionAreas.find((area: CoalitionArea) => { return area.getSelected() }); - } - - bringCoalitionAreaToBack(coalitionArea: CoalitionArea) { - coalitionArea.bringToBack(); - this.#coalitionAreas.splice(this.#coalitionAreas.indexOf(coalitionArea), 1); - this.#coalitionAreas.unshift(coalitionArea); - } - - getVisibilityOptions() { - return this.#visibilityOptions; - } - - isZooming() { - return this.#isZooming; - } - - getPreviousZoom() { - return this.#previousZoom; - } - - getIsUnitProtected(unit: Unit) { - const toggles = this.#mapMarkerVisibilityControls.reduce((list, control: MapMarkerVisibilityControl) => { - if (control.isProtected) { - list = list.concat(control.toggles); - } - return list; - }, [] as string[]); - - if (toggles.length === 0) - return false; - - return toggles.some((toggle: string) => { - // Specific coding for robots - extend later if needed - return (toggle === "dcs" && !unit.getControlled() && !unit.getHuman()); - }); - } - - getMapMarkerVisibilityControls() { - return this.#mapMarkerVisibilityControls; - } - - setSlaveDCSCamera(newSlaveDCSCamera: boolean) { - this.#checkCameraPort(); - this.#slaveDCSCamera = newSlaveDCSCamera; - let button = document.getElementById("camera-link-control"); - button?.classList.toggle("off", !newSlaveDCSCamera); - if (this.#slaveDCSCamera) { - this.#broadcastPosition(); - window.setTimeout(() => { - this.#broadcastPosition(); - }, 500); // DCS does not always apply the altitude correctly at the first set when changing map type - } - } - - setCameraControlMode(newCameraControlMode: string) { - this.#cameraControlMode = newCameraControlMode; - if (this.#slaveDCSCamera) { - this.#broadcastPosition(); - window.setTimeout(() => { - this.#broadcastPosition(); - }, 500); // DCS does not always apply the altitude correctly at the first set when changing map type - } - } - - increaseCameraZoom() { - const slider = document.querySelector(`label[title="${DCS_LINK_RATIO}"] input`); - if (slider instanceof HTMLInputElement) { - slider.value = String(Math.min(Number(slider.max), Number(slider.value) + 10)); - slider.dispatchEvent(new Event('input')); - slider.dispatchEvent(new Event('mouseup')); - } - } - - decreaseCameraZoom() { - const slider = document.querySelector(`label[title="${DCS_LINK_RATIO}"] input`); - if (slider instanceof HTMLInputElement) { - slider.value = String(Math.max(Number(slider.min), Number(slider.value) - 10)); - slider.dispatchEvent(new Event('input')); - slider.dispatchEvent(new Event('mouseup')); - } - } - - /* Event handlers */ - #onClick(e: any) { - if (!this.#preventLeftClick) { - this.hideAllContextMenus(); - if (this.#state === IDLE) { - this.deselectAllCoalitionAreas(); - } - else if (this.#state === COALITIONAREA_DRAW_POLYGON) { - if (this.getSelectedCoalitionArea()?.getEditing()) { - this.getSelectedCoalitionArea()?.addTemporaryLatLng(e.latlng); - } - else { - this.deselectAllCoalitionAreas(); - } - } - else { - this.setState(IDLE); - getApp().getUnitsManager().deselectAllUnits(); - } - } - } - - #onDoubleClick(e: any) { - - } - - #onContextMenu(e: any) { - /* A long press will show the point action context menu */ - window.clearInterval(this.#longPressTimer); - if (this.#longPressHandled) { - this.#longPressHandled = false; - return; - } - - this.hideMapContextMenu(); - if (this.#state === IDLE) { - if (this.#state == IDLE) { - this.showMapContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng); - var clickedCoalitionArea = null; - - /* Coalition areas are ordered in the #coalitionAreas array according to their zindex. Select the upper one */ - for (let coalitionArea of this.#coalitionAreas) { - if (polyContains(e.latlng, coalitionArea)) { - if (coalitionArea.getSelected()) - clickedCoalitionArea = coalitionArea; - else - this.getMapContextMenu().setCoalitionArea(coalitionArea); - } - } - if (clickedCoalitionArea) - this.showCoalitionAreaContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng, clickedCoalitionArea); - } - } - else if (this.#state === MOVE_UNIT) { - if (!e.originalEvent.shiftKey) { - if (!e.originalEvent.ctrlKey) { - getApp().getUnitsManager().clearDestinations(); - } - getApp().getUnitsManager().addDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation) - - this.#destinationGroupRotation = 0; - this.#destinationRotationCenter = null; - this.#computeDestinationRotation = false; - } - } - else { - this.setState(IDLE); - } - } - - #onSelectionStart(e: any) { - this.#selecting = true; - this.#updateCursor(); - } - - #onSelectionEnd(e: any) { - this.#selecting = false; - clearTimeout(this.#leftClickTimer); - this.#preventLeftClick = true; - this.#leftClickTimer = window.setTimeout(() => { - this.#preventLeftClick = false; - }, 200); - getApp().getUnitsManager().selectFromBounds(e.selectionBounds); - this.#updateCursor(); - } - - #onMouseDown(e: any) { - this.hideAllContextMenus(); - - if (this.#state == MOVE_UNIT) { - this.#destinationGroupRotation = 0; - this.#destinationRotationCenter = null; - this.#computeDestinationRotation = false; - if (e.originalEvent.button == 2) { - this.#computeDestinationRotation = true; - this.#destinationRotationCenter = this.getMouseCoordinates(); - } - } - - this.#longPressTimer = window.setTimeout(() => { - this.hideMapContextMenu(); - this.#longPressHandled = true; - - if (e.originalEvent.button != 2 || e.originalEvent.ctrlKey || e.originalEvent.shiftKey) - return; - - var contextActionSet = new ContextActionSet(); - var units = getApp().getUnitsManager().getSelectedUnits(); - units.forEach((unit: Unit) => { - unit.appendContextActions(contextActionSet, null, e.latlng); - }) - - if (Object.keys(contextActionSet.getContextActions()).length > 0) { - getApp().getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng); - getApp().getMap().getUnitContextMenu().setContextActions(contextActionSet); - } - }, 150); - this.#longPressHandled = false; - } - - #onMouseUp(e: any) { - if (this.#state === MOVE_UNIT && e.originalEvent.button == 2 && e.originalEvent.shiftKey) { - if (!e.originalEvent.ctrlKey) { - getApp().getUnitsManager().clearDestinations(); - } - getApp().getUnitsManager().addDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation) - - this.#destinationGroupRotation = 0; - this.#destinationRotationCenter = null; - this.#computeDestinationRotation = false; - } - } - - #onMouseMove(e: any) { - this.#lastMousePosition.x = e.originalEvent.x; - this.#lastMousePosition.y = e.originalEvent.y; - - this.#updateCursor(); - - if (this.#state === MOVE_UNIT) { - /* Update the position of the destination cursors depeding on mouse rotation */ - if (this.#computeDestinationRotation && this.#destinationRotationCenter != null) - this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng); - this.#updateDestinationCursors(); - } - else if (this.#state === COALITIONAREA_DRAW_POLYGON && e.latlng !== undefined) { - this.#drawingCursor.setLatLng(e.latlng); - /* Update the polygon being drawn with the current position of the mouse cursor */ - this.getSelectedCoalitionArea()?.moveActiveVertex(e.latlng); - } - } - - #onKeyDown(e: any) { - this.#shiftKey = e.originalEvent.shiftKey; - this.#ctrlKey = e.originalEvent.ctrlKey; - this.#updateCursor(); - this.#updateDestinationCursors(); - } - - #onKeyUp(e: any) { - this.#shiftKey = e.originalEvent.shiftKey; - this.#ctrlKey = e.originalEvent.ctrlKey; - this.#updateCursor(); - this.#updateDestinationCursors(); - } - - #onZoomStart(e: any) { - this.#previousZoom = this.getZoom(); - if (this.#centerUnit != null) - this.#panToUnit(this.#centerUnit); - this.#isZooming = true; - } - - #onZoom(e: any) { - if (this.#centerUnit != null) - this.#panToUnit(this.#centerUnit); - } - - #onZoomEnd(e: any) { - this.#isZooming = false; - } - - #broadcastPosition() { - if (this.#bradcastPositionXmlHttp?.readyState !== 4 && this.#bradcastPositionXmlHttp !== null) - return - - getGroundElevation(this.getCenter(), (response: string) => { - var groundElevation: number | null = null; - try { - groundElevation = parseFloat(response); - this.#bradcastPositionXmlHttp = new XMLHttpRequest(); - /* Using 127.0.0.1 instead of localhost because the LuaSocket version used in DCS only listens to IPv4. This avoids the lag caused by the - browser if it first tries to send the request on the IPv6 address for localhost */ - this.#bradcastPositionXmlHttp.open("POST", `http://127.0.0.1:${this.#cameraControlPort}`); - - const C = 40075016.686; - let mpp = C * Math.cos(deg2rad(this.getCenter().lat)) / Math.pow(2, this.getZoom() + 8); - let d = mpp * 1920; - let alt = d / 2 * 1 / Math.tan(deg2rad(40)) * this.#cameraZoomRatio; - alt = Math.min(alt, 50000); - this.#bradcastPositionXmlHttp.send(JSON.stringify({ lat: this.getCenter().lat, lng: this.getCenter().lng, alt: alt + groundElevation, mode: this.#cameraControlMode })); - } catch { - console.warn("broadcastPosition: could not retrieve ground elevation") - } - }); - } - - /* */ - #panToUnit(unit: Unit) { - var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng); - this.setView(unitPosition, this.getZoom(), { animate: false }); - this.#updateCursor(); - this.#updateDestinationCursors(); - } - - #getMinimapBoundaries() { - /* Draw the limits of the maps in the minimap*/ - return minimapBoundaries; - } - - #createUnitMarkerControlButtons() { - const unitVisibilityControls = document.getElementById("unit-visibility-control"); - const makeTitle = (isProtected: boolean) => { - return (isProtected) ? "Unit type is protected and will ignore orders" : "Unit is NOT protected and will respond to orders"; - } - this.getMapMarkerVisibilityControls().forEach((control: MapMarkerVisibilityControl) => { - const toggles = `["${control.toggles.join('","')}"]`; - const div = document.createElement("div"); - div.className = control.protectable === true ? "protectable" : ""; - - // TODO: for consistency let's avoid using innerHTML. Let's create elements. - div.innerHTML = ` - - `; - unitVisibilityControls.appendChild(div); - - if (control.protectable) { - div.innerHTML += ` - `; - - const btn = div.querySelector("button.lock"); - btn.addEventListener("click", (ev: MouseEventInit) => { - control.isProtected = !control.isProtected; - btn.toggleAttribute("data-protected", control.isProtected); - btn.title = makeTitle(control.isProtected); - document.dispatchEvent(new CustomEvent("toggleMarkerProtection", { - detail: { - "_element": btn, - "control": control - } - })); - }); - } - }); - - unitVisibilityControls.querySelectorAll(`img[src$=".svg"]`).forEach(img => SVGInjector(img)); - } - - #deselectSelectedCoalitionArea() { - this.getSelectedCoalitionArea()?.setSelected(false); - } - - /* Cursors */ - #updateCursor() { - /* If the ctrl key is being pressed or we are performing an area selection, show the default cursor */ - if (this.#ctrlKey || this.#selecting) { - /* Hide all non default cursors */ - this.#hideDestinationCursors(); - this.#hideDrawingCursor(); - - this.#showDefaultCursor(); - } else { - /* Hide all the unnecessary cursors depending on the active state */ - if (this.#state !== IDLE) this.#hideDefaultCursor(); - if (this.#state !== MOVE_UNIT) this.#hideDestinationCursors(); - if (this.#state !== COALITIONAREA_DRAW_POLYGON) this.#hideDrawingCursor(); - - /* Show the active cursor depending on the active state */ - if (this.#state === IDLE) this.#showDefaultCursor(); - else if (this.#state === MOVE_UNIT) this.#showDestinationCursors(); - else if (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor(); - } - } - - #showDefaultCursor() { - document.getElementById(this.#ID)?.classList.remove("hidden-cursor"); - } - - #hideDefaultCursor() { - document.getElementById(this.#ID)?.classList.add("hidden-cursor"); - } - - #showDestinationCursors() { - const singleCursor = !this.#shiftKey; - const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }).length; - if (singleCursor) { - this.#hideDestinationCursors(); - } - else if (!singleCursor) { - if (selectedUnitsCount > 1) { - while (this.#destinationPreviewCursors.length > selectedUnitsCount) { - this.removeLayer(this.#destinationPreviewCursors[0]); - this.#destinationPreviewCursors.splice(0, 1); - } - - this.#destinationPreviewHandleLine.addTo(this); - this.#destinationPreviewHandle.addTo(this); - - while (this.#destinationPreviewCursors.length < selectedUnitsCount) { - var cursor = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); - cursor.addTo(this); - this.#destinationPreviewCursors.push(cursor); - } - - this.#updateDestinationCursors(); - } - } - } - - #updateDestinationCursors() { - const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }).length; - if (selectedUnitsCount > 1) { - const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(); - if (this.#destinationPreviewCursors.length == 1) - this.#destinationPreviewCursors[0].setLatLng(this.getMouseCoordinates()); - else { - Object.values(getApp().getUnitsManager().computeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { - if (idx < this.#destinationPreviewCursors.length) - this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey ? latlng : this.getMouseCoordinates()); - }) - }; - - this.#destinationPreviewHandleLine.setLatLngs([groupLatLng, this.getMouseCoordinates()]); - this.#destinationPreviewHandle.setLatLng(this.getMouseCoordinates()); - } else { - this.#hideDestinationCursors(); - } - } - - #hideDestinationCursors() { - /* Remove all the destination cursors */ - this.#destinationPreviewCursors.forEach((marker: L.Marker) => { - this.removeLayer(marker); - }) - this.#destinationPreviewCursors = []; - - this.#destinationPreviewHandleLine.removeFrom(this); - this.#destinationPreviewHandle.removeFrom(this); - - /* Reset the variables used to compute the rotation of the group cursors */ - this.#destinationGroupRotation = 0; - this.#computeDestinationRotation = false; - this.#destinationRotationCenter = null; - } - - #showDrawingCursor() { - this.#hideDefaultCursor(); - if (!this.hasLayer(this.#drawingCursor)) - this.#drawingCursor.addTo(this); - } - - #hideDrawingCursor() { - this.#drawingCursor.setLatLng(new L.LatLng(0, 0)); - if (this.hasLayer(this.#drawingCursor)) - this.#drawingCursor.removeFrom(this); - } - - #setVisibilityOption(option: string, ev: any) { - if (typeof this.#visibilityOptions[option] === 'boolean') - this.#visibilityOptions[option] = ev.currentTarget.checked; - else if (typeof this.#visibilityOptions[option] === 'number') - this.#visibilityOptions[option] = Number(ev.currentTarget.value); - else - this.#visibilityOptions[option] = ev.currentTarget.value; - document.dispatchEvent(new CustomEvent("mapOptionsChanged")); - } - - #setSlaveDCSCameraAvailable(newSlaveDCSCameraAvailable: boolean) { - this.#slaveDCSCameraAvailable = newSlaveDCSCameraAvailable; - let linkButton = document.getElementById("camera-link-control"); - if (linkButton) { - if (!newSlaveDCSCameraAvailable) { - //this.setSlaveDCSCamera(false); // Commented to experiment with usability - linkButton.classList.add("red"); - linkButton.title = "Camera link to DCS is not available"; - } else { - linkButton.classList.remove("red"); - linkButton.title = "Link/Unlink DCS camera with Olympus position"; - } - } - } - - /* Check if the camera control plugin is available. Right now this will only change the color of the button, no changes in functionality */ - #checkCameraPort(){ - if (this.#cameraOptionsXmlHttp?.readyState !== 4) - this.#cameraOptionsXmlHttp?.abort() - - this.#cameraOptionsXmlHttp = new XMLHttpRequest(); - - /* Using 127.0.0.1 instead of localhost because the LuaSocket version used in DCS only listens to IPv4. This avoids the lag caused by the - browser if it first tries to send the request on the IPv6 address for localhost */ - this.#cameraOptionsXmlHttp.open("OPTIONS", `http://127.0.0.1:${this.#cameraControlPort}`); - this.#cameraOptionsXmlHttp.onload = (res: any) => { - if (this.#cameraOptionsXmlHttp !== null && this.#cameraOptionsXmlHttp.status == 204) - this.#setSlaveDCSCameraAvailable(true); - else - this.#setSlaveDCSCameraAvailable(false); - }; - this.#cameraOptionsXmlHttp.onerror = (res: any) => { - this.#setSlaveDCSCameraAvailable(false); - } - this.#cameraOptionsXmlHttp.ontimeout = (res: any) => { - this.#setSlaveDCSCameraAvailable(false); - } - this.#cameraOptionsXmlHttp.timeout = 500; - this.#cameraOptionsXmlHttp.send(""); - } -} - diff --git a/frontend/website/src/map/markers/custommarker.ts b/frontend/website/src/map/markers/custommarker.ts deleted file mode 100644 index a6ca5998..00000000 --- a/frontend/website/src/map/markers/custommarker.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { DivIcon, Map, Marker } from "leaflet"; -import { MarkerOptions } from "leaflet"; -import { LatLngExpression } from "leaflet"; - -export class CustomMarker extends Marker { - constructor(latlng: LatLngExpression, options?: MarkerOptions) { - super(latlng, options); - } - - onAdd(map: Map): this { - this.setIcon(new DivIcon()); // Default empty icon - super.onAdd(map); - this.createIcon(); - return this; - } - - onRemove(map: Map): this { - super.onRemove(map); - return this; - } - - createIcon() { - /* Overloaded by child classes */ - } -} \ No newline at end of file diff --git a/frontend/website/src/map/markers/destinationpreviewHandle.ts b/frontend/website/src/map/markers/destinationpreviewHandle.ts deleted file mode 100644 index e6037edf..00000000 --- a/frontend/website/src/map/markers/destinationpreviewHandle.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { DivIcon, LatLng } from "leaflet"; -import { CustomMarker } from "../markers/custommarker"; - -export class DestinationPreviewHandle extends CustomMarker { - constructor(latlng: LatLng) { - super(latlng, {interactive: true, draggable: true}); - } - - createIcon() { - this.setIcon(new DivIcon({ - iconSize: [18, 18], - iconAnchor: [9, 9], - className: "leaflet-destination-preview-handle-marker", - })); - var el = document.createElement("div"); - el.classList.add("ol-destination-preview-handle-icon"); - this.getElement()?.appendChild(el); - } -} \ No newline at end of file diff --git a/frontend/website/src/map/markers/destinationpreviewmarker.ts b/frontend/website/src/map/markers/destinationpreviewmarker.ts deleted file mode 100644 index 28f21650..00000000 --- a/frontend/website/src/map/markers/destinationpreviewmarker.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet"; -import { CustomMarker } from "./custommarker"; - -export class DestinationPreviewMarker extends CustomMarker { - constructor(latlng: LatLngExpression, options?: MarkerOptions) { - super(latlng, options); - this.setZIndexOffset(9999); - } - - createIcon() { - this.setIcon(new DivIcon({ - iconSize: [52, 52], - iconAnchor: [26, 26], - className: "leaflet-destination-preview", - })); - var el = document.createElement("div"); - el.classList.add("ol-destination-preview-icon"); - this.getElement()?.appendChild(el); - } -} diff --git a/frontend/website/src/map/markers/smokemarker.ts b/frontend/website/src/map/markers/smokemarker.ts deleted file mode 100644 index d7ec80cf..00000000 --- a/frontend/website/src/map/markers/smokemarker.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet"; -import { CustomMarker } from "./custommarker"; -import { SVGInjector } from "@tanem/svg-injector"; -import { getApp } from "../.."; - -export class SmokeMarker extends CustomMarker { - #color: string; - - constructor(latlng: LatLngExpression, color: string, options?: MarkerOptions) { - super(latlng, options); - this.setZIndexOffset(9999); - this.#color = color; - window.setTimeout(() => { this.removeFrom(getApp().getMap()); }, 300000) /* Remove the smoke after 5 minutes */ - } - - createIcon() { - this.setIcon(new DivIcon({ - iconSize: [52, 52], - iconAnchor: [26, 52], - className: "leaflet-smoke-marker", - })); - var el = document.createElement("div"); - el.classList.add("ol-smoke-icon"); - el.setAttribute("data-color", this.#color); - var img = document.createElement("img"); - img.src = "/resources/theme/images/markers/smoke.svg"; - img.onload = () => SVGInjector(img); - el.appendChild(img); - this.getElement()?.appendChild(el); - } -} diff --git a/frontend/website/src/map/markers/targetmarker.ts b/frontend/website/src/map/markers/targetmarker.ts deleted file mode 100644 index 9b781f1c..00000000 --- a/frontend/website/src/map/markers/targetmarker.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet"; -import { CustomMarker } from "./custommarker"; - -export class TargetMarker extends CustomMarker { - constructor(latlng: LatLngExpression, options?: MarkerOptions) { - super(latlng, options); - this.setZIndexOffset(9999); - } - - createIcon() { - this.setIcon(new DivIcon({ - iconSize: [52, 52], - iconAnchor: [26, 26], - className: "leaflet-target-marker", - })); - var el = document.createElement("div"); - el.classList.add("ol-target-icon"); - this.getElement()?.appendChild(el); - } -} diff --git a/frontend/website/src/map/markers/temporaryunitmarker.ts b/frontend/website/src/map/markers/temporaryunitmarker.ts deleted file mode 100644 index f302d7ab..00000000 --- a/frontend/website/src/map/markers/temporaryunitmarker.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { CustomMarker } from "./custommarker"; -import { DivIcon, LatLng } from "leaflet"; -import { SVGInjector } from "@tanem/svg-injector"; -import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../../other/utils"; -import { getApp } from "../.."; - -export class TemporaryUnitMarker extends CustomMarker { - #name: string; - #coalition: string; - #commandHash: string|undefined = undefined; - #timer: number = 0; - - constructor(latlng: LatLng, name: string, coalition: string, commandHash?: string) { - super(latlng, {interactive: false}); - this.#name = name; - this.#coalition = coalition; - this.#commandHash = commandHash; - - if (commandHash !== undefined) - this.setCommandHash(commandHash) - } - - setCommandHash(commandHash: string) { - this.#commandHash = commandHash; - this.#timer = window.setInterval(() => { - if (this.#commandHash !== undefined) { - getApp().getServerManager().isCommandExecuted((res: any) => { - if (res.commandExecuted) { - this.removeFrom(getApp().getMap()); - window.clearInterval(this.#timer); - } - }, this.#commandHash) - } - }, 1000); - } - - createIcon() { - const category = getMarkerCategoryByName(this.#name); - const databaseEntry = getUnitDatabaseByCategory(category)?.getByName(this.#name); - - /* Set the icon */ - var icon = new DivIcon({ - className: 'leaflet-unit-icon', - iconAnchor: [25, 25], - iconSize: [50, 50], - }); - this.setIcon(icon); - - var el = document.createElement("div"); - el.classList.add("unit"); - el.setAttribute("data-object", `unit-${category}`); - el.setAttribute("data-coalition", this.#coalition); - - // Main icon - var unitIcon = document.createElement("div"); - unitIcon.classList.add("unit-icon"); - var img = document.createElement("img"); - - img.src = `/resources/theme/images/units/${databaseEntry?.markerFile ?? category}.svg`; - img.onload = () => SVGInjector(img); - unitIcon.appendChild(img); - unitIcon.toggleAttribute("data-rotate-to-heading", false); - el.append(unitIcon); - - // Short label - if (category == "aircraft" || category == "helicopter") { - var shortLabel = document.createElement("div"); - shortLabel.classList.add("unit-short-label"); - shortLabel.innerText = databaseEntry?.shortLabel || ""; - el.append(shortLabel); - } - - this.getElement()?.appendChild(el); - this.getElement()?.classList.add("ol-temporary-marker"); - } -} \ No newline at end of file diff --git a/frontend/website/src/map/rangecircle.ts b/frontend/website/src/map/rangecircle.ts deleted file mode 100644 index 2fa5d9b1..00000000 --- a/frontend/website/src/map/rangecircle.ts +++ /dev/null @@ -1,56 +0,0 @@ -// @ts-nocheck -// This is a horrible hack. But it is needed at the moment to ovveride a default behaviour of Leaflet. TODO please fix me the proper way. - -import { Circle, Point, Polyline } from 'leaflet'; - -/** - * This custom Circle object implements a faster render method for very big circles. When zoomed in, the default ctx.arc method - * is very slow since the circle is huge. Also, when zoomed in most of the circle points will be outside the screen and not needed. This - * simpler, faster renderer approximates the circle with line segements and only draws those currently visibile. - * A more refined version using arcs could be implemented but this works good enough. - */ -export class RangeCircle extends Circle { - _updatePath() { - if (!this._renderer._drawing || this._empty()) { return; } - var p = this._point, - ctx = this._renderer._ctx, - r = Math.max(Math.round(this._radius), 1), - s = (Math.max(Math.round(this._radiusY), 1) || r) / r; - - if (s !== 1) { - ctx.save(); - ctx.scale(1, s); - } - - let pathBegun = false; - let dtheta = Math.PI * 2 / 120; - for (let theta = 0; theta <= Math.PI * 2; theta += dtheta) { - let p1 = new Point(p.x + r * Math.cos(theta), p.y / s + r * Math.sin(theta)); - let p2 = new Point(p.x + r * Math.cos(theta + dtheta), p.y / s + r * Math.sin(theta + dtheta)); - let l1 = this._map.layerPointToLatLng(p1); - let l2 = this._map.layerPointToLatLng(p2); - let line = new Polyline([l1, l2]); - if (this._map.getBounds().intersects(line.getBounds())) { - if (!pathBegun) { - ctx.beginPath(); - ctx.moveTo(p1.x, p1.y); - pathBegun = true; - } - ctx.lineTo(p2.x, p2.y); - } - else { - if (pathBegun) { - this._renderer._fillStroke(ctx, this); - pathBegun = false; - } - } - } - - if (pathBegun) - this._renderer._fillStroke(ctx, this); - - if (s !== 1) - ctx.restore(); - - } -} \ No newline at end of file diff --git a/frontend/website/src/map/touchboxselect.ts b/frontend/website/src/map/touchboxselect.ts deleted file mode 100644 index f4a0cc20..00000000 --- a/frontend/website/src/map/touchboxselect.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { Map, Point } from 'leaflet'; -import { Handler } from 'leaflet'; -import { Util } from 'leaflet'; -import { DomUtil } from 'leaflet'; -import { DomEvent } from 'leaflet'; -import { LatLngBounds } from 'leaflet'; -import { Bounds } from 'leaflet'; - -export var TouchBoxSelect = Handler.extend({ - initialize: function (map: Map) { - this._map = map; - this._container = map.getContainer(); - this._pane = map.getPanes().overlayPane; - this._resetStateTimeout = 0; - this._doubleClicked = false; - map.on('unload', this._destroy, this); - }, - - addHooks: function () { - DomEvent.on(this._container, 'touchstart', this._onMouseDown, this); - }, - - removeHooks: function () { - DomEvent.off(this._container, 'touchstart', this._onMouseDown, this); - }, - - moved: function () { - return this._moved; - }, - - _destroy: function () { - DomUtil.remove(this._pane); - delete this._pane; - }, - - _resetState: function () { - this._resetStateTimeout = 0; - this._moved = false; - }, - - _clearDeferredResetState: function () { - if (this._resetStateTimeout !== 0) { - clearTimeout(this._resetStateTimeout); - this._resetStateTimeout = 0; - } - }, - - _onMouseDown: function (e: any) { - if ((e.which == 0)) { - this._map.fire('selectionstart'); - // Clear the deferred resetState if it hasn't executed yet, otherwise it - // will interrupt the interaction and orphan a box element in the container. - this._clearDeferredResetState(); - this._resetState(); - - DomUtil.disableTextSelection(); - DomUtil.disableImageDrag(); - - this._startPoint = this._getMousePosition(e); - - //@ts-ignore - DomEvent.on(document, { - contextmenu: DomEvent.stop, - touchmove: this._onMouseMove, - touchend: this._onMouseUp - }, this); - } else { - return false; - } - }, - - _onMouseMove: function (e: any) { - if (!this._moved) { - this._moved = true; - - this._box = DomUtil.create('div', 'leaflet-zoom-box', this._container); - DomUtil.addClass(this._container, 'leaflet-crosshair'); - } - - this._point = this._getMousePosition(e); - - var bounds = new Bounds(this._point, this._startPoint), - size = bounds.getSize(); - - if (bounds.min != undefined) - DomUtil.setPosition(this._box, bounds.min); - - this._box.style.width = size.x + 'px'; - this._box.style.height = size.y + 'px'; - }, - - _finish: function () { - if (this._moved) { - DomUtil.remove(this._box); - DomUtil.removeClass(this._container, 'leaflet-crosshair'); - } - - DomUtil.enableTextSelection(); - DomUtil.enableImageDrag(); - - //@ts-ignore - DomEvent.off(document, { - contextmenu: DomEvent.stop, - touchmove: this._onMouseMove, - touchend: this._onMouseUp - }, this); - }, - - _onMouseUp: function (e: any) { - if ((e.which !== 0)) { return; } - - this._finish(); - - if (!this._moved) { return; } - // Postpone to next JS tick so internal click event handling - // still see it as "moved". - window.setTimeout(Util.bind(this._resetState, this), 0); - var bounds = new LatLngBounds( - this._map.containerPointToLatLng(this._startPoint), - this._map.containerPointToLatLng(this._point)); - - this._map.fire('selectionend', { selectionBounds: bounds }); - }, - - _getMousePosition(e: any) { - var scale = DomUtil.getScale(this._container), offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y) - - return new Point( - // offset.left/top values are in page scale (like clientX/Y), - // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies). - (e.touches[0].clientX - offset.left) / scale.x - this._container.clientLeft, - (e.touches[0].clientY - offset.top) / scale.y - this._container.clientTop - ); - } -}); - diff --git a/frontend/website/src/mission/airbase.ts b/frontend/website/src/mission/airbase.ts deleted file mode 100644 index be3f7c55..00000000 --- a/frontend/website/src/mission/airbase.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { DivIcon } from 'leaflet'; -import { CustomMarker } from '../map/markers/custommarker'; -import { SVGInjector } from '@tanem/svg-injector'; -import { AirbaseChartData, AirbaseOptions } from '../interfaces'; - - -export class Airbase extends CustomMarker { - #name: string = ""; - #chartData: AirbaseChartData = { - elevation: "", - ICAO: "", - TACAN: "", - runways: [] - }; - #coalition: string = ""; - #hasChartDataBeenSet: boolean = false; - #properties: string[] = []; - #parkings: string[] = []; - - constructor(options: AirbaseOptions) { - super(options.position, { riseOnHover: true }); - - this.#name = options.name; - } - - chartDataHasBeenSet() { - return this.#hasChartDataBeenSet; - } - - createIcon() { - var icon = new DivIcon({ - className: 'leaflet-airbase-marker', - iconSize: [40, 40], - iconAnchor: [20, 20] - }); // Set the marker, className must be set to avoid white square - this.setIcon(icon); - - var el = document.createElement("div"); - el.classList.add("airbase-icon"); - el.setAttribute("data-object", "airbase"); - var img = document.createElement("img"); - img.src = "/resources/theme/images/markers/airbase.svg"; - img.onload = () => SVGInjector(img); - el.appendChild(img); - this.getElement()?.appendChild(el); - el.addEventListener( "mouseover", ( ev ) => { - document.dispatchEvent( new CustomEvent( "airbaseMouseover", { detail: this })); - }); - el.addEventListener( "mouseout", ( ev ) => { - document.dispatchEvent( new CustomEvent( "airbaseMouseout", { detail: this })); - }); - el.dataset.coalition = this.#coalition; - } - - setCoalition(coalition: string) { - this.#coalition = coalition; - (this.getElement()?.querySelector(".airbase-icon")).dataset.coalition = this.#coalition; - } - - getChartData() { - return this.#chartData; - } - - getCoalition() { - return this.#coalition; - } - - setName(name: string) { - this.#name = name; - } - - getName() { - return this.#name; - } - - setChartData(chartData: AirbaseChartData) { - this.#hasChartDataBeenSet = true; - this.#chartData = chartData; - } - - setProperties(properties: string[]) { - this.#properties = properties; - } - - getProperties() { - return this.#properties; - } - - setParkings(parkings: string[]) { - this.#parkings = parkings; - } - - getParkings() { - return this.#parkings; - } -} \ No newline at end of file diff --git a/frontend/website/src/mission/bullseye.ts b/frontend/website/src/mission/bullseye.ts deleted file mode 100644 index ccadfd77..00000000 --- a/frontend/website/src/mission/bullseye.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { DivIcon } from "leaflet"; -import { CustomMarker } from "../map/markers/custommarker"; -import { SVGInjector } from "@tanem/svg-injector"; - -export class Bullseye extends CustomMarker { - #coalition: string = ""; - - createIcon() { - var icon = new DivIcon({ - className: 'leaflet-bullseye-marker', - iconSize: [40, 40], - iconAnchor: [20, 20] - }); // Set the marker, className must be set to avoid white square - this.setIcon(icon); - - var el = document.createElement("div"); - el.classList.add("bullseye-icon"); - el.setAttribute("data-object", "bullseye"); - var img = document.createElement("img"); - img.src = "/resources/theme/images/markers/bullseye.svg"; - img.onload = () => SVGInjector(img); - el.appendChild(img); - this.getElement()?.appendChild(el); - } - - setCoalition(coalition: string) - { - this.#coalition = coalition; - ( this.getElement()?.querySelector(".bullseye-icon")).dataset.coalition = this.#coalition; - } - - getCoalition() - { - return this.#coalition; - } -} \ No newline at end of file diff --git a/frontend/website/src/mission/missionmanager.ts b/frontend/website/src/mission/missionmanager.ts deleted file mode 100644 index c37fac24..00000000 --- a/frontend/website/src/mission/missionmanager.ts +++ /dev/null @@ -1,326 +0,0 @@ -import { LatLng } from "leaflet"; -import { getApp } from ".."; -import { Airbase } from "./airbase"; -import { Bullseye } from "./bullseye"; -import { BLUE_COMMANDER, ERAS, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants"; -import { Dropdown } from "../controls/dropdown"; -import { groundUnitDatabase } from "../unit/databases/groundunitdatabase"; -import { createCheckboxOption, getCheckboxOptions } from "../other/utils"; -import { aircraftDatabase } from "../unit/databases/aircraftdatabase"; -import { helicopterDatabase } from "../unit/databases/helicopterdatabase"; -import { navyUnitDatabase } from "../unit/databases/navyunitdatabase"; -import { Popup } from "../popups/popup"; -import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionData } from "../interfaces"; - -/** The MissionManager */ -export class MissionManager { - #bullseyes: { [name: string]: Bullseye } = {}; - #airbases: { [name: string]: Airbase } = {}; - #theatre: string = ""; - #dateAndTime: DateAndTime = {date: {Year: 0, Month: 0, Day: 0}, time: {h: 0, m: 0, s: 0}, startTime: 0, elapsedTime: 0}; - #commandModeOptions: CommandModeOptions = {commandMode: NONE, restrictSpawns: false, restrictToCoalition: false, setupTime: Infinity, spawnPoints: {red: Infinity, blue: Infinity}, eras: []}; - #remainingSetupTime: number = 0; - #spentSpawnPoint: number = 0; - #commandModeDialog: HTMLElement; - #commandModeErasDropdown: Dropdown; - #coalitions: {red: string[], blue: string[]} = {red: [], blue: []}; - - constructor() { - document.addEventListener("applycommandModeOptions", () => this.#applycommandModeOptions()); - document.addEventListener("showCommandModeDialog", () => this.showCommandModeDialog()); - document.addEventListener("toggleSpawnRestrictions", (ev:CustomEventInit) => { - this.#toggleSpawnRestrictions(ev.detail._element.checked) - }); - - /* command-mode settings dialog */ - this.#commandModeDialog = document.querySelector("#command-mode-settings-dialog") as HTMLElement; - this.#commandModeErasDropdown = new Dropdown("command-mode-era-options", () => {}); - - } - - /** Update location of bullseyes - * - * @param object - */ - updateBullseyes(data: BullseyesData) { - const commandMode = getApp().getMissionManager().getCommandModeOptions().commandMode; - for (let idx in data.bullseyes) { - const bullseye = data.bullseyes[idx]; - - // Prevent Red and Blue coalitions seeing each other's bulleye(s) - if ((bullseye.coalition === "red" && commandMode === BLUE_COMMANDER) - || (bullseye.coalition === "blue" && commandMode === RED_COMMANDER)) { - continue; - } - - if (!(idx in this.#bullseyes)) - this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getApp().getMap()); - - if (bullseye.latitude && bullseye.longitude && bullseye.coalition) { - this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude)); - this.#bullseyes[idx].setCoalition(bullseye.coalition); - } - } - } - - /** Update airbase information - * - * @param object - */ - updateAirbases(data: AirbasesData) { - for (let idx in data.airbases) { - var airbase = data.airbases[idx] - if (this.#airbases[airbase.callsign] === undefined && airbase.callsign != '') { - this.#airbases[airbase.callsign] = new Airbase({ - position: new LatLng(airbase.latitude, airbase.longitude), - name: airbase.callsign - }).addTo(getApp().getMap()); - this.#airbases[airbase.callsign].on('contextmenu', (e) => this.#onAirbaseClick(e)); - this.#loadAirbaseChartData(airbase.callsign); - } - - if (this.#airbases[airbase.callsign] != undefined && airbase.latitude && airbase.longitude && airbase.coalition) { - this.#airbases[airbase.callsign].setLatLng(new LatLng(airbase.latitude, airbase.longitude)); - this.#airbases[airbase.callsign].setCoalition(airbase.coalition); - } - } - } - - /** Update mission information - * - * @param object - */ - updateMission(data: MissionData) { - if (data.mission) { - - /* Set the mission theatre */ - if (data.mission.theatre != this.#theatre) { - this.#theatre = data.mission.theatre; - getApp().getMap().setTheatre(this.#theatre); - (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Map set to " + this.#theatre); - } - - /* Set the date and time data */ - this.#dateAndTime = data.mission.dateAndTime; - data.mission.dateAndTime.time.s -= 1; // ED has seconds 1-60 and not 0-59?! - - /* Set the coalition countries */ - this.#coalitions = data.mission.coalitions; - - /* Set the command mode options */ - this.#setcommandModeOptions(data.mission.commandModeOptions); - this.#remainingSetupTime = this.getCommandModeOptions().setupTime - this.getDateAndTime().elapsedTime; - var commandModePhaseEl = document.querySelector("#command-mode-phase") as HTMLElement; - if (commandModePhaseEl) { - if (this.#remainingSetupTime > 0) { - var remainingTime = `-${new Date(this.#remainingSetupTime * 1000).toISOString().substring(14, 19)}`; - commandModePhaseEl.dataset.remainingTime = remainingTime; - } - - commandModePhaseEl.classList.toggle("setup-phase", this.#remainingSetupTime > 0 && this.getCommandModeOptions().restrictSpawns); - //commandModePhaseEl.classList.toggle("game-commenced", this.#remainingSetupTime <= 0 || !this.getCommandModeOptions().restrictSpawns); - //commandModePhaseEl.classList.toggle("no-restrictions", !this.getCommandModeOptions().restrictSpawns); - } - } - } - - /** Get the bullseyes set in this theatre - * - * @returns object - */ - getBullseyes() { - return this.#bullseyes; - } - - /** Get the airbases in this theatre - * - * @returns object - */ - getAirbases() { - return this.#airbases; - } - - /** Get the options/settings as set in the command mode - * - * @returns object - */ - getCommandModeOptions() { - return this.#commandModeOptions; - } - - /** Get the current date and time of the mission (based on local time) - * - * @returns object - */ - getDateAndTime() { - return this.#dateAndTime; - } - - /** - * Get the number of seconds left of setup time - * @returns number - */ - getRemainingSetupTime() { - return this.#remainingSetupTime; - } - - /** Get an object with the coalitions in it - * - * @returns object - */ - getCoalitions() { - return this.#coalitions; - } - - /** Get the current theatre (map) name - * - * @returns string - */ - getTheatre() { - return this.#theatre; - } - - getAvailableSpawnPoints() { - if (this.getCommandModeOptions().commandMode === GAME_MASTER) - return Infinity; - else if (this.getCommandModeOptions().commandMode === BLUE_COMMANDER) - return this.getCommandModeOptions().spawnPoints.blue - this.#spentSpawnPoint; - else if (this.getCommandModeOptions().commandMode === RED_COMMANDER) - return this.getCommandModeOptions().spawnPoints.red - this.#spentSpawnPoint; - else - return 0; - } - - getCommandedCoalition() { - if (this.getCommandModeOptions().commandMode === BLUE_COMMANDER) - return "blue"; - else if (this.getCommandModeOptions().commandMode === RED_COMMANDER) - return "red"; - else - return "all"; - } - - refreshSpawnPoints() { - var spawnPointsEl = document.querySelector("#spawn-points"); - if (spawnPointsEl) { - spawnPointsEl.textContent = `${this.getAvailableSpawnPoints()}`; - } - } - - setSpentSpawnPoints(spawnPoints: number) { - this.#spentSpawnPoint = spawnPoints; - this.refreshSpawnPoints(); - } - - showCommandModeDialog() { - const options = this.getCommandModeOptions() - const { restrictSpawns, restrictToCoalition, setupTime } = options; - this.#toggleSpawnRestrictions(restrictSpawns); - - /* Create the checkboxes to select the unit eras */ - this.#commandModeErasDropdown.setOptionsElements( - ERAS.sort((eraA, eraB) => { - return ( eraA.chronologicalOrder > eraB.chronologicalOrder ) ? 1 : -1; - }).map((era) => { - return createCheckboxOption(era.name, `Enable ${era} units spawns`, this.getCommandModeOptions().eras.includes(era.name)); - }) - ); - - this.#commandModeDialog.classList.remove("hide"); - - const restrictSpawnsCheckbox = this.#commandModeDialog.querySelector("#restrict-spawns")?.querySelector("input") as HTMLInputElement; - const restrictToCoalitionCheckbox = this.#commandModeDialog.querySelector("#restrict-to-coalition")?.querySelector("input") as HTMLInputElement; - const blueSpawnPointsInput = this.#commandModeDialog.querySelector("#blue-spawn-points")?.querySelector("input") as HTMLInputElement; - const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement; - const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement; - - restrictSpawnsCheckbox.checked = restrictSpawns; - restrictToCoalitionCheckbox.checked = restrictToCoalition; - blueSpawnPointsInput.value = String(options.spawnPoints.blue); - redSpawnPointsInput.value = String(options.spawnPoints.red); - setupTimeInput.value = String(Math.floor(setupTime / 60.0)); - } - - #applycommandModeOptions() { - this.#commandModeDialog.classList.add("hide"); - - const restrictSpawnsCheckbox = this.#commandModeDialog.querySelector("#restrict-spawns")?.querySelector("input") as HTMLInputElement; - const restrictToCoalitionCheckbox = this.#commandModeDialog.querySelector("#restrict-to-coalition")?.querySelector("input") as HTMLInputElement; - const blueSpawnPointsInput = this.#commandModeDialog.querySelector("#blue-spawn-points")?.querySelector("input") as HTMLInputElement; - const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement; - const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement; - - var eras: string[] = []; - const enabledEras = getCheckboxOptions(this.#commandModeErasDropdown); - Object.keys(enabledEras).forEach((key: string) => {if (enabledEras[key]) eras.push(key)}); - getApp().getServerManager().setCommandModeOptions(restrictSpawnsCheckbox.checked, restrictToCoalitionCheckbox.checked, {blue: parseInt(blueSpawnPointsInput.value), red: parseInt(redSpawnPointsInput.value)}, eras, parseInt(setupTimeInput.value) * 60); - } - - #setcommandModeOptions(commandModeOptions: CommandModeOptions) { - /* Refresh all the data if we have exited the NONE state */ - var requestRefresh = false; - if (this.#commandModeOptions.commandMode === NONE && commandModeOptions.commandMode !== NONE) - requestRefresh = true; - - /* Refresh the page if we have lost Game Master priviledges */ - if (this.#commandModeOptions.commandMode === GAME_MASTER && commandModeOptions.commandMode !== GAME_MASTER) - location.reload(); - - /* Check if any option has changed */ - var commandModeOptionsChanged = (!commandModeOptions.eras.every((value: string, idx: number) => {return value === this.getCommandModeOptions().eras[idx]}) || - commandModeOptions.spawnPoints.red !== this.getCommandModeOptions().spawnPoints.red || - commandModeOptions.spawnPoints.blue !== this.getCommandModeOptions().spawnPoints.blue || - commandModeOptions.restrictSpawns !== this.getCommandModeOptions().restrictSpawns || - commandModeOptions.restrictToCoalition !== this.getCommandModeOptions().restrictToCoalition); - - this.#commandModeOptions = commandModeOptions; - this.setSpentSpawnPoints(0); - this.refreshSpawnPoints(); - - if (commandModeOptionsChanged) { - document.dispatchEvent(new CustomEvent("commandModeOptionsChanged", { detail: this })); - document.getElementById("command-mode-toolbar")?.classList.remove("hide"); - const el = document.getElementById("command-mode"); - if (el) { - el.dataset.mode = commandModeOptions.commandMode; - el.textContent = commandModeOptions.commandMode.toUpperCase(); - } - } - - document.querySelector("#spawn-points-container")?.classList.toggle("hide", this.getCommandModeOptions().commandMode === GAME_MASTER || !this.getCommandModeOptions().restrictSpawns); - document.querySelector("#command-mode-settings-button")?.classList.toggle("hide", this.getCommandModeOptions().commandMode !== GAME_MASTER); - - if (requestRefresh) - getApp().getServerManager().refreshAll(); - } - - #onAirbaseClick(e: any) { - getApp().getMap().showAirbaseContextMenu(e.sourceTarget, e.originalEvent.x, e.originalEvent.y, e.latlng); - } - - #loadAirbaseChartData(callsign: string) { - if ( !this.#theatre ) { - return; - } - - var xhr = new XMLHttpRequest(); - xhr.open('GET', `api/airbases/${this.#theatre.toLowerCase()}/${callsign}`, true); - xhr.responseType = 'json'; - xhr.onload = () => { - var status = xhr.status; - if (status === 200) { - const data = xhr.response; - this.getAirbases()[callsign].setChartData(data); - } else { - console.error(`Error retrieving data for ${callsign} airbase`) - } - }; - xhr.send(); - } - - #toggleSpawnRestrictions(restrictionsEnabled:boolean) { - this.#commandModeDialog.querySelectorAll("input, label, .ol-select").forEach( el => { - if (!el.closest("#restrict-spawns")) el.toggleAttribute("disabled", !restrictionsEnabled); - }); - } -} \ No newline at end of file diff --git a/frontend/website/src/olympusapp.ts b/frontend/website/src/olympusapp.ts deleted file mode 100644 index cb728812..00000000 --- a/frontend/website/src/olympusapp.ts +++ /dev/null @@ -1,485 +0,0 @@ -import { Map } from "./map/map"; -import { MissionManager } from "./mission/missionmanager"; -import { ConnectionStatusPanel } from "./panels/connectionstatuspanel"; -import { HotgroupPanel } from "./panels/hotgrouppanel"; -import { LogPanel } from "./panels/logpanel"; -import { MouseInfoPanel } from "./panels/mouseinfopanel"; -import { ServerStatusPanel } from "./panels/serverstatuspanel"; -import { UnitControlPanel } from "./panels/unitcontrolpanel"; -import { UnitInfoPanel } from "./panels/unitinfopanel"; -import { PluginsManager } from "./plugin/pluginmanager"; -import { Popup } from "./popups/popup"; -import { ShortcutManager } from "./shortcut/shortcutmanager"; -import { CommandModeToolbar } from "./toolbars/commandmodetoolbar"; -import { PrimaryToolbar } from "./toolbars/primarytoolbar"; -import { UnitsManager } from "./unit/unitsmanager"; -import { WeaponsManager } from "./weapon/weaponsmanager"; -import { Manager } from "./other/manager"; -import { SVGInjector } from "@tanem/svg-injector"; -import { ServerManager } from "./server/servermanager"; -import { sha256 } from 'js-sha256'; - -import { BLUE_COMMANDER, FILL_SELECTED_RING, GAME_MASTER, HIDE_UNITS_SHORT_RANGE_RINGS, RED_COMMANDER, SHOW_UNITS_ACQUISITION_RINGS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_LABELS } from "./constants/constants"; -import { aircraftDatabase } from "./unit/databases/aircraftdatabase"; -import { helicopterDatabase } from "./unit/databases/helicopterdatabase"; -import { groundUnitDatabase } from "./unit/databases/groundunitdatabase"; -import { navyUnitDatabase } from "./unit/databases/navyunitdatabase"; -import { UnitListPanel } from "./panels/unitlistpanel"; -import { ContextManager } from "./context/contextmanager"; -import { Context } from "./context/context"; -var VERSION = "{{OLYMPUS_VERSION_NUMBER}}"; - -export class OlympusApp { - /* Global data */ - #activeCoalition: string = "blue"; - #latestVersion: string|undefined = undefined; - #config: any = {}; - - /* Main leaflet map, extended by custom methods */ - #map: Map | null = null; - - /* Managers */ - #contextManager!: ContextManager; - #dialogManager!: Manager; - #missionManager: MissionManager | null = null; - #panelsManager: Manager | null = null; - #pluginsManager: PluginsManager | null = null; - #popupsManager: Manager | null = null; - #serverManager: ServerManager | null = null; - #shortcutManager!: ShortcutManager; - #toolbarsManager: Manager | null = null; - #unitsManager: UnitsManager | null = null; - #weaponsManager: WeaponsManager | null = null; - - constructor() { - } - - // TODO add checks on null - getDialogManager() { - return this.#dialogManager as Manager; - } - - getMap() { - return this.#map as Map; - } - - getCurrentContext() { - return this.getContextManager().getCurrentContext() as Context; - } - - getContextManager() { - return this.#contextManager as ContextManager; - } - - getServerManager() { - return this.#serverManager as ServerManager; - } - - getPanelsManager() { - return this.#panelsManager as Manager; - } - - getPopupsManager() { - return this.#popupsManager as Manager; - } - - getToolbarsManager() { - return this.#toolbarsManager as Manager; - } - - getShortcutManager() { - return this.#shortcutManager as ShortcutManager; - } - - getUnitsManager() { - return this.#unitsManager as UnitsManager; - } - - getWeaponsManager() { - return this.#weaponsManager as WeaponsManager; - } - - getMissionManager() { - return this.#missionManager as MissionManager; - } - - getPluginsManager() { - return this.#pluginsManager as PluginsManager; - } - - /** Set the active coalition, i.e. the currently controlled coalition. A game master can change the active coalition, while a commander is bound to his/her coalition - * - * @param newActiveCoalition - */ - setActiveCoalition(newActiveCoalition: string) { - if (this.getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER) { - this.#activeCoalition = newActiveCoalition; - document.dispatchEvent(new CustomEvent("activeCoalitionChanged")); - } - } - - /** - * - * @returns The active coalition - */ - getActiveCoalition() { - if (this.getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER) - return this.#activeCoalition; - else { - if (this.getMissionManager().getCommandModeOptions().commandMode == BLUE_COMMANDER) - return "blue"; - else if (this.getMissionManager().getCommandModeOptions().commandMode == RED_COMMANDER) - return "red"; - else - return "neutral"; - } - } - - /** - * - * @returns The aircraft database - */ - getAircraftDatabase() { - return aircraftDatabase; - } - - /** - * - * @returns The helicopter database - */ - getHelicopterDatabase() { - return helicopterDatabase; - } - - /** - * - * @returns The ground unit database - */ - getGroundUnitDatabase() { - return groundUnitDatabase; - } - - /** - * - * @returns The navy unit database - */ - getNavyUnitDatabase() { - return navyUnitDatabase; - } - - /** Set a message in the login splash screen - * - * @param status The message to show in the login splash screen - */ - setLoginStatus(status: string) { - const el = document.querySelector("#login-status") as HTMLElement; - if (el) - el.dataset["status"] = status; - } - - start() { - /* Initialize base functionalitites */ - this.#contextManager = new ContextManager(); - this.#contextManager.add( "olympus", {} ); - - this.#map = new Map('map-container'); - - this.#missionManager = new MissionManager(); - this.#panelsManager = new Manager(); - this.#popupsManager = new Manager(); - this.#serverManager = new ServerManager(); - this.#shortcutManager = new ShortcutManager(); - this.#toolbarsManager = new Manager(); - this.#unitsManager = new UnitsManager(); - this.#weaponsManager = new WeaponsManager(); - - // Toolbars - this.getToolbarsManager().add("primaryToolbar", new PrimaryToolbar("primary-toolbar")) - .add("commandModeToolbar", new CommandModeToolbar("command-mode-toolbar")); - - // Panels - this.getPanelsManager() - .add("connectionStatus", new ConnectionStatusPanel("connection-status-panel")) - .add("hotgroup", new HotgroupPanel("hotgroup-panel")) - .add("mouseInfo", new MouseInfoPanel("mouse-info-panel")) - .add("log", new LogPanel("log-panel")) - .add("serverStatus", new ServerStatusPanel("server-status-panel")) - .add("unitControl", new UnitControlPanel("unit-control-panel")) - .add("unitInfo", new UnitInfoPanel("unit-info-panel")) - .add("unitList", new UnitListPanel("unit-list-panel", "unit-list-panel-content")) - - // Popups - this.getPopupsManager() - .add("infoPopup", new Popup("info-popup")); - - this.#pluginsManager = new PluginsManager(); - - /* Set the address of the server */ - this.getServerManager().setAddress(window.location.href.split('?')[0]); - - /* Setup all global events */ - this.#setupEvents(); - - /* Set the splash background image to a random image */ - let splashScreen = document.getElementById("splash-screen") as HTMLElement; - let i = Math.round(Math.random() * 7 + 1); - - new Promise((resolve, reject) => { - const image = new Image(); - image.addEventListener('load', resolve); - image.addEventListener('error', resolve); - image.src = `/resources/theme/images/splash/${i}.jpg`; - }).then(() => { - splashScreen.style.backgroundImage = `url('/resources/theme/images/splash/${i}.jpg')`; - let loadingScreen = document.getElementById("loading-screen") as HTMLElement; - loadingScreen.classList.add("fade-out"); - window.setInterval(() => { loadingScreen.classList.add("hide"); }, 1000); - }) - - /* Check if we are running the latest version */ - const request = new Request("https://raw.githubusercontent.com/Pax1601/DCSOlympus/main/version.json"); - fetch(request).then((response) => { - if (response.status === 200) { - return response.json(); - } else { - throw new Error("Error connecting to Github to retrieve latest version"); - } - }).then((res) => { - this.#latestVersion = res["version"]; - const latestVersionSpan = document.getElementById("latest-version") as HTMLElement; - if (latestVersionSpan) { - latestVersionSpan.innerHTML = this.#latestVersion ?? "Unknown"; - latestVersionSpan.classList.toggle("new-version", this.#latestVersion !== VERSION); - } - }) - - /* Load the config file from the server */ - const configRequest = new Request(location.href + "resources/config"); - fetch(configRequest).then((response) => { - if (response.status === 200) { - return response.json(); - } else { - throw new Error("Error retrieving config file"); - } - }).then((res) => { - this.#config = res; - document.dispatchEvent(new CustomEvent("configLoaded")); - }) - } - - #setupEvents() { - /* Generic clicks */ - document.addEventListener("click", (ev) => { - if (ev instanceof MouseEvent && ev.target instanceof HTMLElement) { - const target = ev.target; - - if (target.classList.contains("olympus-dialog-close")) { - target.closest("div.olympus-dialog")?.classList.add("hide"); - } - - const triggerElement = target.closest("[data-on-click]"); - - if (triggerElement instanceof HTMLElement) { - const eventName: string = triggerElement.dataset.onClick || ""; - let params = JSON.parse(triggerElement.dataset.onClickParams || "{}"); - params._element = triggerElement; - - if (eventName) { - document.dispatchEvent(new CustomEvent(eventName, { - detail: params - })); - } - } - } - }); - - const shortcutManager = this.getShortcutManager(); - shortcutManager.addKeyboardShortcut("togglePause", { - "altKey": false, - "callback": () => { - this.getServerManager().setPaused(!this.getServerManager().getPaused()); - }, - "code": "Space", - "context": "olympus", - "ctrlKey": false - }).addKeyboardShortcut("deselectAll", { - "callback": (ev: KeyboardEvent) => { - this.getUnitsManager().deselectAllUnits(); - }, - "code": "Escape", - "context": "olympus" - }).addKeyboardShortcut("toggleUnitLabels", { - "altKey": false, - "callback": () => { - const chk = document.querySelector(`label[title="${SHOW_UNIT_LABELS}"] input[type="checkbox"]`); - if (chk instanceof HTMLElement) { - chk.click(); - } - }, - "code": "KeyL", - "context": "olympus", - "ctrlKey": false, - "shiftKey": false - }).addKeyboardShortcut("toggleAcquisitionRings", { - "altKey": false, - "callback": () => { - const chk = document.querySelector(`label[title="${SHOW_UNITS_ACQUISITION_RINGS}"] input[type="checkbox"]`); - if (chk instanceof HTMLElement) { - chk.click(); - } - }, - "code": "KeyE", - "context": "olympus", - "ctrlKey": false, - "shiftKey": false - }).addKeyboardShortcut("toggleEngagementRings", { - "altKey": false, - "callback": () => { - const chk = document.querySelector(`label[title="${SHOW_UNITS_ENGAGEMENT_RINGS}"] input[type="checkbox"]`); - if (chk instanceof HTMLElement) { - chk.click(); - } - }, - "code": "KeyQ", - "context": "olympus", - "ctrlKey": false, - "shiftKey": false - }).addKeyboardShortcut("toggleHideShortEngagementRings", { - "altKey": false, - "callback": () => { - const chk = document.querySelector(`label[title="${HIDE_UNITS_SHORT_RANGE_RINGS}"] input[type="checkbox"]`); - if (chk instanceof HTMLElement) { - chk.click(); - } - }, - "code": "KeyR", - "context": "olympus", - "ctrlKey": false, - "shiftKey": false - }).addKeyboardShortcut("toggleFillEngagementRings", { - "altKey": false, - "callback": () => { - const chk = document.querySelector(`label[title="${FILL_SELECTED_RING}"] input[type="checkbox"]`); - if (chk instanceof HTMLElement) { - chk.click(); - } - }, - "code": "KeyF", - "context": "olympus", - "ctrlKey": false, - "shiftKey": false - }).addKeyboardShortcut("increaseCameraZoom", { - "altKey": true, - "callback": () => { - this.getMap().increaseCameraZoom(); - }, - "code": "Equal", - "context": "olympus", - "ctrlKey": false, - "shiftKey": false - }).addKeyboardShortcut("decreaseCameraZoom", { - "altKey": true, - "callback": () => { - this.getMap().decreaseCameraZoom(); - }, - "code": "Minus", - "context": "olympus", - "ctrlKey": false, - "shiftKey": false - }); - - ["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => { - shortcutManager.addKeyboardShortcut(`pan${code}keydown`, { - "altKey": false, - "callback": (ev: KeyboardEvent) => { - this.getMap().handleMapPanning(ev); - }, - "code": code, - "context": "olympus", - "ctrlKey": false, - "event": "keydown" - }); - - shortcutManager.addKeyboardShortcut(`pan${code}keyup`, { - "callback": (ev: KeyboardEvent) => { - this.getMap().handleMapPanning(ev); - }, - "code": code, - "context": "olympus" - }); - }); - - const digits = ["Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9"]; - - digits.forEach(code => { - shortcutManager.addKeyboardShortcut(`hotgroup${code}`, { - "altKey": false, - "callback": (ev: KeyboardEvent) => { - if (ev.ctrlKey && ev.shiftKey) - this.getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5)), false); // "Select hotgroup X in addition to any units already selected" - else if (ev.ctrlKey && !ev.shiftKey) - this.getUnitsManager().setHotgroup(parseInt(ev.code.substring(5))); // "These selected units are hotgroup X (forget any previous membership)" - else if (!ev.ctrlKey && ev.shiftKey) - this.getUnitsManager().addToHotgroup(parseInt(ev.code.substring(5))); // "Add (append) these units to hotgroup X (in addition to any existing members)" - else - this.getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5))); // "Select hotgroup X, deselect any units not in it." - }, - "code": code - }); - - // Stop hotgroup controls sending the browser to another tab - document.addEventListener("keydown", (ev: KeyboardEvent) => { - if (ev.code === code && ev.ctrlKey === true && ev.altKey === false && ev.shiftKey === false) { - ev.preventDefault(); - } - }); - }); - - // TODO: move from here in dedicated class - document.addEventListener("closeDialog", (ev: CustomEventInit) => { - ev.detail._element.closest(".ol-dialog").classList.add("hide"); - document.getElementById("gray-out")?.classList.toggle("hide", true); - }); - - /* Try and connect with the Olympus REST server */ - const loginForm = document.getElementById("authentication-form"); - if (loginForm instanceof HTMLFormElement) { - loginForm.addEventListener("submit", (ev:SubmitEvent) => { - ev.preventDefault(); - ev.stopPropagation(); - var hash = sha256.create(); - const username = (loginForm.querySelector("#username") as HTMLInputElement).value; - const password = hash.update((loginForm.querySelector("#password") as HTMLInputElement).value).hex(); - - // Update the user credentials - this.getServerManager().setCredentials(username, password); - - // Start periodically requesting updates - this.getServerManager().startUpdate(); - - this.setLoginStatus("connecting"); - }); - } else { - console.error("Unable to find login form."); - } - - /* Reload the page, used to mimic a restart of the app */ - document.addEventListener("reloadPage", () => { - location.reload(); - }) - - /* Inject the svgs with the corresponding svg code. This allows to dynamically manipulate the svg, like changing colors */ - document.querySelectorAll("[inject-svg]").forEach((el: Element) => { - var img = el as HTMLImageElement; - var isLoaded = img.complete; - if (isLoaded) - SVGInjector(img); - else - img.addEventListener("load", () => { SVGInjector(img); }); - }) - } - - getConfig() { - return this.#config; - } -} \ No newline at end of file diff --git a/frontend/website/src/other/eventsmanager.ts b/frontend/website/src/other/eventsmanager.ts deleted file mode 100644 index 9173b2b1..00000000 --- a/frontend/website/src/other/eventsmanager.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Manager } from "./manager"; - -export abstract class EventsManager extends Manager { - constructor() { - super(); - } -} \ No newline at end of file diff --git a/frontend/website/src/other/manager.ts b/frontend/website/src/other/manager.ts deleted file mode 100644 index c889e713..00000000 --- a/frontend/website/src/other/manager.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Context } from "../context/context"; - -export class Manager { - - #items: { [key: string]: any } = {}; - - constructor() { - - } - - add(name: string, item: any) { - const regex = new RegExp("^[a-z][a-z0-9]{2,}$", "i"); - if (regex.test(name) === false) { - throw new Error(`Item name "${name}" does not match regex: ${regex.toString()}.`); - } - - if (this.#items.hasOwnProperty(name)) { - throw new Error(`Item with name "${name}" already exists.`); - } - - this.#items[name] = item; - return this; - } - - get(name: string) { - if (this.#items.hasOwnProperty(name)) { - return this.#items[name]; - } else { - return false; - } - } - - getAll() { - return this.#items; - } - -} \ No newline at end of file diff --git a/frontend/website/src/other/utils.ts b/frontend/website/src/other/utils.ts deleted file mode 100644 index 6908f20f..00000000 --- a/frontend/website/src/other/utils.ts +++ /dev/null @@ -1,582 +0,0 @@ -import { LatLng, Point, Polygon } from "leaflet"; -import * as turf from "@turf/turf"; -import { UnitDatabase } from "../unit/databases/unitdatabase"; -import { aircraftDatabase } from "../unit/databases/aircraftdatabase"; -import { helicopterDatabase } from "../unit/databases/helicopterdatabase"; -import { groundUnitDatabase } from "../unit/databases/groundunitdatabase"; -import { Buffer } from "buffer"; -import { GROUND_UNIT_AIR_DEFENCE_REGEX, ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants"; -import { Dropdown } from "../controls/dropdown"; -import { navyUnitDatabase } from "../unit/databases/navyunitdatabase"; -import { DateAndTime, UnitBlueprint } from "../interfaces"; -import { Slider } from "../controls/slider"; - -// comment -const usng = require("usng.js"); - -export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) { - const φ1 = deg2rad(lat1); // φ, λ in radians - const φ2 = deg2rad(lat2); - const λ1 = deg2rad(lon1); // φ, λ in radians - const λ2 = deg2rad(lon2); - const y = Math.sin(λ2 - λ1) * Math.cos(φ2); - const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(λ2 - λ1); - const θ = Math.atan2(y, x); - const brng = (rad2deg(θ) + 360) % 360; // in degrees - - return brng; -} - -export function distance(lat1: number, lon1: number, lat2: number, lon2: number) { - const R = 6371e3; // metres - const φ1 = deg2rad(lat1); // φ, λ in radians - const φ2 = deg2rad(lat2); - const Δφ = deg2rad(lat2 - lat1); - const Δλ = deg2rad(lon2 - lon1); - - const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - - const d = R * c; // in metres - - return d; -} - -export function bearingAndDistanceToLatLng(lat: number, lon: number, brng: number, dist: number) { - const R = 6371e3; // metres - const φ1 = deg2rad(lat); // φ, λ in radians - const λ1 = deg2rad(lon); - const φ2 = Math.asin(Math.sin(φ1) * Math.cos(dist / R) + Math.cos(φ1) * Math.sin(dist / R) * Math.cos(brng)); - const λ2 = λ1 + Math.atan2(Math.sin(brng) * Math.sin(dist / R) * Math.cos(φ1), Math.cos(dist / R) - Math.sin(φ1) * Math.sin(φ2)); - - return new LatLng(rad2deg(φ2), rad2deg(λ2)); -} - -export function ConvertDDToDMS(D: number, lng: boolean) { - var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N"; - var deg = 0 | (D < 0 ? (D = -D) : D); - var min = 0 | (((D += 1e-9) % 1) * 60); - var sec = (0 | (((D * 60) % 1) * 6000)) / 100; - var dec = Math.round((sec - Math.floor(sec)) * 100); - var sec = Math.floor(sec); - if (lng) - return dir + zeroPad(deg, 3) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\""; - else - return dir + zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\""; -} - -export function dataPointMap(container: HTMLElement, data: any) { - Object.keys(data).forEach((key) => { - const val = "" + data[key]; // Ensure a string - container.querySelectorAll(`[data-point="${key}"]`).forEach(el => { - // We could probably have options here - if (el instanceof HTMLInputElement) { - el.value = val; - } else if (el instanceof HTMLElement) { - el.innerText = val; - } - }); - }); -} - -export function deg2rad(deg: number) { - var pi = Math.PI; - return deg * (pi / 180); -} - -export function rad2deg(rad: number) { - var pi = Math.PI; - return rad / (pi / 180); -} - -export function generateUUIDv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); -} - -export function keyEventWasInInput(event: KeyboardEvent) { - const target = event.target; - return (target instanceof HTMLElement && (["INPUT", "TEXTAREA"].includes(target.nodeName))); -} - -export function reciprocalHeading(heading: number): number { - return heading > 180 ? heading - 180 : heading + 180; -} - -/** - * Prepend numbers to the start of a string - * - * @param num subject number - * @param places places to pad - * @param decimal whether this is a decimal number or not - * - * */ -export const zeroAppend = function (num: number, places: number, decimal: boolean = false) { - var string = decimal ? num.toFixed(2) : String(num); - while (string.length < places) { - string = "0" + string; - } - return string; -} - -export const zeroPad = function (num: number, places: number) { - var string = String(num); - while (string.length < places) { - string += "0"; - } - return string; -} - -export function similarity(s1: string, s2: string) { - var longer = s1; - var shorter = s2; - if (s1.length < s2.length) { - longer = s2; - shorter = s1; - } - var longerLength = longer.length; - if (longerLength == 0) { - return 1.0; - } - return (longerLength - editDistance(longer, shorter)) / longerLength; -} - -export function editDistance(s1: string, s2: string) { - s1 = s1.toLowerCase(); - s2 = s2.toLowerCase(); - - var costs = new Array(); - for (var i = 0; i <= s1.length; i++) { - var lastValue = i; - for (var j = 0; j <= s2.length; j++) { - if (i == 0) - costs[j] = j; - else { - if (j > 0) { - var newValue = costs[j - 1]; - if (s1.charAt(i - 1) != s2.charAt(j - 1)) - newValue = Math.min(Math.min(newValue, lastValue), - costs[j]) + 1; - costs[j - 1] = lastValue; - lastValue = newValue; - } - } - } - if (i > 0) - costs[s2.length] = lastValue; - } - return costs[s2.length]; -} - -export type MGRS = { - bandLetter: string, - columnLetter: string, - easting: string, - groups: string[], - northing: string, - precision: number, - rowLetter: string, - string: string, - zoneNumber: string -} - -export function latLngToMGRS(lat: number, lng: number, precision: number = 4): MGRS | false { - - if (precision < 0 || precision > 6) { - console.error("latLngToMGRS: precision must be a number >= 0 and <= 6. Given precision: " + precision); - return false; - } - - const mgrs = new usng.Converter().LLtoMGRS(lat, lng, precision); - const match = mgrs.match(new RegExp(`^(\\d{2})([A-Z])([A-Z])([A-Z])(\\d+)$`)); - const easting = match[5].substr(0, match[5].length / 2); - const northing = match[5].substr(match[5].length / 2); - - let output: MGRS = { - bandLetter: match[2], - columnLetter: match[3], - groups: [match[1] + match[2], match[3] + match[4], easting, northing], - easting: easting, - northing: northing, - precision: precision, - rowLetter: match[4], - string: match[0], - zoneNumber: match[1] - } - - return output; -} - -export function latLngToUTM(lat: number, lng: number) { - return new usng.Converter().LLtoUTM(lat, lng); -} - -export function latLngToMercator(lat: number, lng: number): { x: number, y: number } { - var rMajor = 6378137; //Equatorial Radius, WGS84 - var shift = Math.PI * rMajor; - var x = lng * shift / 180; - var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180); - y = y * shift / 180; - - return { x: x, y: y }; -} - -export function mercatorToLatLng(x: number, y: number) { - var rMajor = 6378137; //Equatorial Radius, WGS84 - var shift = Math.PI * rMajor; - var lng = x / shift * 180.0; - var lat = y / shift * 180.0; - lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0); - - return { lng: lng, lat: lat }; -} - -export function createDivWithClass(className: string) { - var el = document.createElement("div"); - el.classList.add(className); - return el; -} - -export function knotsToMs(knots: number) { - return knots / 1.94384; -} - -export function msToKnots(ms: number) { - return ms * 1.94384; -} - -export function ftToM(ft: number) { - return ft * 0.3048; -} - -export function mToFt(m: number) { - return m / 0.3048; -} - -export function mToNm(m: number) { - return m * 0.000539957; -} - -export function nmToM(nm: number) { - return nm / 0.000539957; -} - -export function nmToFt(nm: number) { - return nm * 6076.12; -} - -export function polyContains(latlng: LatLng, polygon: Polygon) { - var poly = polygon.toGeoJSON(); - return turf.inside(turf.point([latlng.lng, latlng.lat]), poly); -} - -export function randomPointInPoly(polygon: Polygon): LatLng { - var bounds = polygon.getBounds(); - var x_min = bounds.getEast(); - var x_max = bounds.getWest(); - var y_min = bounds.getSouth(); - var y_max = bounds.getNorth(); - - var lat = y_min + (Math.random() * (y_max - y_min)); - var lng = x_min + (Math.random() * (x_max - x_min)); - - var poly = polygon.toGeoJSON(); - var inside = turf.inside(turf.point([lng, lat]), poly); - - if (inside) { - return new LatLng(lat, lng); - } else { - return randomPointInPoly(polygon); - } -} - -export function polygonArea(polygon: Polygon) { - var poly = polygon.toGeoJSON(); - return turf.area(poly); -} - -export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: { type?: string, role?: string, ranges?: string[], eras?: string[], coalition?: string }) { - /* Start from all the unit blueprints in the database */ - var unitBlueprints = Object.values(unitDatabase.getBlueprints()); - - /* If a specific type or role is provided, use only the blueprints of that type or role */ - if (options.type && options.role) { - console.error("Can't create random unit if both type and role are provided. Either create by type or by role.") - return null; - } - - if (options.type) { - unitBlueprints = unitDatabase.getByType(options.type); - } - else if (options.role) { - unitBlueprints = unitDatabase.getByType(options.role); - } - - /* Keep only the units that have a range included in the requested values */ - if (options.ranges) { - unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => { - var rangeType = ""; - var range = unitBlueprint.acquisitionRange; - if (range !== undefined) { - if (range >= 0 && range < 10000) - rangeType = "Short range"; - else if (range >= 10000 && range < 100000) - rangeType = "Medium range"; - else if (range >= 100000 && range < 999999) - rangeType = "Long range"; - } - return options.ranges?.includes(rangeType); - }); - } - - /* Keep only the units that have an era included in the requested values */ - if (options.eras) { - unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => { - return unitBlueprint.era ? options.eras?.includes(unitBlueprint.era) : true; - }); - } - - /* Keep only the units that have the correct coalition, if selected */ - if (options.coalition) { - unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => { - return (unitBlueprint.coalition && unitBlueprint.coalition !== "") ? options.coalition === unitBlueprint.coalition : true; - }); - } - - var index = Math.floor(Math.random() * unitBlueprints.length); - return unitBlueprints[index]; -} - -export function getMarkerCategoryByName(name: string) { - if (aircraftDatabase.getByName(name) != null) - return "aircraft"; - else if (helicopterDatabase.getByName(name) != null) - return "helicopter"; - else if (groundUnitDatabase.getByName(name) != null) { - var type = groundUnitDatabase.getByName(name)?.type ?? ""; - if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type)) - return "groundunit-sam"; - else - return "groundunit-other"; - } - else if (navyUnitDatabase.getByName(name) != null) - return "navyunit"; - else - return "aircraft"; // TODO add other unit types -} - -export function getUnitDatabaseByCategory(category: string) { - if (category.toLowerCase() == "aircraft") - return aircraftDatabase; - else if (category.toLowerCase() == "helicopter") - return helicopterDatabase; - else if (category.toLowerCase().includes("groundunit")) - return groundUnitDatabase; - else if (category.toLowerCase().includes("navyunit")) - return navyUnitDatabase; - else - return null; -} - -export function getCategoryBlueprintIconSVG(category: string, unitName: string) { - - const path = "/resources/theme/images/buttons/visibility/"; - - // We can just send these back okay - if (["Aircraft", "Helicopter", "NavyUnit"].includes(category)) return `${path}${category.toLowerCase()}.svg`; - - // Return if not a ground units as it's therefore something we don't recognise - if (category !== "GroundUnit") return false; - - /** We need to get the unit detail for ground units so we can work out if it's an air defence unit or not **/ - return GROUND_UNIT_AIR_DEFENCE_REGEX.test(unitName) ? `${path}groundunit-sam.svg` : `${path}groundunit.svg`; -} - -export function base64ToBytes(base64: string) { - return Buffer.from(base64, 'base64').buffer; -} - -export function enumToState(state: number) { - if (state < states.length) - return states[state]; - else - return states[0]; -} - -export function enumToROE(ROE: number) { - if (ROE < ROEs.length) - return ROEs[ROE]; - else - return ROEs[0]; -} - -export function enumToReactionToThreat(reactionToThreat: number) { - if (reactionToThreat < reactionsToThreat.length) - return reactionsToThreat[reactionToThreat]; - else - return reactionsToThreat[0]; -} - -export function enumToEmissioNCountermeasure(emissionCountermeasure: number) { - if (emissionCountermeasure < emissionsCountermeasures.length) - return emissionsCountermeasures[emissionCountermeasure]; - else - return emissionsCountermeasures[0]; -} - -export function enumToCoalition(coalitionID: number) { - switch (coalitionID) { - case 0: return "neutral"; - case 1: return "red"; - case 2: return "blue"; - } - return ""; -} - -export function coalitionToEnum(coalition: string) { - switch (coalition) { - case "neutral": return 0; - case "red": return 1; - case "blue": return 2; - } - return 0; -} - - -export function convertDateAndTimeToDate(dateAndTime: DateAndTime) { - const date = dateAndTime.date; - const time = dateAndTime.time; - - if (!date) { - return new Date(); - } - - let year = date.Year; - let month = date.Month - 1; - - if (month < 0) { - month = 11; - year--; - } - - return new Date(year, month, date.Day, time.h, time.m, time.s); -} - -export function createCheckboxOption(text: string, description: string, checked: boolean = true, callback: CallableFunction = (ev: any) => { }, options?: any) { - options = { - "disabled": false, - "name": "", - "readOnly": false, - "value": null, - ...options - }; - var div = document.createElement("div"); - div.classList.add("ol-checkbox"); - var label = document.createElement("label"); - label.title = description; - var input = document.createElement("input"); - input.type = "checkbox"; - input.checked = checked; - input.name = options.name; - input.disabled = options.disabled; - input.readOnly = options.readOnly; - input.value = options.value; - var span = document.createElement("span"); - span.innerText = text; - label.appendChild(input); - label.appendChild(span); - div.appendChild(label); - input.onclick = (ev: any) => callback(ev); - return div as HTMLElement; -} - -export function getCheckboxOptions(dropdown: Dropdown) { - var values: { [key: string]: boolean } = {}; - const element = dropdown.getOptionElements(); - for (let idx = 0; idx < element.length; idx++) { - const option = element.item(idx) as HTMLElement; - const key = option.querySelector("span")?.innerText; - const value = option.querySelector("input")?.checked; - if (key !== undefined && value !== undefined) - values[key] = value; - } - return values; -} - -export function createTextInputOption(text: string, description: string, initialValue: string, type: string, callback: CallableFunction = (ev: any) => { }, options?: any) { - options = { - "disabled": false, - "name": "", - "readOnly": false, - ...options - }; - var div = document.createElement("div"); - div.classList.add("ol-text-input", "border"); - var label = document.createElement("label"); - label.title = description; - var input = document.createElement("input"); - input.type = type; - input.name = options.name; - input.disabled = options.disabled; - input.readOnly = options.readOnly; - if (options.min) - input.min = options.min; - if (options.max) - input.max = options.max; - input.value = initialValue; - input.style.width = "80px"; - var span = document.createElement("span"); - span.innerText = text; - label.appendChild(span); - label.appendChild(input); - div.appendChild(label); - input.onchange = (ev: any) => { - if (type === 'number') { - if (Number(input.max) && Number(ev.srcElement.value) > Number(input.max)) - input.value = input.max; - else if (Number(input.min) && Number(ev.srcElement.value) < Number(input.min)) - input.value = input.min; - } - callback(ev); - } - return div as HTMLElement; -} - -export function createSliderInputOption(text: string, description: string, initialValue: number, callback: CallableFunction = (ev: any) => { }, options?: any) { - var div = document.createElement("div"); - var label = document.createElement("label"); - label.title = description; - var input = new Slider(null, options.min ?? 0, options.max ?? 100, "", (val: number) => { - callback({currentTarget: {value: val}}); - }); - input.setValue(initialValue); - input.getContainer()?.querySelector(".ol-data-grid")?.classList.add("hide"); - var span = document.createElement("span"); - span.innerText = text; - span.style.width = "100%"; - span.style.margin = "auto"; - label.appendChild(span); - label.appendChild(input.getContainer() as HTMLElement); - label.style.display = "flex"; - label.style.alignContent = "center"; - label.style.width = "100%"; - div.appendChild(label); - return div as HTMLElement; -} - -export function getGroundElevation(latlng: LatLng, callback: CallableFunction) { - /* Get the ground elevation from the server endpoint */ - const xhr = new XMLHttpRequest(); - xhr.open('GET', `api/elevation/${latlng.lat}/${latlng.lng}`, true); - xhr.timeout = 500; // ms - xhr.responseType = 'json'; - xhr.onload = () => { - var status = xhr?.status; - if (status === 200) { - callback(xhr.response) - } - }; - xhr.send(); -} \ No newline at end of file diff --git a/frontend/website/src/panels/connectionstatuspanel.ts b/frontend/website/src/panels/connectionstatuspanel.ts deleted file mode 100644 index cd363de1..00000000 --- a/frontend/website/src/panels/connectionstatuspanel.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Panel } from "./panel"; - -export class ConnectionStatusPanel extends Panel { - - #elapsedTimeElement:HTMLElement; - #missionTimeElement:HTMLElement; - - constructor(ID: string) { - super( ID ); - this.#elapsedTimeElement = this.getElement().querySelector( ".mission-elapsed-time" ); - this.#missionTimeElement = this.getElement().querySelector( ".mission-time" ); - - this.#elapsedTimeElement.addEventListener( "click", () => { - this.#toggleTime(); - }); - - this.#missionTimeElement.addEventListener( "click", () => { - this.#toggleTime(); - }); - - } - - setElapsedTime( time:string ) { - this.#elapsedTimeElement.innerText = `ET: ${time}`; - } - - setMissionTime( time:string ) { - this.#missionTimeElement.innerText = `MT: ${time} L`; - } - - showDisconnected() { - this.getElement().toggleAttribute( "data-is-connected", false ); - this.getElement().toggleAttribute( "data-is-paused", false ); - } - - - showConnected() { - this.getElement().toggleAttribute( "data-is-connected", true ); - this.getElement().toggleAttribute( "data-is-paused", false ); - } - - - showServerPaused() { - this.getElement().toggleAttribute( "data-is-connected", false ); - this.getElement().toggleAttribute( "data-is-paused", true ); - } - - #toggleTime() { - this.getElement().toggleAttribute( "data-mission-time" ); - } -} \ No newline at end of file diff --git a/frontend/website/src/panels/hotgrouppanel.ts b/frontend/website/src/panels/hotgrouppanel.ts deleted file mode 100644 index ef9d2e5f..00000000 --- a/frontend/website/src/panels/hotgrouppanel.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { getApp } from ".."; -import { Unit } from "../unit/unit"; -import { Panel } from "./panel"; - -export class HotgroupPanel extends Panel { - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID); - document.addEventListener("unitDeath", () => this.refreshHotgroups()); - } - - refreshHotgroups() { - for (let hotgroup = 1; hotgroup <= 9; hotgroup++){ - this.removeHotgroup(hotgroup); - if (getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).length > 0) - this.addHotgroup(hotgroup); - - } - } - - addHotgroup(hotgroup: number) { - // Hotgroup number - var hotgroupDiv = document.createElement("div"); - hotgroupDiv.classList.add("unit-hotgroup"); - var idDiv = document.createElement("div"); - idDiv.classList.add("unit-hotgroup-id"); - idDiv.innerText = String(hotgroup); - hotgroupDiv.appendChild(idDiv); - - // Hotgroup unit count - var countDiv = document.createElement("div"); - countDiv.innerText = `x${getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).length}`; - - var el = document.createElement("div"); - el.appendChild(hotgroupDiv); - el.appendChild(countDiv); - el.classList.add("hotgroup-selector"); - el.toggleAttribute(`data-hotgroup-${hotgroup}`, true) - - this.getElement().appendChild(el); - - el.addEventListener("click", ( ev:MouseEvent ) => { - getApp().getUnitsManager().selectUnitsByHotgroup(hotgroup, (!ev.ctrlKey)); - }); - - el.addEventListener("mouseover", () => { - getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(true)); - }); - - el.addEventListener("mouseout", () => { - getApp().getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(false)); - }); - } - - removeHotgroup(hotgroup: number) { - const el = this.getElement().querySelector(`[data-hotgroup-${hotgroup}]`) as HTMLElement; - if (el) el.remove(); - } -} \ No newline at end of file diff --git a/frontend/website/src/panels/logpanel.ts b/frontend/website/src/panels/logpanel.ts deleted file mode 100644 index f41017f2..00000000 --- a/frontend/website/src/panels/logpanel.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { getApp } from ".."; -import { MouseInfoPanel } from "./mouseinfopanel"; -import { Panel } from "./panel"; - -export class LogPanel extends Panel { - #open: boolean = false; - #queuedMessages: number = 0; - #scrolledDown: boolean = true; - #logs: {[key: string]: string} = {}; - - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID); - - document.addEventListener("toggleLogPanel", () => { - this.getElement().classList.toggle("open"); - this.#open = !this.#open; - this.#queuedMessages = 0; - this.#updateHeader(); - this.#calculateHeight(); - - if (this.#scrolledDown) - this.#scrollDown(); - }); - - const scrollEl = this.getElement().querySelector(".ol-scrollable"); - if (scrollEl) { - scrollEl.addEventListener("scroll", () => { - this.#scrolledDown = Math.abs(scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight) < 1 - }); - } - - window.addEventListener("resize", () => { - this.#calculateHeight(); - }); - - - const mouseInfoPanel = getApp().getPanelsManager().get("mouseInfo") as MouseInfoPanel; - new ResizeObserver(() => this.#calculateHeight()).observe(mouseInfoPanel.getElement()) - } - - show() { - super.show(); - this.#calculateHeight(); - } - - appendLogs(logs: {[key: string]: string}) { - Object.keys(logs).forEach((key: string) => { - if (!(key in this.#logs)) { - this.#logs[key] = logs[key]; - this.appendLog(logs[key]); - } - }); - } - - appendLog(log: string) { - var el = document.createElement("div"); - el.classList.add("ol-log-entry"); - el.textContent = log; - this.getElement().querySelector(".ol-scrollable")?.appendChild(el); - console.log(log); - - if (!this.#open) - this.#queuedMessages++; - - this.#updateHeader(); - - if (this.#scrolledDown) - this.#scrollDown(); - } - - #updateHeader() { - const headerEl = this.getElement().querySelector("#log-panel-header") as HTMLElement; - if (headerEl) { - if (this.#queuedMessages) - headerEl.innerText = `Server log (${this.#queuedMessages})`; - else - headerEl.innerText = `Server log`; - } - } - - #scrollDown() { - const scrollEl = this.getElement().querySelector(".ol-scrollable"); - if (scrollEl) { - scrollEl.scrollTop = scrollEl.scrollHeight - scrollEl.clientHeight; - } - } - - #calculateHeight() { - const mouseInfoPanel = getApp().getPanelsManager().get("mouseInfo"); - if (this.#open) - this.getElement().style.height = `${mouseInfoPanel.getElement().offsetTop - this.getElement().offsetTop - 10}px`; - else - this.getElement().style.height = "fit-content"; - } -} \ No newline at end of file diff --git a/frontend/website/src/panels/mouseinfopanel.ts b/frontend/website/src/panels/mouseinfopanel.ts deleted file mode 100644 index 82dba41e..00000000 --- a/frontend/website/src/panels/mouseinfopanel.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { Icon, LatLng, Marker, Polyline } from "leaflet"; -import { getApp } from ".."; -import { distance, bearing, zeroAppend, mToNm, nmToFt, mToFt, latLngToMGRS, MGRS, latLngToUTM, latLngToMercator } from "../other/utils"; -import { Unit } from "../unit/unit"; -import { Panel } from "./panel"; -import formatcoords from "formatcoords"; -import { MGRS_PRECISION_100M, MGRS_PRECISION_10KM, MGRS_PRECISION_10M, MGRS_PRECISION_1KM, MGRS_PRECISION_1M } from "../constants/constants"; - -export class MouseInfoPanel extends Panel { - #coordinatesElement:HTMLElement; - #locationSystems = [ "LatLng", "MGRS", "UTM" ]; - #measureMarker: Marker; - #measurePoint: LatLng | null = null; - #measureIcon: Icon; - #measureLine: Polyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1, interactive: false }); - #measureBox: HTMLElement; - #MGRSPrecisions = [ MGRS_PRECISION_10KM, MGRS_PRECISION_1KM, MGRS_PRECISION_100M, MGRS_PRECISION_10M, MGRS_PRECISION_1M ]; - #selectedMGRSPrecisionIndex = 3; - #selectedLocationSystemIndex = 0; - #elevationRequest: XMLHttpRequest | null = null; - - constructor(ID: string) { - super( ID ); - - this.#measureIcon = new Icon({ iconUrl: 'resources/theme/images/icons/pin.svg', iconAnchor: [16, 32] }); - this.#measureMarker = new Marker([0, 0], { icon: this.#measureIcon, interactive: false }); - - this.#measureBox = document.createElement("div"); - this.#measureBox.classList.add("ol-measure-box", "hide"); - document.body.appendChild(this.#measureBox); - - getApp().getMap()?.on("click", (e: any) => this.#onMapClick(e)); - getApp().getMap()?.on('zoom', (e: any) => this.#onZoom(e)); - getApp().getMap()?.on('mousemove', (e: any) => this.#onMouseMove(e)); - getApp().getMap()?.on('drag', (e: any) => this.#onMouseMove(e)); - - document.addEventListener('unitsSelection', (e: CustomEvent) => this.#update()); - document.addEventListener('clearSelection', () => this.#update()); - - this.#coordinatesElement = this.getElement().querySelector( '#coordinates-tool' ); - - this.#coordinatesElement.addEventListener( "click", ( ev:MouseEvent ) => { - this.#changeLocationSystem(); - }); - - getApp().getShortcutManager().addKeyboardShortcut( "switchMapLocationSystem", { - "callback": ( ev:KeyboardEvent ) => { - this.#changeLocationSystem(); - }, - "code": "KeyZ" - }).addKeyboardShortcut( "decreaseMGRSPrecision", { - "callback": ( ev:KeyboardEvent ) => { - if ( this.#getLocationSystem() !== "MGRS" ) { - return; - } - - if ( this.#selectedMGRSPrecisionIndex > 0 ) { - this.#selectedMGRSPrecisionIndex--; - this.#update(); - } - }, - "code": "Comma" - }).addKeyboardShortcut( "increaseMGRSPrecision", { - "callback": ( ev:KeyboardEvent ) => { - if ( this.#getLocationSystem() !== "MGRS" ) { - return; - } - - if ( this.#selectedMGRSPrecisionIndex < this.#MGRSPrecisions.length - 1 ) { - this.#selectedMGRSPrecisionIndex++; - this.#update(); - } - }, - "code": "Period" - }); - } - - #update() { - const mousePosition = getApp().getMap().getMouseCoordinates(); - - var selectedUnitPosition = null; - var selectedUnits = getApp().getUnitsManager().getSelectedUnits(); - if (selectedUnits && selectedUnits.length == 1) - selectedUnitPosition = new LatLng(selectedUnits[0].getPosition().lat, selectedUnits[0].getPosition().lng); - - /* Draw measures from selected unit, from pin location, and from bullseyes */ - this.#drawMeasure("ref-measure-position", "measure-position", this.#measurePoint, mousePosition); - this.#drawMeasure("ref-unit-position", "unit-position", selectedUnitPosition, mousePosition); - - this.getElement().querySelector(`#measuring-tool`)?.classList.toggle("hide", this.#measurePoint === null); - this.getElement().querySelector(`#unit-bullseye-tool`)?.classList.toggle("hide", selectedUnitPosition === null); - - var bullseyes = getApp().getMissionManager().getBullseyes(); - for (let idx in bullseyes) - this.#drawMeasure(null, `bullseye-${idx}`, bullseyes[idx].getLatLng(), mousePosition); - - /* Draw coordinates */ - var coords = formatcoords(mousePosition.lat, mousePosition.lng); - var coordString = coords.format('XDDMMss', {decimalPlaces: 4}); - - if ( this.#getLocationSystem() === "MGRS" ) { - const mgrs = latLngToMGRS( mousePosition.lat, mousePosition.lng, this.#MGRSPrecisions[ this.#selectedMGRSPrecisionIndex ] ); - this.#drawCoordinates("ref-mouse-position-mgrs", "mouse-position-mgrs", "M"+mgrs.groups.join(" ") ); - } else if ( this.#getLocationSystem() === "UTM" ) { - const utm = latLngToUTM( mousePosition.lat, mousePosition.lng ); - this.#drawCoordinates("ref-mouse-position-utm-northing", "mouse-position-utm-northing", "N"+utm.northing); - this.#drawCoordinates("ref-mouse-position-utm-easting", "mouse-position-utm-easting", "E"+utm.easting); - } else { - this.#drawCoordinates("ref-mouse-position-latitude", "mouse-position-latitude", coordString.split(" ")[0]); - this.#drawCoordinates("ref-mouse-position-longitude", "mouse-position-longitude", coordString.split(" ")[1]); - } - - /* Get the ground elevation from the server endpoint */ - if (this.#elevationRequest == null) { - this.#elevationRequest = new XMLHttpRequest(); - this.#elevationRequest.open('GET', `api/elevation/${mousePosition.lat}/${mousePosition.lng}`, true); - this.#elevationRequest.timeout = 500; // ms - this.#elevationRequest.responseType = 'json'; - this.#elevationRequest.onload = () => { - var status = this.#elevationRequest?.status; - if (status === 200) { - const el = this.getElement().querySelector(`#mouse-position-elevation`) as HTMLElement; - try { - el.dataset.value = `${Math.floor(mToFt(parseFloat(this.#elevationRequest?.response)))} ft`; - } catch { - el.dataset.value = `N/A`; - } - } - this.#elevationRequest = null; - }; - this.#elevationRequest.ontimeout = () => {this.#elevationRequest = null;} - this.#elevationRequest.onerror = () => {this.#elevationRequest = null;} - this.#elevationRequest.onabort = () => {this.#elevationRequest = null;} - this.#elevationRequest.send(); - } - } - - #onMapClick(e: any) { - if (e.originalEvent.ctrlKey) { - if (!this.#measurePoint) { - this.#measureBox.classList.toggle("hide", false); - this.#measurePoint = e.latlng; - this.#measureMarker.setLatLng(e.latlng); - this.#measureMarker.addTo(getApp().getMap()); - if (!getApp().getMap().hasLayer(this.#measureLine)) - this.#measureLine.addTo(getApp().getMap()); - } - else { - this.#measureBox.classList.toggle("hide", true); - this.#measurePoint = null; - if (getApp().getMap().hasLayer(this.#measureMarker)) - getApp().getMap().removeLayer(this.#measureMarker); - - this.#measureLine.setLatLngs([]); - if (getApp().getMap().hasLayer(this.#measureLine)) - getApp().getMap().removeLayer(this.#measureLine); - } - } - - this.#update(); - } - - #drawMeasureLine() { - var mouseLatLng = getApp().getMap().containerPointToLatLng(getApp().getMap().getMousePosition()); - const mousePosition = getApp().getMap().getMousePosition(); - if (this.#measurePoint != null) { - var points = [this.#measurePoint, mouseLatLng]; - this.#measureLine.setLatLngs(points); - var dist = distance(this.#measurePoint.lat, this.#measurePoint.lng, mouseLatLng.lat, mouseLatLng.lng); - var bear = bearing(this.#measurePoint.lat, this.#measurePoint.lng, mouseLatLng.lat, mouseLatLng.lng); - var startXY = getApp().getMap().latLngToContainerPoint(this.#measurePoint); - var dx = mousePosition.x - startXY.x; - var dy = mousePosition.y - startXY.y; - - var angle = Math.atan2(dy, dx); - if (angle > Math.PI / 2) - angle = angle - Math.PI; - - if (angle < -Math.PI / 2) - angle = angle + Math.PI; - - let bng = zeroAppend(Math.floor(bear), 3); - - if (bng === "000") - bng = "360"; - - var [str, unit] = this.#computeDistanceString(dist) - - let data = [`${bng}°`, `${str} ${unit}`]; - - this.#measureBox.innerText = data.join(" / "); - this.#measureBox.style.left = (mousePosition.x + startXY.x) / 2 - this.#measureBox.offsetWidth / 2 + "px"; - this.#measureBox.style.top = (mousePosition.y + startXY.y) / 2 - this.#measureBox.offsetHeight / 2 + "px"; - this.#measureBox.style.rotate = angle + "rad"; - } - } - - #onMouseMove(e: any) { - this.#update(); - this.#drawMeasureLine(); - } - - #onZoom(e: any) { - this.#drawMeasureLine(); - } - - #drawMeasure(imgID: string | null, textID: string, value: LatLng | null, mousePosition: LatLng) { - var el = this.getElement().querySelector(`#${textID}`) as HTMLElement; - var img = imgID != null ? this.getElement().querySelector(`#${imgID}`) as HTMLElement : null; - if (value) { - if (el != null) { - el.classList.remove("hide"); - - var bear = bearing(value.lat, value.lng, mousePosition.lat, mousePosition.lng); - var dist = distance(value.lat, value.lng, mousePosition.lat, mousePosition.lng); - - let bng = zeroAppend(Math.floor(bear), 3); - - if (bng === "000") - bng = "360"; - - var [str, unit] = this.#computeDistanceString(dist) - - el.dataset.bearing = bng; - el.dataset.distance = str; - el.dataset.distanceUnits = unit; - } - if (img != null) - img.classList.remove("hide"); - } - else { - if (el != null) - el.classList.add("hide"); - if (img != null) - img.classList.add("hide"); - } - } - - #drawCoordinates(imgID: string, textID: string, value: string) { - const el = this.getElement().querySelector(`#${textID}`) as HTMLElement; - const img = this.getElement().querySelector(`#${imgID}`) as HTMLElement; - if (img && el) { - el.innerText = value.substring(1); - img.innerText = value[0]; - } - } - - #computeDistanceString(dist: number) { - var val = mToNm(dist); - var strVal = 0; - var decimal = false; - var unit = "NM"; - if (val > 10) - strVal = Math.floor(val); - else if (val > 1 && val <= 10) { - strVal = Math.floor(val * 100) / 100; - decimal = true; - } - else { - strVal = Math.floor(nmToFt(val)); - unit = "ft"; - } - - return [zeroAppend(strVal, 3, decimal), unit]; - } - - #changeLocationSystem() { - - if ( this.#selectedLocationSystemIndex < this.#locationSystems.length - 1 ) { - this.#selectedLocationSystemIndex++; - } else { - this.#selectedLocationSystemIndex = 0; - } - - this.#coordinatesElement.setAttribute( "data-location-system", this.#getLocationSystem() ); - this.#update(); - } - - - #getLocationSystem() { - const x = this.#locationSystems; - const y = this.#selectedLocationSystemIndex; - const z = this.#locationSystems[ this.#selectedLocationSystemIndex ]; - return this.#locationSystems[this.#selectedLocationSystemIndex]; - } -} diff --git a/frontend/website/src/panels/panel.ts b/frontend/website/src/panels/panel.ts deleted file mode 100644 index a315ea16..00000000 --- a/frontend/website/src/panels/panel.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { PanelEventsManager } from "./paneleventsmanager"; - -export abstract class Panel { - - #element: HTMLElement - #eventsManager!: PanelEventsManager; - - constructor(ID: string) { - this.#element = document.getElementById(ID); - - this.#eventsManager = new PanelEventsManager(); - } - - show() { - this.#element.classList.toggle("hide", false); - this.getEventsManager()?.trigger("show", {}); - } - - hide() { - this.#element.classList.toggle("hide", true); - this.getEventsManager()?.trigger("hide", {}); - } - - toggle() { - // Simple way to track if currently visible - if (this.getVisible()) - this.hide(); - else - this.show(); - } - - getElement() { - return this.#element; - } - - getVisible() { - return (!this.getElement().classList.contains("hide")); - } - - getEventsManager() { - return this.#eventsManager; - } -} \ No newline at end of file diff --git a/frontend/website/src/panels/paneleventsmanager.ts b/frontend/website/src/panels/paneleventsmanager.ts deleted file mode 100644 index 2f916787..00000000 --- a/frontend/website/src/panels/paneleventsmanager.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Listener } from "../interfaces"; -import { EventsManager } from "../other/eventsmanager"; - -export class PanelEventsManager extends EventsManager { - constructor() { - super(); - - this.add("hide", []); - this.add("show", []); - } - - on(eventName: string, listener: Listener) { - const event = this.get(eventName); - if (!event) { - throw new Error(`Event name "${eventName}" is not valid.`); - } - this.get(eventName).push({ - "callback": listener.callback - }); - } - - trigger(eventName: string, contextData: object) { - const listeners = this.get(eventName); - if (listeners) { - listeners.forEach((listener: Listener) => { - listener.callback(contextData); - }); - } - } -} \ No newline at end of file diff --git a/frontend/website/src/panels/serverstatuspanel.ts b/frontend/website/src/panels/serverstatuspanel.ts deleted file mode 100644 index 74a87a3f..00000000 --- a/frontend/website/src/panels/serverstatuspanel.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Panel } from "./panel"; - -export class ServerStatusPanel extends Panel { - constructor(ID: string) { - super( ID ); - } - - update(frameRate: number, load: number) { - const frameRateEl = this.getElement().querySelector("#server-frame-rate"); - if (frameRateEl) { - frameRateEl.textContent = `${frameRate}`; - frameRateEl.classList.toggle("fps-high", frameRate >= 60) - frameRateEl.classList.toggle("fps-medium", frameRate >= 30 && frameRate < 60) - frameRateEl.classList.toggle("fps-low", frameRate <= 30) - } - - const loadEl = this.getElement().querySelector("#server-load"); - if (loadEl) { - loadEl.textContent = `${load}`; - loadEl.classList.toggle("load-high", load >= 1000) - loadEl.classList.toggle("load-medium", load >= 100 && load < 1000) - loadEl.classList.toggle("load-low", load <= 100) - } - - } -} \ No newline at end of file diff --git a/frontend/website/src/panels/unitcontrolpanel.ts b/frontend/website/src/panels/unitcontrolpanel.ts deleted file mode 100644 index 91af3e15..00000000 --- a/frontend/website/src/panels/unitcontrolpanel.ts +++ /dev/null @@ -1,504 +0,0 @@ -import { SVGInjector } from "@tanem/svg-injector"; -import { getApp } from ".."; -import { Dropdown } from "../controls/dropdown"; -import { Slider } from "../controls/slider"; -import { Unit } from "../unit/unit"; -import { Panel } from "./panel"; -import { Switch } from "../controls/switch"; -import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, shotsIntensityDescriptions, shotsScatterDescriptions, speedIncrements } from "../constants/constants"; -import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils"; -import { GeneralSettings, Radio, TACAN } from "../interfaces"; -import { ContextActionSet } from "../unit/contextactionset"; -import { Popup } from "../popups/popup"; - -export class UnitControlPanel extends Panel { - #altitudeSlider: Slider; - #tankerSwitch: Switch; - #AWACSSwitch: Switch; - #altitudeTypeSwitch: Switch; - #speedSlider: Slider; - #speedTypeSwitch: Switch; - #onOffSwitch: Switch; - #followRoadsSwitch: Switch; - #operateAsSwitch: Switch; - #TACANXYDropdown: Dropdown; - #radioDecimalsDropdown: Dropdown; - #radioCallsignDropdown: Dropdown; - #optionButtons: { [key: string]: HTMLButtonElement[] } = {} - #advancedSettingsDialog: HTMLElement; - #units: Unit[] = []; - #selectedUnitsTypes: string[] = []; - #deleteDropdown: Dropdown; - - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string) { - super(ID); - - /* Unit control sliders */ - this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getApp().getUnitsManager().setAltitude(ftToM(value)); }); - this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getApp().getUnitsManager().setAltitudeType(value ? "ASL" : "AGL"); }); - - this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getApp().getUnitsManager().setSpeed(knotsToMs(value)); }); - this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getApp().getUnitsManager().setSpeedType(value ? "CAS" : "GS"); }); - - /* Option buttons */ - // Reversing the ROEs so that the least "aggressive" option is always on the left - this.#optionButtons["ROE"] = ROEs.slice(0).reverse().map((option: string, index: number) => { - return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions.slice(0).reverse()[index], () => { getApp().getUnitsManager().setROE(option); }); - }).filter((button: HTMLButtonElement, index: number) => { return ROEs[index] !== ""; }); - - this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string, index: number) => { - return this.#createOptionButton(option, `threat/${option.toLowerCase()}.svg`, reactionsToThreatDescriptions[index], () => { getApp().getUnitsManager().setReactionToThreat(option); }); - }); - - this.#optionButtons["emissionsCountermeasures"] = emissionsCountermeasures.map((option: string, index: number) => { - return this.#createOptionButton(option, `emissions/${option.toLowerCase()}.svg`, emissionsCountermeasuresDescriptions[index], () => { getApp().getUnitsManager().setEmissionsCountermeasures(option); }); - }); - - this.#optionButtons["shotsScatter"] = [1, 2, 3].map((option: number, index: number) => { - return this.#createOptionButton(option.toString(), `scatter/${option.toString().toLowerCase()}.svg`, shotsScatterDescriptions[index], () => { getApp().getUnitsManager().setShotsScatter(option); }); - }); - - this.#optionButtons["shotsIntensity"] = [1, 2, 3].map((option: number, index: number) => { - return this.#createOptionButton(option.toString(), `intensity/${option.toString().toLowerCase()}.svg`, shotsIntensityDescriptions[index], () => { getApp().getUnitsManager().setShotsIntensity(option); }); - }); - - this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]); - this.getElement().querySelector("#reaction-to-threat-buttons-container")?.append(...this.#optionButtons["reactionToThreat"]); - this.getElement().querySelector("#emissions-countermeasures-buttons-container")?.append(...this.#optionButtons["emissionsCountermeasures"]); - this.getElement().querySelector("#shots-scatter-buttons-container")?.append(...this.#optionButtons["shotsScatter"]); - this.getElement().querySelector("#shots-intensity-buttons-container")?.append(...this.#optionButtons["shotsIntensity"]); - - /* Tanker */ - this.#tankerSwitch = new Switch("tanker-on-switch", (value: boolean) => { - // TODO: split setAdvancedOptions into setIsTanker, setIsAWACS, setAdvancedOptions - var selectedUnits = getApp().getUnitsManager().getSelectedUnits(); - selectedUnits.forEach((unit: Unit) => { - unit.setAdvancedOptions(value, unit.getIsActiveAWACS(), unit.getTACAN(), unit.getRadio(), unit.getGeneralSettings()); - }); - }); - - /* AWACS */ - this.#AWACSSwitch = new Switch("AWACS-on-switch", (value: boolean) => { - // TODO: split setAdvancedOptions into setIsTanker, setIsAWACS, setAdvancedOptions - var selectedUnits = getApp().getUnitsManager().getSelectedUnits(); - selectedUnits.forEach((unit: Unit) => { - unit.setAdvancedOptions(unit.getIsActiveTanker(), value, unit.getTACAN(), unit.getRadio(), unit.getGeneralSettings()); - }); - }); - - /* On off switch */ - this.#onOffSwitch = new Switch("on-off-switch", (value: boolean) => { - getApp().getUnitsManager().setOnOff(value); - }); - - /* Follow roads switch */ - this.#followRoadsSwitch = new Switch("follow-roads-switch", (value: boolean) => { - getApp().getUnitsManager().setFollowRoads(value); - if (value) - (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Warning: follow roads movements can cause lag"); - }); - - /* Operate as */ - this.#operateAsSwitch = new Switch("operate-as-switch", (value: boolean) => { - getApp().getUnitsManager().setOperateAs(value); - }); - - /* Mouseover of (?) highlights activation buttons */ - const operateAsQuestionMark = this.getElement().querySelector("#operate-as h4 img"); - operateAsQuestionMark.addEventListener("mouseover", () => { - document.querySelectorAll(`#rapid-controls button.scenic-action`).forEach((btn: Element) => { - btn.classList.add(`pulse`); - }); - }); - operateAsQuestionMark.addEventListener("mouseout", () => { - document.querySelectorAll(`#rapid-controls button.scenic-action.pulse`).forEach((btn: Element) => { - btn.classList.remove(`pulse`); - }); - }); - - /* Advanced settings dialog */ - this.#advancedSettingsDialog = document.querySelector("#advanced-settings-dialog"); - - /* Advanced settings dropdowns */ - this.#TACANXYDropdown = new Dropdown("TACAN-XY", () => { }); - this.#TACANXYDropdown.setOptions(["X", "Y"]); - this.#radioDecimalsDropdown = new Dropdown("radio-decimals", () => { }); - this.#radioDecimalsDropdown.setOptions([".000", ".250", ".500", ".750"]); - this.#radioCallsignDropdown = new Dropdown("radio-callsign", () => { }); - this.#deleteDropdown = new Dropdown("delete-options", () => { }); - - /* Events and timer */ - window.setInterval(() => { this.update(); }, 25); - - document.addEventListener("unitsSelection", (e: CustomEvent) => { - this.show(); - this.addButtons(); - this.#updateRapidControls(); - }); - document.addEventListener("clearSelection", () => { - this.hide(); - this.#updateRapidControls(); - }); - document.addEventListener("applyAdvancedSettings", () => { this.#applyAdvancedSettings(); }) - document.addEventListener("showAdvancedSettings", () => { - this.#updateAdvancedSettingsDialog(getApp().getUnitsManager().getSelectedUnits()); - this.#advancedSettingsDialog.classList.remove("hide"); - }); - - /* This is for when a ctrl-click happens on the map for deselection and we need to remove the selected unit from the panel */ - document.addEventListener("unitsDeselection", (ev: CustomEventInit) => { - if (ev.detail.length > 0) { - this.show(); - this.addButtons(); - this.#updateRapidControls(); - } else { - this.hide(); - this.#updateRapidControls(); - } - }); - - window.addEventListener("resize", (e: any) => this.#calculateMaxHeight()); - - const element = document.getElementById("toolbar-container"); - if (element) - new ResizeObserver(() => this.#calculateTop()).observe(element); - - this.#calculateMaxHeight() - this.hide(); - } - - show() { - const context = getApp().getCurrentContext(); - if (!context.getUseUnitControlPanel()) - return; - - this.#updateNumberOfSelectedUnits(); - super.show(); - this.#speedTypeSwitch.resetExpectedValue(); - this.#altitudeTypeSwitch.resetExpectedValue(); - this.#tankerSwitch.resetExpectedValue(); - this.#AWACSSwitch.resetExpectedValue(); - this.#onOffSwitch.resetExpectedValue(); - this.#followRoadsSwitch.resetExpectedValue(); - this.#operateAsSwitch.resetExpectedValue(); - this.#altitudeSlider.resetExpectedValue(); - this.#speedSlider.resetExpectedValue(); - this.#calculateMaxHeight(); - } - - addButtons() { - this.#units = getApp().getUnitsManager().getSelectedUnits(); - this.#selectedUnitsTypes = getApp().getUnitsManager().getSelectedUnitsCategories(); - - if (this.#units.length < 20) { - this.getElement().querySelector("#selected-units-container")?.replaceChildren(...this.#units.map((unit: Unit, index: number) => { - var button = document.createElement("button"); - var callsign = unit.getUnitName() || ""; - var label = unit.getDatabase()?.getByName(unit.getName())?.label || unit.getName(); - - button.setAttribute("data-unit-id", "" + unit.ID); - button.setAttribute("data-label", label); - button.setAttribute("data-callsign", callsign); - - button.setAttribute("data-coalition", unit.getCoalition()); - button.classList.add("pill", "highlight-coalition") - - button.addEventListener("click", (ev: MouseEventInit) => { - // Ctrl-click deselection - if (ev.ctrlKey === true && ev.shiftKey === false && ev.altKey === false) { - getApp().getUnitsManager().deselectUnit(unit.ID); - button.remove(); - // Deselect all - } else { - getApp().getUnitsManager().deselectAllUnits(); - getApp().getUnitsManager().selectUnit(unit.ID, true); - } - }); - return (button); - })); - } else { - var el = document.createElement("div"); - el.innerText = "Too many units selected"; - this.getElement().querySelector("#selected-units-container")?.replaceChildren(el); - } - } - - update() { - if (this.getVisible()) { - const element = this.getElement(); - if (element != null && this.#units.length > 0) { - /* Toggle visibility of control elements */ - var isTanker = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.isTanker(); }); - var isAWACS = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.isAWACS(); }); - var isActiveTanker = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getIsActiveTanker() }); - var isActiveAWACAS = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getIsActiveAWACS() }); - - element.toggleAttribute("data-show-categories-tooltip", this.#selectedUnitsTypes.length > 1); - element.toggleAttribute("data-show-speed-slider", this.#selectedUnitsTypes.length == 1); - element.toggleAttribute("data-show-altitude-slider", this.#selectedUnitsTypes.length == 1 && (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter"))); - element.toggleAttribute("data-show-roe", !isTanker && !isAWACS); - element.toggleAttribute("data-show-threat", (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")) && !(this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit"))); - element.toggleAttribute("data-show-emissions-countermeasures", (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")) && !(this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit"))); - element.toggleAttribute("data-show-shots-scatter", this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit")); //TODO: more refined - element.toggleAttribute("data-show-shots-intensity", this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit")); //TODO: more refined - element.toggleAttribute("data-show-tanker-button", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.isTanker(); }) === true); - element.toggleAttribute("data-show-AWACS-button", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.isAWACS(); }) === true); - element.toggleAttribute("data-show-on-off", (this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")) && !(this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter"))); - element.toggleAttribute("data-show-follow-roads", (this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit"))); - element.toggleAttribute("data-show-operate-as", this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit") && getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getCoalition() }) === "neutral"); - - if (this.#units.length == 1) { - if (isAWACS) - element.toggleAttribute("data-show-advanced-settings-button", isActiveAWACAS); - else if (isTanker) - element.toggleAttribute("data-show-advanced-settings-button", isActiveTanker); - else - element.toggleAttribute("data-show-advanced-settings-button", true); - } - - if (this.#selectedUnitsTypes.length == 1) { - /* Flight controls */ - var desiredAltitude = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getDesiredAltitude() }); - var desiredAltitudeType = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getDesiredAltitudeType() }); - var desiredSpeed = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getDesiredSpeed() }); - var desiredSpeedType = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getDesiredSpeedType() }); - var isActiveTanker = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getIsActiveTanker() }); - var isActiveAWACAS = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getIsActiveAWACS() }); - var onOff = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getOnOff() }); - var followRoads = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getFollowRoads() }); - var operateAs = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getOperateAs() }); - - this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined ? desiredAltitudeType == "ASL" : undefined, false); - this.#speedTypeSwitch.setValue(desiredSpeedType != undefined ? desiredSpeedType == "CAS" : undefined, false); - - this.#speedSlider.setMinMax(minSpeedValues[this.#selectedUnitsTypes[0]], maxSpeedValues[this.#selectedUnitsTypes[0]]); - this.#altitudeSlider.setMinMax(minAltitudeValues[this.#selectedUnitsTypes[0]], maxAltitudeValues[this.#selectedUnitsTypes[0]]); - this.#speedSlider.setIncrement(speedIncrements[this.#selectedUnitsTypes[0]]); - this.#altitudeSlider.setIncrement(altitudeIncrements[this.#selectedUnitsTypes[0]]); - - this.#speedSlider.setActive(desiredSpeed != undefined); - if (desiredSpeed != undefined) - this.#speedSlider.setValue(msToKnots(desiredSpeed), false); - - this.#altitudeSlider.setActive(desiredAltitude != undefined); - if (desiredAltitude != undefined) - this.#altitudeSlider.setValue(mToFt(desiredAltitude), false); - } - else { - this.#speedSlider.setActive(false); - this.#altitudeSlider.setActive(false); - } - - /* Option buttons */ - this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => { - button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getROE() === button.value)) - }); - - this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => { - button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getReactionToThreat() === button.value)) - }); - - this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => { - button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getEmissionsCountermeasures() === button.value)) - }); - - this.#optionButtons["shotsScatter"].forEach((button: HTMLButtonElement) => { - button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getShotsScatter().toString() === button.value)) - }); - - this.#optionButtons["shotsIntensity"].forEach((button: HTMLButtonElement) => { - button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getShotsIntensity().toString() === button.value)) - }); - - this.#tankerSwitch.setValue(isActiveTanker, false); - this.#AWACSSwitch.setValue(isActiveAWACAS, false); - this.#onOffSwitch.setValue(onOff, false); - this.#followRoadsSwitch.setValue(followRoads, false); - this.#operateAsSwitch.setValue(operateAs ? operateAs === "blue" : undefined, false); - } - } - } - - #updateRapidControls() { - var contextActionSet = new ContextActionSet(); - var units = getApp().getUnitsManager().getSelectedUnits(); - - var showAltitudeChange = units.some((unit: Unit) => { return ["Aircraft", "Helicopter"].includes(unit.getCategory()); }); - this.getElement().querySelector("#climb")?.classList.toggle("hide", !showAltitudeChange); - this.getElement().querySelector("#descend")?.classList.toggle("hide", !showAltitudeChange); - - units.forEach((unit: Unit) => { - unit.appendContextActions(contextActionSet, null, null); - }) - - const rapidControlsContainer = this.getElement().querySelector("#rapid-controls") as HTMLElement; - const unitActionButtons = rapidControlsContainer.querySelectorAll(".unit-action-button"); - for (let button of unitActionButtons) { - rapidControlsContainer.removeChild(button); - } - - for (let key in contextActionSet.getContextActions()) { - const contextAction = contextActionSet.getContextActions()[key]; - let button = document.createElement("button"); - button.title = contextAction.getDescription(); - button.classList.add("ol-button", "unit-action-button"); - if (contextAction.getOptions().isScenic) - button.classList.add("scenic-action"); - button.id = key; - rapidControlsContainer.appendChild(button); - button.onclick = () => { - contextAction.executeCallback(); - } - } - } - - #updateAdvancedSettingsDialog(units: Unit[]) { - if (units.length == 1) { - /* HTML Elements */ - const unitNameEl = this.#advancedSettingsDialog.querySelector("#unit-name") as HTMLElement; - const prohibitJettisonCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-jettison-checkbox")?.querySelector("input") as HTMLInputElement; - const prohibitAfterburnerCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-afterburner-checkbox")?.querySelector("input") as HTMLInputElement; - const prohibitAACheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-AA-checkbox")?.querySelector("input") as HTMLInputElement; - const prohibitAGCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-AG-checkbox")?.querySelector("input") as HTMLInputElement; - const prohibitAirWpnCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-air-wpn-checkbox")?.querySelector("input") as HTMLInputElement; - const TACANCheckbox = this.#advancedSettingsDialog.querySelector("#TACAN-checkbox")?.querySelector("input") as HTMLInputElement; - const TACANChannelInput = this.#advancedSettingsDialog.querySelector("#TACAN-channel")?.querySelector("input") as HTMLInputElement; - const TACANCallsignInput = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input") as HTMLInputElement; - const radioMhzInput = this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input") as HTMLInputElement; - const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement; - - const unit = units[0]; - const isTanker = unit.isTanker(); - const isAWACS = unit.isAWACS(); - const radioMHz = Math.floor(unit.getRadio().frequency / 1000000); - const radioDecimals = (unit.getRadio().frequency / 1000000 - radioMHz) * 1000; - - /* Activate the correct options depending on unit type */ - this.#advancedSettingsDialog.toggleAttribute("data-show-settings", !isTanker && !isAWACS); - this.#advancedSettingsDialog.toggleAttribute("data-show-air-unit-checkboxes", ["Aircraft", "Helicopter"].includes(units[0].getCategory())); - this.#advancedSettingsDialog.toggleAttribute("data-show-tasking", isTanker || isAWACS); - this.#advancedSettingsDialog.toggleAttribute("data-show-tanker", isTanker); - this.#advancedSettingsDialog.toggleAttribute("data-show-AWACS", isAWACS); - this.#advancedSettingsDialog.toggleAttribute("data-show-TACAN", isTanker || ["Aircraft Carrier", "Super Aircraft Carrier"].includes(units[0].getType())); - this.#advancedSettingsDialog.toggleAttribute("data-show-radio", isTanker || isAWACS); - - /* Set common properties */ - // Name - unitNameEl.innerText = unit.getUnitName(); - - // General settings - prohibitJettisonCheckbox.checked = unit.getGeneralSettings().prohibitJettison; - prohibitAfterburnerCheckbox.checked = unit.getGeneralSettings().prohibitAfterburner; - prohibitAACheckbox.checked = unit.getGeneralSettings().prohibitAA; - prohibitAGCheckbox.checked = unit.getGeneralSettings().prohibitAG; - prohibitAirWpnCheckbox.checked = unit.getGeneralSettings().prohibitAirWpn; - - // TACAN - TACANCheckbox.checked = unit.getTACAN().isOn; - TACANChannelInput.value = String(unit.getTACAN().channel); - TACANCallsignInput.value = String(unit.getTACAN().callsign); - this.#TACANXYDropdown.setValue(unit.getTACAN().XY); - - // Radio - radioMhzInput.value = String(radioMHz); - radioCallsignNumberInput.value = String(unit.getRadio().callsignNumber); - this.#radioDecimalsDropdown.setValue("." + radioDecimals); - - if (isTanker) /* Set tanker specific options */ - this.#radioCallsignDropdown.setOptions(["Texaco", "Arco", "Shell"], null); - else if (isAWACS) /* Set AWACS specific options */ - this.#radioCallsignDropdown.setOptions(["Overlord", "Magic", "Wizard", "Focus", "Darkstar"], null); - else - this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"], null); - - // This must be done after setting the options - if (!this.#radioCallsignDropdown.selectValue(unit.getRadio().callsign - 1)) // Ensure the selected value is in the acceptable range - this.#radioCallsignDropdown.selectValue(0); - } - } - - #updateNumberOfSelectedUnits() { - const num = getApp().getUnitsManager().getSelectedUnits().length; - this.getElement().querySelectorAll(".num-selected-units").forEach(el => { - if (el instanceof HTMLElement) el.innerText = num + ""; - }); - } - - #applyAdvancedSettings() { - /* HTML Elements */ - const prohibitJettisonCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-jettison-checkbox")?.querySelector("input") as HTMLInputElement; - const prohibitAfterburnerCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-afterburner-checkbox")?.querySelector("input") as HTMLInputElement; - const prohibitAACheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-AA-checkbox")?.querySelector("input") as HTMLInputElement; - const prohibitAGCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-AG-checkbox")?.querySelector("input") as HTMLInputElement; - const prohibitAirWpnCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-air-wpn-checkbox")?.querySelector("input") as HTMLInputElement; - const TACANCheckbox = this.#advancedSettingsDialog.querySelector("#TACAN-checkbox")?.querySelector("input") as HTMLInputElement; - const TACANChannelInput = this.#advancedSettingsDialog.querySelector("#TACAN-channel")?.querySelector("input") as HTMLInputElement; - const TACANCallsignInput = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input") as HTMLInputElement; - const radioMhzInput = this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input") as HTMLInputElement; - const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement; - - - /* TACAN */ - const TACAN: TACAN = { - isOn: TACANCheckbox.checked ? true : false, - channel: Number(TACANChannelInput.value), - XY: this.#TACANXYDropdown.getValue(), - callsign: TACANCallsignInput.value as string - } - - /* Radio */ - const radioMHz = Number(radioMhzInput.value); - const radioDecimals = this.#radioDecimalsDropdown.getValue(); - const radio: Radio = { - frequency: (radioMHz * 1000 + Number(radioDecimals.substring(1))) * 1000, - callsign: this.#radioCallsignDropdown.getIndex() + 1, - callsignNumber: Number(radioCallsignNumberInput.value) - } - - /* General settings */ - const generalSettings: GeneralSettings = { - prohibitJettison: prohibitJettisonCheckbox.checked ? true : false, - prohibitAfterburner: prohibitAfterburnerCheckbox.checked ? true : false, - prohibitAA: prohibitAACheckbox.checked ? true : false, - prohibitAG: prohibitAGCheckbox.checked ? true : false, - prohibitAirWpn: prohibitAirWpnCheckbox.checked ? true : false - } - - /* Send command and close */ - var units = getApp().getUnitsManager().getSelectedUnits(); - // TODO: split setAdvancedOptions into setIsTanker, setIsAWACS, setAdvancedOptions - if (units.length > 0) - units[0].setAdvancedOptions(units[0].getIsActiveTanker(), units[0].getIsActiveAWACS(), TACAN, radio, generalSettings); - - this.#advancedSettingsDialog.classList.add("hide"); - } - - #createOptionButton(value: string, url: string, title: string, callback: EventListenerOrEventListenerObject) { - var button = document.createElement("button"); - button.title = title; - button.value = value; - var img = document.createElement("img"); - img.src = `/resources/theme/images/buttons/${url}`; - img.onload = () => SVGInjector(img); - button.appendChild(img); - button.addEventListener("click", callback); - return button; - } - - #calculateTop() { - const element = document.getElementById("toolbar-container"); - if (element) - this.getElement().style.top = `${element.offsetTop + element.offsetHeight + 10}px`; - } - - #calculateMaxHeight() { - const element = document.getElementById("unit-control-panel-content"); - this.#calculateTop(); - if (element) - element.style.maxHeight = `${window.innerHeight - this.getElement().offsetTop - 10}px`; - } -} \ No newline at end of file diff --git a/frontend/website/src/panels/unitinfopanel.ts b/frontend/website/src/panels/unitinfopanel.ts deleted file mode 100644 index 84e30580..00000000 --- a/frontend/website/src/panels/unitinfopanel.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { getApp } from ".."; -import { Ammo } from "../interfaces"; -import { aircraftDatabase } from "../unit/databases/aircraftdatabase"; -import { Unit } from "../unit/unit"; -import { Panel } from "./panel"; - -export class UnitInfoPanel extends Panel { - #currentTask: HTMLElement; - #fuelBar: HTMLElement; - #fuelPercentage: HTMLElement; - #loadoutContainer: HTMLElement; - #silhouette: HTMLImageElement; - #unitControl: HTMLElement; - #unitLabel: HTMLElement; - #unitGroup: HTMLElement; - #unitName: HTMLElement; - - constructor(ID: string) { - super( ID ); - - this.#currentTask = (this.getElement().querySelector("#current-task")) as HTMLElement; - this.#fuelBar = (this.getElement().querySelector("#fuel-bar")) as HTMLElement; - this.#fuelPercentage = (this.getElement().querySelector("#fuel-percentage")) as HTMLElement; - this.#loadoutContainer = (this.getElement().querySelector("#loadout-container")) as HTMLElement; - this.#silhouette = (this.getElement().querySelector("#loadout-silhouette")) as HTMLImageElement; - this.#unitControl = (this.getElement().querySelector("#unit-control")) as HTMLElement; - this.#unitLabel = (this.getElement().querySelector("#unit-label")) as HTMLElement; - this.#unitGroup = (this.getElement().querySelector("#unit-group")) as HTMLElement; - this.#unitName = (this.getElement().querySelector("#unit-name")) as HTMLElement; - - document.addEventListener("unitsSelection", (e: CustomEvent) => this.#onUnitsSelection(e.detail)); - document.addEventListener("unitsDeselection", (e: CustomEvent) => this.#onUnitsDeselection(e.detail)); - document.addEventListener("clearSelection", (e: CustomEvent) => this.#onUnitsDeselection([])); - document.addEventListener("unitUpdated", (e: CustomEvent) => this.#onUnitUpdate(e.detail)); - - this.hide(); - } - - #onUnitUpdate(unit: Unit) { - if (this.getElement() != null && this.getVisible() && unit.getSelected()) { - - /* Set the unit info */ - this.#unitLabel.innerText = unit.getDatabaseEntry()?.label || unit.getName(); - this.#unitGroup.dataset.groupName = unit.getGroup()?.getName() ?? "No group"; - this.#unitName.innerText = unit.getUnitName(); - if (unit.getHuman()) - this.#unitControl.innerText = "Human"; - else if (unit.getControlled()) - this.#unitControl.innerText = "Olympus controlled"; - else - this.#unitControl.innerText = "DCS Controlled"; - this.#fuelBar.style.width = String(unit.getFuel() + "%"); - this.#fuelPercentage.dataset.percentage = "" + unit.getFuel(); - this.#currentTask.dataset.currentTask = unit.getTask() !== "" ? unit.getTask() : "No task"; - this.#currentTask.dataset.coalition = unit.getCoalition(); - - const filename = unit.getDatabase()?.getByName(unit.getName())?.filename; - if (filename) - this.#silhouette.src = `/images/units/${filename}`; - this.#silhouette.classList.toggle("hide", filename == undefined || filename == ''); - - /* Add the loadout elements */ - const items = this.#loadoutContainer.querySelector("#loadout-items") as HTMLElement; - - if (items) { - const ammo = Object.values(unit.getAmmo()); - if (ammo.length > 0) { - items.replaceChildren(...Object.values(unit.getAmmo()).map( - (ammo: Ammo) => { - var el = document.createElement("div"); - el.dataset.qty = `${ammo.quantity}`; - el.dataset.item = ammo.name; - return el; - } - )); - - } else { - items.innerText = "No loadout"; - } - } - } - } - - #onUnitsSelection(units: Unit[]) { - if (units.length == 1) { - this.show(); - this.#onUnitUpdate(units[0]); - } - else - this.hide(); - } - - #onUnitsDeselection(units: Unit[]) { - if (units.length == 1) { - this.show(); - this.#onUnitUpdate(units[0]); - } - else - this.hide(); - } - - show() { - const context = getApp().getCurrentContext(); - if ( !context.getUseUnitInfoPanel() ) - return; - - super.show(); - } -} \ No newline at end of file diff --git a/frontend/website/src/panels/unitlistpanel.ts b/frontend/website/src/panels/unitlistpanel.ts deleted file mode 100644 index b35c6267..00000000 --- a/frontend/website/src/panels/unitlistpanel.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { getApp } from ".."; -import { ShortcutKeyboard } from "../shortcut/shortcut"; -import { Unit } from "../unit/unit"; -import { Panel } from "./panel"; - -export class UnitListPanel extends Panel { - #contentElement: HTMLElement; - #currentSortAlgorithm: string = "unitName"; - #currentSortDirection: string = "ASC"; - #units: Unit[] = []; - #unitNameCache: {[key:string]: string} = {}; - #updatesInterval!: ReturnType; - - constructor(panelElement: string, contentElement: string) { - super(panelElement); - const getElement = document.getElementById(contentElement); - - if (getElement instanceof HTMLElement) - this.#contentElement = getElement; - else - throw new Error(`UnitList: unable to find element "${contentElement}".`); - - // Add the header click listener and sorting - [].slice.call(this.getElement().querySelectorAll(".headers > *")).forEach((header: HTMLElement) => { - header.addEventListener("click", (ev: MouseEvent) => { - const el = ev.target; - if (el instanceof HTMLElement) { - const newSort = el.getAttribute("data-sort-field") || "unitName"; - - if (this.#currentSortAlgorithm === newSort) - this.#currentSortDirection = (this.#currentSortDirection === "ASC") ? "DESC" : "ASC"; - else - this.#currentSortDirection = "ASC"; - - this.#currentSortAlgorithm = newSort; - - this.doUpdate(); - } - }); - }); - - - // Dynamically listen for clicks in order to do stuff with units - this.getElement().addEventListener("click", (ev: MouseEvent) => { - const t = ev.target; - - if (t instanceof HTMLElement) { - const el = t.closest( "[data-unit-id]"); - - if (el instanceof HTMLElement) { - let id: number = Number(el.getAttribute("data-unit-id") || 0); - getApp().getUnitsManager().selectUnit(id); - } - } - }); - - new ShortcutKeyboard({ - "callback": () => { this.toggle() }, - "code": "KeyU" - }); - - this.startUpdates(); - } - - doUpdate() { - if (!this.getVisible()) - return; - - this.#contentElement.innerHTML = ""; - - this.#units = Object.values(getApp().getUnitsManager().getUnits()); - - if (this.#currentSortAlgorithm === "coalition") - this.#sortUnitsByCoalition(); - - if (this.#currentSortAlgorithm === "name") - this.#sortUnitsByName(); - - if (this.#currentSortAlgorithm === "unitName") - this.#sortUnitsByUnitName(); - - Object.values(this.#units).forEach((unit: Unit) => { - // Exclude dead units - if (!unit.getAlive()) { - return; - } - - const name = unit.getName(); - - if ( this.#unitNameCache.hasOwnProperty( name ) === false ) { - this.#unitNameCache[name] = unit.getDatabase()?.getByName(unit.getName())?.label || unit.getName(); - } - - this.#contentElement.innerHTML += ` -
-
${unit.getUnitName()}
-
${this.#unitNameCache[name]}
-
${unit.getCategory()}
-
${unit.getCoalition()}
-
${unit.getHuman() ? "Human" : "AI"}
-
`; - }); - } - - getContentElement() { - return this.#contentElement; - } - - #sortStringsCompare(str1: string, str2: string) { - if (str1 > str2) { - return (this.#currentSortDirection === "ASC") ? 1 : -1; - } else if (str1 < str2) { - return (this.#currentSortDirection === "ASC") ? -1 : 1; - } - - return 0; - } - - #sortUnitsByUnitName() { - this.#units.sort((unit1: Unit, unit2: Unit) => { - - const str1 = unit1.getUnitName().toLowerCase(); - const str2 = unit2.getUnitName().toLowerCase(); - - return this.#sortStringsCompare(str1, str2); - }); - } - - #sortUnitsByCategory() { - this.#units.sort((unit1: Unit, unit2: Unit) => { - let str1 = unit1.getCategory(); - let str2 = unit2.getCategory(); - - let cmp = this.#sortStringsCompare(str1, str2); - - if (cmp !== 0) - return cmp; - - str1 = unit1.getUnitName().toLowerCase(); - str2 = unit2.getUnitName().toLowerCase(); - - return this.#sortStringsCompare(str1, str2); - }); - } - - #sortUnitsByCoalition() { - this.#units.sort((unit1: Unit, unit2: Unit) => { - let str1 = unit1.getCoalition(); - let str2 = unit2.getCoalition(); - - let cmp = this.#sortStringsCompare(str1, str2); - - if (cmp !== 0) - return cmp; - - str1 = unit1.getUnitName().toLowerCase(); - str2 = unit2.getUnitName().toLowerCase(); - - return this.#sortStringsCompare(str1, str2); - }); - } - - #sortUnitsByName() { - this.#units.sort((unit1: Unit, unit2: Unit) => { - const str1 = unit1.getName().toLowerCase(); - const str2 = unit2.getName().toLowerCase(); - return this.#sortStringsCompare(str1, str2); - }); - } - - startUpdates() { - this.doUpdate(); - - this.#updatesInterval = setInterval(() => { - this.doUpdate(); - }, 4000); - } - - stopUpdates() { - clearInterval(this.#updatesInterval); - } - - toggle() { - if (this.getVisible()) - this.stopUpdates(); - else - this.startUpdates(); - - super.toggle(); - } -} diff --git a/frontend/website/src/plugin/pluginmanager.ts b/frontend/website/src/plugin/pluginmanager.ts deleted file mode 100644 index 029de939..00000000 --- a/frontend/website/src/plugin/pluginmanager.ts +++ /dev/null @@ -1,74 +0,0 @@ -import path from "path"; -import { Manager } from "../other/manager"; -import { getApp } from ".."; -import { OlympusPlugin } from "../interfaces"; - -/** The plugins manager is responsible for loading and initializing all the plugins. Plugins are located in the public/plugins folder. - * Each plugin must be comprised of a single folder containing a index.js file. Each plugin must set the globalThis.getOlympusPlugin variable to - * return a valid class implementing the OlympusPlugin interface. - */ - -export class PluginsManager extends Manager { - constructor() { - super(); - - var xhr = new XMLHttpRequest(); - xhr.open('GET', "/plugins/list", true); - xhr.responseType = 'json'; - xhr.onload = () => { - var status = xhr.status; - if (status === 200) { - this.#loadPlugins(xhr.response); - } else { - console.error(`Error retrieving plugins`) - } - }; - xhr.send(); - } - - #loadPlugins(pluginsFolders: string[]) { - pluginsFolders.forEach((pluginName: string) => { - var xhr = new XMLHttpRequest(); - xhr.open('GET', path.join("/plugins", pluginName, "index.js"), true); - xhr.responseType = 'text'; - xhr.onload = () => { - var status = xhr.status; - if (status === 200) { - /* Inject the plugin style */ - var link = document.createElement("link"); - link.href = path.join("/plugins", pluginName, "style.css"); - link.type = "text/css"; - link.rel = "stylesheet"; - document.getElementsByTagName("head")[0].appendChild(link); - - /* Evaluate the plugin javascript */ - var plugin: OlympusPlugin | null = null; - try { - eval(xhr.response); - plugin = globalThis.getOlympusPlugin() as OlympusPlugin; - console.log(plugin.getName() + " loaded correctly"); - } catch (error: any) { - console.log("An error occured while loading a plugin from " + pluginName); - console.log(error); - } - - /* If the plugin was loaded, try to initialize it */ - if (plugin != null) { - try { - if (plugin.initialize(getApp())) { - console.log(plugin.getName() + " initialized correctly"); - this.add(pluginName, plugin); - } - } catch (error: any) { - console.log("An error occured while initializing a plugin from " + pluginName); - console.log(error); - } - } - } else { - console.error(`Error retrieving plugin from ${pluginName}`) - } - }; - xhr.send(); - }) - } -} \ No newline at end of file diff --git a/frontend/website/src/popups/popup.ts b/frontend/website/src/popups/popup.ts deleted file mode 100644 index 3d4ea5bb..00000000 --- a/frontend/website/src/popups/popup.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Panel } from "../panels/panel"; - -export class PopupMessage { - #element: HTMLDivElement; - #fadeTime: number = 2000; // Milliseconds - - constructor(text: string, fateTime: number) { - this.#element = document.createElement("div"); - - this.#fadeTime = fateTime; - this.#element.innerText = text; - this.#element.classList.remove("invisible"); - this.#element.classList.add("visible"); - - window.setTimeout(() => { - this.#element.classList.remove("visible"); - this.#element.classList.add("invisible"); - window.setTimeout(() => { - this.#element.dispatchEvent(new Event("removed")); - this.#element.remove(); - }, 2000); - }, this.#fadeTime); - } - - getElement() { - return this.#element; - } -} - -export class Popup extends Panel { - #fadeTime: number = 2000; // Milliseconds - #messages: PopupMessage[] = []; - #stackAfter: number; - - constructor(ID: string, stackAfter: number = 3) { - super(ID); - this.show(); - this.#stackAfter = stackAfter; - } - - setFadeTime(fadeTime: number) { - this.#fadeTime = fadeTime; - } - - setText(text: string) { - var message = new PopupMessage(text, this.#fadeTime); - message.getElement().addEventListener("removed", () => { - var index = this.#messages.indexOf(message); - if (index !== -1) - this.#messages.splice(index, 1); - }) - this.getElement().appendChild(message.getElement()); - this.#messages.push(message); - if (this.#messages.length > this.#stackAfter) { - this.#messages[this.#messages.length - this.#stackAfter - 1].getElement().classList.add("ol-popup-stack"); - } - } -} \ No newline at end of file diff --git a/frontend/website/src/schemas/airbases.schema.json b/frontend/website/src/schemas/airbases.schema.json deleted file mode 100644 index c43f57d3..00000000 --- a/frontend/website/src/schemas/airbases.schema.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "type": "object", - "additionalProperties": false, - "properties": { - "airfields": { - "type": "object", - "minProperties": 1, - "patternProperties": { - ".*": { - "type": "object", - "properties": { - "elevation": { - "type": "string", - "pattern": "^(0|([1-9][0-9]*))?$" - }, - "ICAO": { - "type": "string" - }, - "runways": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "headings": { - "type": "array", - "items": { - "type": "object", - "patternProperties": { - ".*": { - "type": "object", - "properties": { - "ILS": { - "type": "string", - "pattern": "^(1[0-9]{1,2}\\.[0-9][05])?$" - }, - "magHeading": { - "type": "string", - "pattern": "^([0-2][0-9]{2})|(3(([0-5][0-9])|(60)))?$" - } - }, - "required": ["magHeading"] - } - } - }, - "minItems": 1 - }, - "length": { - "type": "string", - "pattern": "^[1-9][0-9]{3,4}$" - } - }, - "required": [ "headings", "length" ] - } - }, - "TACAN": { - "type": "string", - "pattern": "^([1-9][0-9]{1,2}X)?$" - } - }, - "required": [ "elevation", "runways" ] - } - } - } - }, - "required": ["airfields"] -} \ No newline at end of file diff --git a/frontend/website/src/schemas/importdata.schema.json b/frontend/website/src/schemas/importdata.schema.json deleted file mode 100644 index 43e9cfd2..00000000 --- a/frontend/website/src/schemas/importdata.schema.json +++ /dev/null @@ -1,428 +0,0 @@ -{ - "$defs": { - "coalitionName": { - "enum": [ - "blue", - "neutral", - "red" - ], - "type": "string" - }, - "lat": { - "maximum": 90, - "minimum": -90, - "type": "number" - }, - "lng": { - "maximum": 180, - "minimum": -180, - "type": "number" - }, - "vec2": { - "additionalProperties": false, - "properties": { - "lat": { - "$ref": "#/$defs/lat" - }, - "lng": { - "$ref": "#/$defs/lng" - } - }, - "required": [ - "lat", - "lng" - ], - "type": "object" - }, - "vec3": { - "additionalProperties": false, - "properties": { - "alt": { - "type": "number" - }, - "lat": { - "$ref": "#/$defs/lat" - }, - "lng": { - "$ref": "#/$defs/lng" - } - }, - "required": [ - "alt", - "lat", - "lng" - ], - "type": "object" - } - }, - "patternProperties": { - ".*": { - "items": { - "additionalProperties": false, - "properties": { - "activePath": { - "items": { - "$ref": "#/$defs/vec3" - }, - "type": "array" - }, - "alive": { - "type": "boolean" - }, - "ammo": { - "items": { - "additionalProperties": false, - "properties": { - "category": { - "minimum": 0, - "type": "number" - }, - "guidance": { - "minimum": 0, - "type": "number" - }, - "missileCategory": { - "minimum": 0, - "type": "number" - }, - "name": { - "minLength": 3, - "type": "string" - }, - "quantity": { - "minimum": 0, - "type": "number" - } - }, - "required": [ - "quantity", - "name", - "guidance", - "category", - "missileCategory" - ], - "type": "object" - }, - "type": "array" - }, - "category": { - "type": "string" - }, - "categoryDisplayName": { - "type": "string" - }, - "coalition": { - "$ref": "#/$defs/coalitionName" - }, - "contacts": { - "type": "array" - }, - "controlled": { - "type": "boolean" - }, - "country": { - "type": "number" - }, - "desiredAltitude": { - "minimum": 0, - "type": "number" - }, - "desiredAltitudeType": { - "enum": [ - "AGL", - "ASL" - ], - "type": "string" - }, - "desiredSpeed": { - "minimum": 0, - "type": "number" - }, - "desiredSpeedType": { - "enum": [ - "CAS", - "GS" - ], - "type": "string" - }, - "emissionsCountermeasures": { - "enum": [ - "attack", - "defend", - "free", - "silent" - ], - "type": "string" - }, - "followRoads": { - "type": "boolean" - }, - "formationOffset": { - "additionalProperties": false, - "properties": { - "x": { - "minimum": 0, - "type": "number" - }, - "y": { - "minimum": 0, - "type": "number" - }, - "z": { - "minimum": 0, - "type": "number" - } - }, - "required": [ - "x", - "y", - "z" - ], - "type": "object" - }, - "fuel": { - "maximum": 100, - "minimum": 0, - "type": "number" - }, - "generalSettings": { - "additionalProperties": false, - "properties": { - "prohibitAA": { - "type": "boolean" - }, - "prohibitAfterburner": { - "type": "boolean" - }, - "prohibitAG": { - "type": "boolean" - }, - "prohibitAirWpn": { - "type": "boolean" - }, - "prohibitJettison": { - "type": "boolean" - } - }, - "required": [ - "prohibitAA", - "prohibitAfterburner", - "prohibitAG", - "prohibitAirWpn", - "prohibitJettison" - ], - "type": "object" - }, - "groupName": { - "type": "string" - }, - "hasTask": { - "type": "boolean" - }, - "heading": { - "type": "number" - }, - "health": { - "maximum": 100, - "minimum": 0, - "type": "number" - }, - "horizontalVelocity": { - "minimum": 0, - "type": "number" - }, - "human": { - "type": "boolean" - }, - "ID": { - "type": "number" - }, - "isActiveAWACS": { - "type": "boolean" - }, - "isActiveTanker": { - "type": "boolean" - }, - "isLeader": { - "type": "boolean" - }, - "leaderID": { - "minimum": 0, - "type": "number" - }, - "name": { - "type": "string" - }, - "onOff": { - "type": "boolean" - }, - "operateAs": { - "$ref": "#/$defs/coalitionName" - }, - "position": { - "$ref": "#/$defs/vec3" - }, - "radio": { - "additionalProperties": false, - "properties": { - "callsign": { - "type": "number" - }, - "callsignNumber": { - "type": "number" - }, - "frequency": { - "type": "number" - } - }, - "required": [ - "frequency", - "callsign", - "callsignNumber" - ], - "type": "object" - }, - "reactionToThreat": { - "enum": [ - "evade", - "maneouvre", - "none", - "passive" - ], - "type": "string" - }, - "ROE": { - "enum": [ - "designated", - "free", - "hold", - "return" - ], - "type": "string" - }, - "shotsIntensity": { - "maximum": 3, - "minimum": 1, - "type": "number" - }, - "shotsScatter": { - "maximum": 3, - "minimum": 1, - "type": "number" - }, - "speed": { - "minimum": 0, - "type": "number" - }, - "state": { - "type": "string" - }, - "TACAN": { - "properties": { - "callsign": { - "type": "string" - }, - "channel": { - "minimum": 0, - "type": "number" - }, - "isOn": { - "type": "boolean" - }, - "XY": { - "enum": [ - "X", - "Y" - ], - "type": "string" - } - }, - "required": [ - "callsign", - "channel", - "isOn", - "XY" - ], - "type": "object" - }, - "targetID": { - "minimum": 0, - "type": "number" - }, - "targetPosition": { - "anyOf": [ - {"$ref": "#/$defs/vec2"}, - {"$ref": "#/$defs/vec3"} - ] - }, - "task": { - "type": "string" - }, - "track": { - "type": "number" - }, - "unitName": { - "type": "string" - }, - "verticalVelocity": { - "minimum": 0, - "type": "number" - } - }, - "type": "object", - "required": [ - "activePath", - "alive", - "ammo", - "category", - "categoryDisplayName", - "coalition", - "contacts", - "controlled", - "country", - "desiredAltitude", - "desiredAltitudeType", - "desiredSpeed", - "desiredSpeedType", - "emissionsCountermeasures", - "followRoads", - "formationOffset", - "fuel", - "generalSettings", - "groupName", - "hasTask", - "heading", - "health", - "horizontalVelocity", - "human", - "ID", - "isActiveAWACS", - "isActiveTanker", - "isLeader", - "leaderID", - "name", - "onOff", - "operateAs", - "position", - "radio", - "reactionToThreat", - "ROE", - "shotsIntensity", - "shotsScatter", - "speed", - "state", - "TACAN", - "targetID", - "targetPosition", - "task", - "track", - "unitName", - "verticalVelocity" - ] - }, - "minItems": 1, - "type": "array" - } - }, - "type": "object" -} \ No newline at end of file diff --git a/frontend/website/src/schemas/schema.ts b/frontend/website/src/schemas/schema.ts deleted file mode 100644 index a332f845..00000000 --- a/frontend/website/src/schemas/schema.ts +++ /dev/null @@ -1,50 +0,0 @@ -import Ajv from "ajv"; -import { AnySchemaObject } from "ajv/dist/core"; - - -// For future extension -abstract class JSONSchemaValidator { - - #ajv: Ajv; - #compiledValidator: any; - #schema!: AnySchemaObject; - - constructor(schema: AnySchemaObject) { - this.#schema = schema; - - this.#ajv = new Ajv({ - "allErrors": true - }); - - this.#compiledValidator = this.getAjv().compile(this.getSchema()); - - } - - getAjv() { - return this.#ajv; - } - - getCompiledValidator() { - return this.#compiledValidator; - } - - getErrors() { - return this.getCompiledValidator().errors; - } - - getSchema() { - return this.#schema; - } - - validate(data: any) { - return (this.getCompiledValidator())(data); - } - -} - -export class ImportFileJSONSchemaValidator extends JSONSchemaValidator { - constructor() { - const schema = require("../schemas/importdata.schema.json"); - super(schema); - } -} \ No newline at end of file diff --git a/frontend/website/src/server/dataextractor.ts b/frontend/website/src/server/dataextractor.ts deleted file mode 100644 index 2f255ce1..00000000 --- a/frontend/website/src/server/dataextractor.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { LatLng } from "leaflet"; -import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../interfaces"; - -export class DataExtractor { - #seekPosition = 0; - #dataview: DataView; - #decoder: TextDecoder; - #buffer: ArrayBuffer; - - constructor(buffer: ArrayBuffer) { - this.#buffer = buffer; - this.#dataview = new DataView(this.#buffer); - this.#decoder = new TextDecoder("utf-8"); - } - - setSeekPosition(seekPosition: number) { - this.#seekPosition = seekPosition; - } - - getSeekPosition() { - return this.#seekPosition; - } - - extractBool() { - const value = this.#dataview.getUint8(this.#seekPosition); - this.#seekPosition += 1; - return value > 0; - } - - extractUInt8() { - const value = this.#dataview.getUint8(this.#seekPosition); - this.#seekPosition += 1; - return value; - } - - extractUInt16() { - const value = this.#dataview.getUint16(this.#seekPosition, true); - this.#seekPosition += 2; - return value; - } - - extractUInt32() { - const value = this.#dataview.getUint32(this.#seekPosition, true); - this.#seekPosition += 4; - return value; - } - - extractUInt64() { - const value = this.#dataview.getBigUint64(this.#seekPosition, true); - this.#seekPosition += 8; - return value; - } - - extractFloat64() { - const value = this.#dataview.getFloat64(this.#seekPosition, true); - this.#seekPosition += 8; - return value; - } - - extractLatLng() { - return new LatLng(this.extractFloat64(), this.extractFloat64(), this.extractFloat64()) - } - - extractFromBitmask(bitmask: number, position: number) { - return ((bitmask >> position) & 1) > 0; - } - - extractString(length?: number) { - if (length === undefined) - length = this.extractUInt16() - var stringBuffer = this.#buffer.slice(this.#seekPosition, this.#seekPosition + length); - var view = new Int8Array(stringBuffer); - var stringLength = length; - view.every((value: number, idx: number) => { - if (value === 0) { - stringLength = idx; - return false; - } else - return true; - }); - const value = this.#decoder.decode(stringBuffer); - this.#seekPosition += length; - return value.substring(0, stringLength).trim(); - } - - extractChar() { - return this.extractString(1); - } - - extractTACAN() { - const value: TACAN = { - isOn: this.extractBool(), - channel: this.extractUInt8(), - XY: this.extractChar(), - callsign: this.extractString(4) - } - return value; - } - - extractRadio() { - const value: Radio = { - frequency: this.extractUInt32(), - callsign: this.extractUInt8(), - callsignNumber: this.extractUInt8() - } - return value; - } - - extractGeneralSettings() { - const value: GeneralSettings = { - prohibitJettison: this.extractBool(), - prohibitAA: this.extractBool(), - prohibitAG: this.extractBool(), - prohibitAfterburner: this.extractBool(), - prohibitAirWpn: this.extractBool(), - } - return value; - } - - extractAmmo() { - const value: Ammo[] = []; - const size = this.extractUInt16(); - for (let idx = 0; idx < size; idx++) { - value.push({ - quantity: this.extractUInt16(), - name: this.extractString(33), - guidance: this.extractUInt8(), - category: this.extractUInt8(), - missileCategory: this.extractUInt8() - }); - } - return value; - } - - extractContacts(){ - const value: Contact[] = []; - const size = this.extractUInt16(); - for (let idx = 0; idx < size; idx++) { - value.push({ - ID: this.extractUInt32(), - detectionMethod: this.extractUInt8() - }); - } - return value; - } - - extractActivePath() { - const value: LatLng[] = []; - const size = this.extractUInt16(); - for (let idx = 0; idx < size; idx++) { - value.push(this.extractLatLng()); - } - return value; - } - - extractOffset() { - const value: Offset = { - x: this.extractFloat64(), - y: this.extractFloat64(), - z: this.extractFloat64(), - } - return value; - } -} \ No newline at end of file diff --git a/frontend/website/src/server/servermanager.ts b/frontend/website/src/server/servermanager.ts deleted file mode 100644 index 6555c3f1..00000000 --- a/frontend/website/src/server/servermanager.ts +++ /dev/null @@ -1,602 +0,0 @@ -import { LatLng } from 'leaflet'; -import { getApp } from '..'; -import { AIRBASES_URI, BULLSEYE_URI, COMMANDS_URI, LOGS_URI, MISSION_URI, NONE, ROEs, UNITS_URI, WEAPONS_URI, emissionsCountermeasures, reactionsToThreat } from '../constants/constants'; -import { ServerStatusPanel } from '../panels/serverstatuspanel'; -import { LogPanel } from '../panels/logpanel'; -import { Popup } from '../popups/popup'; -import { ConnectionStatusPanel } from '../panels/connectionstatuspanel'; -import { AirbasesData, BullseyesData, GeneralSettings, MissionData, Radio, ServerRequestOptions, TACAN } from '../interfaces'; -import { zeroAppend } from '../other/utils'; - -export class ServerManager { - #connected: boolean = false; - #paused: boolean = false; - #REST_ADDRESS = "http://localhost:3001/olympus"; - #username = ""; - #password = ""; - #sessionHash: string | null = null; - #lastUpdateTimes: { [key: string]: number } = {} - #previousMissionElapsedTime: number = 0; // Track if mission elapsed time is increasing (i.e. is the server paused) - #serverIsPaused: boolean = false; - #intervals: number[] = []; - #requests: { [key: string]: XMLHttpRequest } = {}; - - constructor() { - this.#lastUpdateTimes[UNITS_URI] = Date.now(); - this.#lastUpdateTimes[WEAPONS_URI] = Date.now(); - this.#lastUpdateTimes[LOGS_URI] = Date.now(); - this.#lastUpdateTimes[AIRBASES_URI] = Date.now(); - this.#lastUpdateTimes[BULLSEYE_URI] = Date.now(); - this.#lastUpdateTimes[MISSION_URI] = Date.now(); - } - - setCredentials(newUsername: string, newPassword: string) { - this.#username = newUsername; - this.#password = newPassword; - } - - GET(callback: CallableFunction, uri: string, options?: ServerRequestOptions, responseType: string = 'text', force: boolean = false) { - var xmlHttp = new XMLHttpRequest(); - - /* If a request on this uri is still pending (meaning it's not done or did not yet fail), skip the request, to avoid clogging the TCP workers */ - /* If we are forcing the request we don't care if one already exists, just send it. CAREFUL: this makes sense only for low frequency requests, like refreshes, when we - are reasonably confident any previous request will be done before we make a new one on the same URI. */ - if (uri in this.#requests && this.#requests[uri].readyState !== 4 && !force) { - console.warn(`GET request on ${uri} URI still pending, skipping...`); - return; - } - - if (!force) - this.#requests[uri] = xmlHttp; - - /* Assemble the request options string */ - var optionsString = ''; - if (options?.time != undefined) - optionsString = `time=${options.time}`; - if (options?.commandHash != undefined) - optionsString = `commandHash=${options.commandHash}`; - - /* On the connection */ - xmlHttp.open("GET", `${this.#REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true); - - /* If provided, set the credentials */ - if (this.#username && this.#password) - xmlHttp.setRequestHeader("Authorization", "Basic " + btoa(`${this.#username}:${this.#password}`)); - - /* If specified, set the response type */ - if (responseType) - xmlHttp.responseType = responseType as XMLHttpRequestResponseType; - - xmlHttp.onload = (e) => { - if (xmlHttp.status == 200) { - /* Success */ - this.setConnected(true); - if (xmlHttp.responseType == 'arraybuffer') - this.#lastUpdateTimes[uri] = callback(xmlHttp.response); - else { - const result = JSON.parse(xmlHttp.responseText); - this.#lastUpdateTimes[uri] = callback(result); - - if (result.frameRate !== undefined && result.load !== undefined) - (getApp().getPanelsManager().get("serverStatus") as ServerStatusPanel).update(result.frameRate, result.load); - } - } else if (xmlHttp.status == 401) { - /* Bad credentials */ - console.error("Incorrect username/password"); - getApp().setLoginStatus("failed"); - } else { - /* Failure, probably disconnected */ - this.setConnected(false); - } - }; - xmlHttp.onreadystatechange = (res) => { - if (xmlHttp.readyState == 4 && xmlHttp.status === 0) { - console.error("An error occurred during the XMLHttpRequest"); - this.setConnected(false); - } - }; - xmlHttp.send(null); - } - - PUT(request: object, callback: CallableFunction) { - var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("PUT", this.#REST_ADDRESS); - xmlHttp.setRequestHeader("Content-Type", "application/json"); - if (this.#username && this.#password) - xmlHttp.setRequestHeader("Authorization", "Basic " + btoa(`${this.#username}:${this.#password}`)); - xmlHttp.onload = (res: any) => { - var res = JSON.parse(xmlHttp.responseText); - callback(res); - }; - xmlHttp.send(JSON.stringify(request)); - } - - getConfig(callback: CallableFunction) { - var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("GET", window.location.href.split('?')[0] + "config", true); - xmlHttp.onload = function (e) { - var data = JSON.parse(xmlHttp.responseText); - callback(data); - }; - xmlHttp.onerror = function () { - console.error("An error occurred during the XMLHttpRequest, could not retrieve configuration file"); - }; - xmlHttp.send(null); - } - - setAddress(address: string) { - this.#REST_ADDRESS = `${address}olympus` - console.log(`Setting REST address to ${this.#REST_ADDRESS}`) - } - - getAirbases(callback: CallableFunction) { - this.GET(callback, AIRBASES_URI); - } - - getBullseye(callback: CallableFunction) { - this.GET(callback, BULLSEYE_URI); - } - - getLogs(callback: CallableFunction, refresh: boolean = false) { - this.GET(callback, LOGS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[LOGS_URI] }, 'text', refresh); - } - - getMission(callback: CallableFunction) { - this.GET(callback, MISSION_URI); - } - - getUnits(callback: CallableFunction, refresh: boolean = false) { - this.GET(callback, UNITS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[UNITS_URI] }, 'arraybuffer', refresh); - } - - getWeapons(callback: CallableFunction, refresh: boolean = false) { - this.GET(callback, WEAPONS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[WEAPONS_URI] }, 'arraybuffer', refresh); - } - - isCommandExecuted(callback: CallableFunction, commandHash: string) { - this.GET(callback, COMMANDS_URI, { commandHash: commandHash }); - } - - addDestination(ID: number, path: any, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "path": path } - var data = { "setPath": command } - this.PUT(data, callback); - } - - spawnSmoke(color: string, latlng: LatLng, callback: CallableFunction = () => { }) { - var command = { "color": color, "location": latlng }; - var data = { "smoke": command } - this.PUT(data, callback); - } - - 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); - } - - spawnAircrafts(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => { }) { - var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "country": country, "immediate": immediate, "spawnPoints": spawnPoints }; - var data = { "spawnAircrafts": command } - this.PUT(data, callback); - } - - spawnHelicopters(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => { }) { - var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "country": country, "immediate": immediate, "spawnPoints": spawnPoints }; - var data = { "spawnHelicopters": command } - this.PUT(data, callback); - } - - spawnGroundUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => { }) { - var command = { "units": units, "coalition": coalition, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };; - var data = { "spawnGroundUnits": command } - this.PUT(data, callback); - } - - spawnNavyUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => { }) { - var command = { "units": units, "coalition": coalition, "country": country, "immediate": immediate, "spawnPoints": spawnPoints }; - var data = { "spawnNavyUnits": command } - this.PUT(data, callback); - } - - attackUnit(ID: number, targetID: number, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "targetID": targetID }; - var data = { "attackUnit": command } - this.PUT(data, callback); - } - - followUnit(ID: number, targetID: number, offset: { "x": number, "y": number, "z": number }, callback: CallableFunction = () => { }) { - // X: front-rear, positive front - // Y: top-bottom, positive bottom - // Z: left-right, positive right - - var command = { "ID": ID, "targetID": targetID, "offsetX": offset["x"], "offsetY": offset["y"], "offsetZ": offset["z"] }; - var data = { "followUnit": command } - this.PUT(data, callback); - } - - cloneUnits(units: { ID: number, location: LatLng }[], deleteOriginal: boolean, spawnPoints: number, callback: CallableFunction = () => { }) { - var command = { "units": units, "deleteOriginal": deleteOriginal, "spawnPoints": spawnPoints }; - var data = { "cloneUnits": command } - this.PUT(data, callback); - } - - 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); - } - - landAt(ID: number, latlng: LatLng, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "location": latlng }; - var data = { "landAt": command } - this.PUT(data, callback); - } - - changeSpeed(ID: number, speedChange: string, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "change": speedChange } - var data = { "changeSpeed": command } - this.PUT(data, callback); - } - - setSpeed(ID: number, speed: number, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "speed": speed } - var data = { "setSpeed": command } - this.PUT(data, callback); - } - - setSpeedType(ID: number, speedType: string, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "speedType": speedType } - var data = { "setSpeedType": command } - this.PUT(data, callback); - } - - changeAltitude(ID: number, altitudeChange: string, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "change": altitudeChange } - var data = { "changeAltitude": command } - this.PUT(data, callback); - } - - setAltitudeType(ID: number, altitudeType: string, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "altitudeType": altitudeType } - var data = { "setAltitudeType": command } - this.PUT(data, callback); - } - - setAltitude(ID: number, altitude: number, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "altitude": altitude } - var data = { "setAltitude": command } - this.PUT(data, callback); - } - - createFormation(ID: number, isLeader: boolean, wingmenIDs: number[], callback: CallableFunction = () => { }) { - var command = { "ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader } - var data = { "setLeader": command } - this.PUT(data, callback); - } - - setROE(ID: number, ROE: string, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "ROE": ROEs.indexOf(ROE) } - var data = { "setROE": command } - this.PUT(data, callback); - } - - setReactionToThreat(ID: number, reactionToThreat: string, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "reactionToThreat": reactionsToThreat.indexOf(reactionToThreat) } - var data = { "setReactionToThreat": command } - this.PUT(data, callback); - } - - setEmissionsCountermeasures(ID: number, emissionCountermeasure: string, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "emissionsCountermeasures": emissionsCountermeasures.indexOf(emissionCountermeasure) } - var data = { "setEmissionsCountermeasures": command } - this.PUT(data, callback); - } - - setOnOff(ID: number, onOff: boolean, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "onOff": onOff } - var data = { "setOnOff": command } - this.PUT(data, callback); - } - - setFollowRoads(ID: number, followRoads: boolean, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "followRoads": followRoads } - var data = { "setFollowRoads": command } - this.PUT(data, callback); - } - - setOperateAs(ID: number, operateAs: number, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "operateAs": operateAs } - var data = { "setOperateAs": command } - this.PUT(data, callback); - } - - - refuel(ID: number, callback: CallableFunction = () => { }) { - var command = { "ID": ID }; - var data = { "refuel": command } - this.PUT(data, callback); - } - - bombPoint(ID: number, latlng: LatLng, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "location": latlng } - var data = { "bombPoint": command } - this.PUT(data, callback); - } - - carpetBomb(ID: number, latlng: LatLng, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "location": latlng } - var data = { "carpetBomb": command } - this.PUT(data, callback); - } - - bombBuilding(ID: number, latlng: LatLng, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "location": latlng } - var data = { "bombBuilding": command } - this.PUT(data, callback); - } - - fireAtArea(ID: number, latlng: LatLng, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "location": latlng } - var data = { "fireAtArea": command } - this.PUT(data, callback); - } - - simulateFireFight(ID: number, latlng: LatLng, altitude: number, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "location": latlng, "altitude": altitude } - var data = { "simulateFireFight": command } - this.PUT(data, callback); - } - - // TODO: Remove coalition - scenicAAA(ID: number, coalition: string, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "coalition": coalition } - var data = { "scenicAAA": command } - this.PUT(data, callback); - } - - // TODO: Remove coalition - missOnPurpose(ID: number, coalition: string, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "coalition": coalition } - var data = { "missOnPurpose": command } - this.PUT(data, callback); - } - - landAtPoint(ID: number, latlng: LatLng, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "location": latlng } - var data = { "landAtPoint": command } - this.PUT(data, callback); - } - - setShotsScatter(ID: number, shotsScatter: number, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "shotsScatter": shotsScatter } - var data = { "setShotsScatter": command } - this.PUT(data, callback); - } - - setShotsIntensity(ID: number, shotsIntensity: number, callback: CallableFunction = () => { }) { - var command = { "ID": ID, "shotsIntensity": shotsIntensity } - var data = { "setShotsIntensity": command } - this.PUT(data, callback); - } - - setAdvacedOptions(ID: number, isActiveTanker: boolean, isActiveAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback: CallableFunction = () => { }) { - var command = { - "ID": ID, - "isActiveTanker": isActiveTanker, - "isActiveAWACS": isActiveAWACS, - "TACAN": TACAN, - "radio": radio, - "generalSettings": generalSettings - }; - - var data = { "setAdvancedOptions": command }; - this.PUT(data, callback); - } - - setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: { blue: number, red: number }, eras: string[], setupTime: number, callback: CallableFunction = () => { }) { - var command = { - "restrictSpawns": restrictSpawns, - "restrictToCoalition": restrictToCoalition, - "spawnPoints": spawnPoints, - "eras": eras, - "setupTime": setupTime - }; - - var data = { "setCommandModeOptions": command }; - this.PUT(data, callback); - } - - reloadDatabases(callback: CallableFunction = () => { }) { - var data = { "reloadDatabases": {} }; - this.PUT(data, callback); - } - - startUpdate() { - /* Clear any existing interval */ - this.#intervals.forEach((interval: number) => { window.clearInterval(interval); }); - this.#intervals = []; - - this.#intervals.push(window.setInterval(() => { - if (!this.getPaused()) { - this.getMission((data: MissionData) => { - this.checkSessionHash(data.sessionHash); - getApp().getMissionManager()?.updateMission(data); - return data.time; - }); - } - }, 1000)); - - this.#intervals.push(window.setInterval(() => { - if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { - this.getAirbases((data: AirbasesData) => { - this.checkSessionHash(data.sessionHash); - getApp().getMissionManager()?.updateAirbases(data); - return data.time; - }); - } - }, 10000)); - - this.#intervals.push(window.setInterval(() => { - if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { - this.getBullseye((data: BullseyesData) => { - this.checkSessionHash(data.sessionHash); - getApp().getMissionManager()?.updateBullseyes(data); - return data.time; - }); - } - }, 10000)); - - this.#intervals.push(window.setInterval(() => { - if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { - this.getLogs((data: any) => { - this.checkSessionHash(data.sessionHash); - (getApp().getPanelsManager().get("log") as LogPanel).appendLogs(data.logs) - return data.time; - }); - } - }, 1000)); - - this.#intervals.push(window.setInterval(() => { - if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { - this.getUnits((buffer: ArrayBuffer) => { - var time = getApp().getUnitsManager()?.update(buffer); - return time; - }, false); - } - }, 250)); - - this.#intervals.push(window.setInterval(() => { - if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { - this.getWeapons((buffer: ArrayBuffer) => { - var time = getApp().getWeaponsManager()?.update(buffer); - return time; - }, false); - } - }, 250)); - - this.#intervals.push(window.setInterval(() => { - if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { - this.getUnits((buffer: ArrayBuffer) => { - var time = getApp().getUnitsManager()?.update(buffer); - return time; - }, true); - - const elapsedMissionTime = getApp().getMissionManager().getDateAndTime().elapsedTime; - this.#serverIsPaused = (elapsedMissionTime === this.#previousMissionElapsedTime); - this.#previousMissionElapsedTime = elapsedMissionTime; - - const csp = (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel); - - if (this.getConnected()) { - if (this.getServerIsPaused()) { - csp.showServerPaused(); - } else { - csp.showConnected(); - } - } else { - csp.showDisconnected(); - } - - } - }, (this.getServerIsPaused() ? 500 : 5000))); - - // Mission clock and elapsed time - this.#intervals.push(window.setInterval(() => { - - if (!this.getConnected() || this.#serverIsPaused) { - return; - } - - const elapsedMissionTime = getApp().getMissionManager().getDateAndTime().elapsedTime; - - const csp = (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel); - const mt = getApp().getMissionManager().getDateAndTime().time; - - csp.setMissionTime([mt.h, mt.m, mt.s].map(n => zeroAppend(n, 2)).join(":")); - csp.setElapsedTime(new Date(elapsedMissionTime * 1000).toISOString().substring(11, 19)); - - }, 1000)); - - this.#intervals.push(window.setInterval(() => { - if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) { - this.getWeapons((buffer: ArrayBuffer) => { - var time = getApp().getWeaponsManager()?.update(buffer); - return time; - }, true); - } - }, 5000)); - } - - refreshAll() { - this.getAirbases((data: AirbasesData) => { - this.checkSessionHash(data.sessionHash); - getApp().getMissionManager()?.updateAirbases(data); - return data.time; - }); - - this.getBullseye((data: BullseyesData) => { - this.checkSessionHash(data.sessionHash); - getApp().getMissionManager()?.updateBullseyes(data); - return data.time; - }); - - this.getLogs((data: any) => { - this.checkSessionHash(data.sessionHash); - (getApp().getPanelsManager().get("log") as LogPanel).appendLogs(data.logs) - return data.time; - }); - - this.getWeapons((buffer: ArrayBuffer) => { - var time = getApp().getWeaponsManager()?.update(buffer); - return time; - }, true); - - this.getUnits((buffer: ArrayBuffer) => { - var time = getApp().getUnitsManager()?.update(buffer); - return time; - }, true); - } - - checkSessionHash(newSessionHash: string) { - if (this.#sessionHash != null) { - if (newSessionHash !== this.#sessionHash) - location.reload(); - } - else - this.#sessionHash = newSessionHash; - } - - setConnected(newConnected: boolean) { - if (this.#connected != newConnected) { - newConnected ? (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Connected to DCS Olympus server") : (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Disconnected from DCS Olympus server"); - if (newConnected) { - document.getElementById("splash-screen")?.classList.add("hide"); - document.getElementById("gray-out")?.classList.add("hide"); - } - } - - this.#connected = newConnected; - } - - getConnected() { - return this.#connected; - } - - setPaused(newPaused: boolean) { - this.#paused = newPaused; - this.#paused ? (getApp().getPopupsManager().get("infoPopup") as Popup).setText("View paused") : (getApp().getPopupsManager().get("infoPopup") as Popup).setText("View unpaused"); - } - - getPaused() { - return this.#paused; - } - - getServerIsPaused() { - return this.#serverIsPaused; - } - - getRequests() { - return this.#requests; - } -} diff --git a/frontend/website/src/shortcut/shortcut.ts b/frontend/website/src/shortcut/shortcut.ts deleted file mode 100644 index 39c6d362..00000000 --- a/frontend/website/src/shortcut/shortcut.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { getApp } from ".."; -import { ShortcutKeyboardOptions, ShortcutMouseOptions, ShortcutOptions } from "../interfaces"; -import { keyEventWasInInput } from "../other/utils"; - -export abstract class Shortcut { - #config: ShortcutOptions - - constructor(config: ShortcutOptions) { - this.#config = config; - } - - getConfig() { - return this.#config; - } -} - -export class ShortcutKeyboard extends Shortcut { - constructor(config: ShortcutKeyboardOptions) { - config.event = config.event || "keyup"; - super(config); - - document.addEventListener(config.event, (ev: any) => { - if ( typeof config.context === "string" && !getApp().getContextManager().currentContextIs( config.context ) ) { - return; - } - - if (ev instanceof KeyboardEvent === false || keyEventWasInInput(ev)) { - return; - } - - if (config.code !== ev.code) { - return; - } - - if (((typeof config.altKey !== "boolean") || (typeof config.altKey === "boolean" && ev.altKey === config.altKey)) - && ((typeof config.ctrlKey !== "boolean") || (typeof config.ctrlKey === "boolean" && ev.ctrlKey === config.ctrlKey)) - && ((typeof config.shiftKey !== "boolean") || (typeof config.shiftKey === "boolean" && ev.shiftKey === config.shiftKey))) { - config.callback(ev); - } - }); - } -} - -export class ShortcutMouse extends Shortcut { - constructor(config: ShortcutMouseOptions) { - super(config); - } -} \ No newline at end of file diff --git a/frontend/website/src/shortcut/shortcutmanager.ts b/frontend/website/src/shortcut/shortcutmanager.ts deleted file mode 100644 index 8d86f741..00000000 --- a/frontend/website/src/shortcut/shortcutmanager.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { ShortcutKeyboardOptions, ShortcutMouseOptions } from "../interfaces"; -import { Manager } from "../other/manager"; - -import { ShortcutKeyboard, ShortcutMouse } from "./shortcut"; - -export class ShortcutManager extends Manager { - - #keysBeingHeld: string[] = []; - #keyDownCallbacks: CallableFunction[] = []; - #keyUpCallbacks: CallableFunction[] = []; - - constructor() { - - super(); - - document.addEventListener("keydown", (ev: KeyboardEvent) => { - if (this.#keysBeingHeld.indexOf(ev.code) < 0) { - this.#keysBeingHeld.push(ev.code) - } - this.#keyDownCallbacks.forEach(callback => callback(ev)); - }); - - document.addEventListener("keyup", (ev: KeyboardEvent) => { - this.#keysBeingHeld = this.#keysBeingHeld.filter(held => held !== ev.code); - this.#keyUpCallbacks.forEach(callback => callback(ev)); - }); - - } - - add(name: string, shortcut: any) { - console.error("ShortcutManager:add() cannot be used. Use addKeyboardShortcut or addMouseShortcut."); - return this; - } - - addKeyboardShortcut(name: string, shortcutKeyboardOptions: ShortcutKeyboardOptions) { - super.add(name, new ShortcutKeyboard(shortcutKeyboardOptions)); - return this; - } - - addMouseShortcut(name: string, shortcutMouseOptions: ShortcutMouseOptions) { - super.add(name, new ShortcutMouse(shortcutMouseOptions)); - return this; - } - - getKeysBeingHeld() { - return this.#keysBeingHeld; - } - - keyComboMatches(combo: string[]) { - const heldKeys = this.getKeysBeingHeld(); - if (combo.length !== heldKeys.length) { - return false; - } - - return combo.every(key => heldKeys.indexOf(key) > -1); - } - - onKeyDown(callback: CallableFunction) { - this.#keyDownCallbacks.push(callback); - } - - onKeyUp(callback: CallableFunction) { - this.#keyUpCallbacks.push(callback); - } -} \ No newline at end of file diff --git a/frontend/website/src/toolbars/commandmodetoolbar.ts b/frontend/website/src/toolbars/commandmodetoolbar.ts deleted file mode 100644 index a551ee84..00000000 --- a/frontend/website/src/toolbars/commandmodetoolbar.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Toolbar } from "./toolbar"; - -export class CommandModeToolbar extends Toolbar { - // TODO move here all code about the command mode toolbar -} \ No newline at end of file diff --git a/frontend/website/src/toolbars/primarytoolbar.ts b/frontend/website/src/toolbars/primarytoolbar.ts deleted file mode 100644 index a9d477c1..00000000 --- a/frontend/website/src/toolbars/primarytoolbar.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getApp } from ".."; -import { Dropdown } from "../controls/dropdown"; -import { Switch } from "../controls/switch"; -import { Toolbar } from "./toolbar"; - -export class PrimaryToolbar extends Toolbar { - #mainDropdown: Dropdown; - #cameraLinkTypeSwitch: Switch; - - constructor(ID: string) { - super(ID); - - /* The content of the dropdown is entirely defined in the .ejs file */ - this.#mainDropdown = new Dropdown("app-icon", () => { }); - - this.#cameraLinkTypeSwitch = new Switch("camera-link-type-switch", (value: boolean) => { - getApp().getMap().setCameraControlMode(value? 'map': 'live'); - }) - } - - getMainDropdown() { - return this.#mainDropdown; - } -} \ No newline at end of file diff --git a/frontend/website/src/toolbars/toolbar.ts b/frontend/website/src/toolbars/toolbar.ts deleted file mode 100644 index 9ddd329e..00000000 --- a/frontend/website/src/toolbars/toolbar.ts +++ /dev/null @@ -1,38 +0,0 @@ -export class Toolbar { - #element: HTMLElement - #visible: boolean = true; - - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - this.#element = document.getElementById(ID) as HTMLElement; - } - - show() { - this.#element.classList.toggle("hide", false); - this.#visible = true; - } - - hide() { - this.#element.classList.toggle("hide", true); - this.#visible = false; - } - - toggle() { - // Simple way to track if currently visible - if (this.#visible) - this.hide(); - else - this.show(); - } - - getElement() { - return this.#element; - } - - getVisible(){ - return this.#visible; - } -} \ No newline at end of file diff --git a/frontend/website/src/unit/contextaction.ts b/frontend/website/src/unit/contextaction.ts deleted file mode 100644 index dd3a4b6b..00000000 --- a/frontend/website/src/unit/contextaction.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Unit } from "./unit"; - -export interface ContextActionOptions { - isScenic?: boolean -} - -export class ContextAction { - #id: string = ""; - #label: string = ""; - #description: string = ""; - #callback: CallableFunction | null = null; - #units: Unit[] = []; - #hideContextAfterExecution: boolean = true - #options: ContextActionOptions; - - constructor(id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true, options: ContextActionOptions) { - this.#id = id; - this.#label = label; - this.#description = description; - this.#callback = callback; - this.#hideContextAfterExecution = hideContextAfterExecution; - this.#options = { - "isScenic": false, - ...options - } - } - - addUnit(unit: Unit) { - this.#units.push(unit); - } - - getId() { - return this.#id; - } - - getLabel() { - return this.#label; - } - - getOptions() { - return this.#options; - } - - getDescription() { - return this.#description; - } - - getCallback() { - return this.#callback; - } - - executeCallback() { - if (this.#callback) - this.#callback(this.#units); - } - - getHideContextAfterExecution() { - return this.#hideContextAfterExecution; - } -} diff --git a/frontend/website/src/unit/contextactionset.ts b/frontend/website/src/unit/contextactionset.ts deleted file mode 100644 index d061cd1d..00000000 --- a/frontend/website/src/unit/contextactionset.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ContextAction, ContextActionOptions } from "./contextaction"; -import { Unit } from "./unit"; - -export class ContextActionSet { - #contextActions: {[key: string]: ContextAction} = {}; - - constructor() { - - } - - addContextAction(unit: Unit, id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true, options?:ContextActionOptions) { - options = options || {}; - - if (!(id in this.#contextActions)) { - this.#contextActions[id] = new ContextAction(id, label, description, callback, hideContextAfterExecution, options); - } - this.#contextActions[id].addUnit(unit); - } - - getContextActions() { - return this.#contextActions; - } - - -} \ No newline at end of file diff --git a/frontend/website/src/unit/databases/aircraftdatabase.ts b/frontend/website/src/unit/databases/aircraftdatabase.ts deleted file mode 100644 index bb0d58f3..00000000 --- a/frontend/website/src/unit/databases/aircraftdatabase.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { getApp } from "../.."; -import { GAME_MASTER } from "../../constants/constants"; -import { UnitDatabase } from "./unitdatabase" - -export class AircraftDatabase extends UnitDatabase { - constructor() { - super('api/databases/units/aircraftdatabase'); - } - - getCategory() { - return "Aircraft"; - } - - getSpawnPointsByName(name: string) { - if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns) - return 0; - - const blueprint = this.getByName(name); - if (blueprint?.cost != undefined) - return blueprint?.cost; - - if (blueprint?.era == "WW2") - return 20; - else if (blueprint?.era == "Early Cold War") - return 50; - else if (blueprint?.era == "Mid Cold War") - return 100; - else if (blueprint?.era == "Late Cold War") - return 200; - else if (blueprint?.era == "Modern") - return 400; - return 0; - } -} - -export var aircraftDatabase = new AircraftDatabase(); - diff --git a/frontend/website/src/unit/databases/citiesdatabase.ts b/frontend/website/src/unit/databases/citiesdatabase.ts deleted file mode 100644 index 5979ff28..00000000 --- a/frontend/website/src/unit/databases/citiesdatabase.ts +++ /dev/null @@ -1,7137 +0,0 @@ -export var citiesDatabase: {lat: number, lng: number, pop: number}[] = [ - { - lat: 41.0136, - lng: 28.955, - pop: 16079000, - }, - { - lat: 39.93, - lng: 32.85, - pop: 5503985, - }, - { - lat: 31.9497, - lng: 35.9328, - pop: 4007526, - }, - { - lat: 25.2631, - lng: 55.2972, - pop: 3331420, - }, - { - lat: 29.3697, - lng: 47.9783, - pop: 3000000, - }, - { - lat: 40.1833, - lng: 29.05, - pop: 2901396, - }, - { - lat: 36.8874, - lng: 30.7075, - pop: 2426356, - }, - { - lat: 32.6447, - lng: 51.6675, - pop: 2219343, - }, - { - lat: 36.2333, - lng: -115.2654, - pop: 2150373, - }, - { - lat: 37.0628, - lng: 37.3792, - pop: 2028563, - }, - { - lat: 37.1583, - lng: 38.7917, - pop: 1985753, - }, - { - lat: 36.2, - lng: 37.16, - pop: 1916781, - }, - { - lat: 36.8, - lng: 34.6333, - pop: 1814468, - }, - { - lat: 37, - lng: 35.3213, - pop: 1765981, - }, - { - lat: 33.5131, - lng: 36.2919, - pop: 1754000, - }, - { - lat: 29.61, - lng: 52.5425, - pop: 1565572, - }, - { - lat: 24.4667, - lng: 54.3667, - pop: 1483000, - }, - { - lat: 23.6139, - lng: 58.5922, - pop: 1421409, - }, - { - lat: 32.08, - lng: 34.78, - pop: 1388400, - }, - { - lat: 41.2903, - lng: 36.3336, - pop: 1335716, - }, - { - lat: 30.515, - lng: 47.81, - pop: 1326564, - }, - { - lat: 31.3203, - lng: 48.6692, - pop: 1261042, - }, - { - lat: 25.3575, - lng: 55.3908, - pop: 1247749, - }, - { - lat: 41.0167, - lng: 39.55, - pop: 1215351, - }, - { - lat: 25.2867, - lng: 51.5333, - pop: 1186023, - }, - { - lat: 47.2333, - lng: 39.7, - pop: 1137704, - }, - { - lat: 41.7225, - lng: 44.7925, - pop: 1118035, - }, - { - lat: 40.1814, - lng: 44.5144, - pop: 1075800, - }, - { - lat: 46.4775, - lng: 30.7326, - pop: 1017699, - }, - { - lat: 47.0228, - lng: 28.8353, - pop: 702300, - }, - { - lat: 26.225, - lng: 50.5775, - pop: 436000, - }, - { - lat: 33.8869, - lng: 35.5131, - pop: 361366, - }, - { - lat: 35.1725, - lng: 33.365, - pop: 330000, - }, - { - lat: 15.2137, - lng: 145.7546, - pop: 2500, - }, - { - lat: 13.4745, - lng: 144.7504, - pop: 1051, - }, - { - lat: 45.0333, - lng: 38.9667, - pop: 948827, - }, - { - lat: 39.9244, - lng: 32.8856, - pop: 914501, - }, - { - lat: 41.047, - lng: 28.658, - pop: 891120, - }, - { - lat: 26.4333, - lng: 50.1, - pop: 903312, - }, - { - lat: 39.7767, - lng: 30.5206, - pop: 871187, - }, - { - lat: 36.9831, - lng: 35.3328, - pop: 792536, - }, - { - lat: 41.0344, - lng: 28.8564, - pop: 734369, - }, - { - lat: 34.7333, - lng: 36.7167, - pop: 775404, - }, - { - lat: 41, - lng: 28.8, - pop: 770317, - }, - { - lat: 39.9086, - lng: 41.2769, - pop: 767848, - }, - { - lat: 24.2075, - lng: 55.7447, - pop: 766936, - }, - { - lat: 30.2833, - lng: 57.0833, - pop: 738374, - }, - { - lat: 35.5167, - lng: 35.7833, - pop: 700000, - }, - { - lat: 35.1333, - lng: 36.75, - pop: 696863, - }, - { - lat: 40.8747, - lng: 29.235, - pop: 693599, - }, - { - lat: 25.3833, - lng: 49.5833, - pop: 660788, - }, - { - lat: 40.11, - lng: 29.0821, - pop: 643681, - }, - { - lat: 25.25, - lng: 51.4, - pop: 605712, - }, - { - lat: 32.8192, - lng: 34.9992, - pop: 600000, - }, - { - lat: 42.9833, - lng: 47.4833, - pop: 592976, - }, - { - lat: 35.3529, - lng: -119.0359, - pop: 590845, - }, - { - lat: 29.4964, - lng: 60.8628, - pop: 587730, - }, - { - lat: 32.3399, - lng: 36.2052, - pop: 580000, - }, - { - lat: 32.55, - lng: 35.85, - pop: 569068, - }, - { - lat: 34.4367, - lng: 35.8344, - pop: 530000, - }, - { - lat: 46.35, - lng: 48.035, - pop: 532504, - }, - { - lat: 31.8822, - lng: 54.3397, - pop: 529673, - }, - { - lat: 27.1833, - lng: 56.2667, - pop: 526648, - }, - { - lat: 26.556, - lng: 49.996, - pop: 524182, - }, - { - lat: 44.605, - lng: 33.5225, - pop: 522057, - }, - { - lat: 46.975, - lng: 31.995, - pop: 498748, - }, - { - lat: 32.3436, - lng: 62.1194, - pop: 500000, - }, - { - lat: 25.4136, - lng: 55.4456, - pop: 490035, - }, - { - lat: 32.0833, - lng: 36.1, - pop: 481300, - }, - { - lat: 40.8872, - lng: 29.19, - pop: 461155, - }, - { - lat: 41.0719, - lng: 28.9664, - pop: 437026, - }, - { - lat: 41.0339, - lng: 28.8903, - pop: 444561, - }, - { - lat: 45.05, - lng: 41.9833, - pop: 450680, - }, - { - lat: 47.0958, - lng: 37.5494, - pop: 449498, - }, - { - lat: 40.9792, - lng: 28.7214, - pop: 435625, - }, - { - lat: 37.5833, - lng: 36.9333, - pop: 443575, - }, - { - lat: 41.005, - lng: 39.7225, - pop: 426882, - }, - { - lat: 43.5853, - lng: 39.7203, - pop: 411524, - }, - { - lat: 36.9981, - lng: 35.3439, - pop: 407054, - }, - { - lat: 29.35, - lng: 47.6833, - pop: 393432, - }, - { - lat: 27, - lng: 49.6544, - pop: 392948, - }, - { - lat: 34.5277, - lng: -117.3536, - pop: 389060, - }, - { - lat: 23.5333, - lng: 58.3833, - pop: 383257, - }, - { - lat: 34.6935, - lng: -118.1753, - pop: 381732, - }, - { - lat: 36.2025, - lng: 36.1606, - pop: 377793, - }, - { - lat: 39.75, - lng: 37.0167, - pop: 377561, - }, - { - lat: 40.8, - lng: 29.4333, - pop: 371000, - }, - { - lat: 30.3392, - lng: 48.3042, - pop: 370180, - }, - { - lat: 30.3833, - lng: 47.7, - pop: 370000, - }, - { - lat: 40.7625, - lng: 29.9175, - pop: 363416, - }, - { - lat: 43.2167, - lng: 27.9167, - pop: 348668, - }, - { - lat: 44.9484, - lng: 34.1, - pop: 341799, - }, - { - lat: 41.1669, - lng: 29.0572, - pop: 342503, - }, - { - lat: 36.9165, - lng: 34.8951, - pop: 339676, - }, - { - lat: 40.9683, - lng: 29.2617, - pop: 327798, - }, - { - lat: 41.0011, - lng: 28.6419, - pop: 331525, - }, - { - lat: 40.6828, - lng: 46.3606, - pop: 331400, - }, - { - lat: 39.6333, - lng: 27.8833, - pop: 331788, - }, - { - lat: 41.0225, - lng: 28.8717, - pop: 289331, - }, - { - lat: 36.55, - lng: 32, - pop: 312319, - }, - { - lat: 36.0133, - lng: -115.0381, - pop: 311250, - }, - { - lat: 43.04, - lng: 44.6775, - pop: 306978, - }, - { - lat: 35.95, - lng: 39.01, - pop: 299824, - }, - { - lat: 36.5817, - lng: 36.165, - pop: 297943, - }, - { - lat: 25.4416, - lng: 49.6642, - pop: 298562, - }, - { - lat: 40.5455, - lng: 34.957, - pop: 294807, - }, - { - lat: 43.3125, - lng: 45.6986, - pop: 291687, - }, - { - lat: 46.6425, - lng: 32.625, - pop: 291428, - }, - { - lat: 32.0178, - lng: 36.0464, - pop: 280000, - }, - { - lat: 44.1667, - lng: 28.6333, - pop: 283872, - }, - { - lat: 41.15, - lng: 27.8, - pop: 279251, - }, - { - lat: 31.5831, - lng: 64.3692, - pop: 276831, - }, - { - lat: 34.4175, - lng: -118.4964, - pop: 275230, - }, - { - lat: 44.7167, - lng: 37.75, - pop: 273278, - }, - { - lat: 40.7833, - lng: 30.4, - pop: 271515, - }, - { - lat: 35.3333, - lng: 40.15, - pop: 271800, - }, - { - lat: 41.1856, - lng: 28.7406, - pop: 270549, - }, - { - lat: 43.4833, - lng: 43.6167, - pop: 265162, - }, - { - lat: 40.0806, - lng: 29.5097, - pop: 268155, - }, - { - lat: 32.3825, - lng: 48.4019, - pop: 264709, - }, - { - lat: 37.075, - lng: 36.25, - pop: 264373, - }, - { - lat: 36.2883, - lng: -115.0888, - pop: 259638, - }, - { - lat: 40.8161, - lng: 29.3006, - pop: 255468, - }, - { - lat: 31.95, - lng: 34.8, - pop: 249860, - }, - { - lat: 41.0369, - lng: 29.1786, - pop: 251937, - }, - { - lat: 47.2167, - lng: 38.9167, - pop: 250287, - }, - { - lat: 37.1939, - lng: 40.5861, - pop: 252656, - }, - { - lat: 45.4233, - lng: 28.0425, - pop: 249432, - }, - { - lat: 36.3276, - lng: -119.3269, - pop: 249804, - }, - { - lat: 41.02, - lng: 28.5775, - pop: 247736, - }, - { - lat: 32.7003, - lng: 51.5211, - pop: 247128, - }, - { - lat: 41.1342, - lng: 29.0922, - pop: 246700, - }, - { - lat: 32.0889, - lng: 34.8864, - pop: 236169, - }, - { - lat: 40.8417, - lng: 31.1583, - pop: 240633, - }, - { - lat: 30.4411, - lng: 47.9725, - pop: 240300, - }, - { - lat: 34.6747, - lng: 33.0442, - pop: 235056, - }, - { - lat: 23.6703, - lng: 58.1891, - pop: 237816, - }, - { - lat: 32.6347, - lng: 51.3653, - pop: 235281, - }, - { - lat: 36.7833, - lng: 31.4333, - pop: 230597, - }, - { - lat: 40.9833, - lng: 37.8833, - pop: 229214, - }, - { - lat: 28.9264, - lng: 50.8514, - pop: 223504, - }, - { - lat: 26.2833, - lng: 50.2, - pop: 219679, - }, - { - lat: 36.0091, - lng: -115.2278, - pop: 219566, - }, - { - lat: 32.3286, - lng: 34.8567, - pop: 217244, - }, - { - lat: 36.0952, - lng: -115.2636, - pop: 217441, - }, - { - lat: 23.6167, - lng: 58.5667, - pop: 214901, - }, - { - lat: 33.5606, - lng: 35.3758, - pop: 200000, - }, - { - lat: 33.8936, - lng: 35.5403, - pop: 150000, - }, - { - lat: 32.0833, - lng: 34.8333, - pop: 193774, - }, - { - lat: 40.7347, - lng: 31.6075, - pop: 205525, - }, - { - lat: 32.0167, - lng: 34.7667, - pop: 194300, - }, - { - lat: 32.8667, - lng: 59.2167, - pop: 203636, - }, - { - lat: 25.69, - lng: 51.51, - pop: 202031, - }, - { - lat: 40.3139, - lng: 36.5542, - pop: 201294, - }, - { - lat: 34.4285, - lng: -119.7202, - pop: 198240, - }, - { - lat: 36.1783, - lng: -115.0487, - pop: 196411, - }, - { - lat: 37.2306, - lng: 39.7653, - pop: 195910, - }, - { - lat: 37.1847, - lng: 38.7908, - pop: 195552, - }, - { - lat: 37.1819, - lng: 33.2181, - pop: 194018, - }, - { - lat: 39.8417, - lng: 33.5139, - pop: 193093, - }, - { - lat: 45, - lng: 41.1167, - pop: 190709, - }, - { - lat: 32.3311, - lng: 50.8594, - pop: 190441, - }, - { - lat: 36.0872, - lng: -115.1355, - pop: 189852, - }, - { - lat: 36.5117, - lng: 40.7422, - pop: 188160, - }, - { - lat: 37.05, - lng: 41.22, - pop: 184231, - }, - { - lat: 45.2692, - lng: 27.9575, - pop: 180302, - }, - { - lat: 41.2792, - lng: 31.4208, - pop: 175605, - }, - { - lat: 29.437, - lng: 55.6802, - pop: 175000, - }, - { - lat: 33.2708, - lng: 35.1961, - pop: 160000, - }, - { - lat: 32.8667, - lng: 51.5667, - pop: 173329, - }, - { - lat: 41.6458, - lng: 41.6417, - pop: 169095, - }, - { - lat: 32.07, - lng: 34.8236, - pop: 159200, - }, - { - lat: 23.8494, - lng: 57.4386, - pop: 170000, - }, - { - lat: 31.8667, - lng: 36, - pop: 169434, - }, - { - lat: 34.5944, - lng: -118.1057, - pop: 167987, - }, - { - lat: 41.2833, - lng: 28, - pop: 166789, - }, - { - lat: 40.7833, - lng: 29.7333, - pop: 165503, - }, - { - lat: 35.9333, - lng: 36.6333, - pop: 165000, - }, - { - lat: 29.1322, - lng: 48.1261, - pop: 164212, - }, - { - lat: 40.6667, - lng: 29.8333, - pop: 162584, - }, - { - lat: 30.5589, - lng: 49.1981, - pop: 162797, - }, - { - lat: 30.4067, - lng: 55.9939, - pop: 161909, - }, - { - lat: 25.3722, - lng: 51.2047, - pop: 161240, - }, - { - lat: 30.96, - lng: 61.86, - pop: 160902, - }, - { - lat: 37.0289, - lng: 35.8125, - pop: 160474, - }, - { - lat: 32.2222, - lng: 35.2611, - pop: 156906, - }, - { - lat: 39.7464, - lng: 39.4914, - pop: 157452, - }, - { - lat: 32.5589, - lng: 36.0147, - pop: 155693, - }, - { - lat: 34.447, - lng: 35.8178, - pop: 150000, - }, - { - lat: 29.2694, - lng: 51.22, - pop: 155567, - }, - { - lat: 41.6344, - lng: 32.3375, - pop: 155016, - }, - { - lat: 46.8489, - lng: 35.3675, - pop: 154992, - }, - { - lat: 40.35, - lng: 27.9667, - pop: 154359, - }, - { - lat: 26.65, - lng: 50.1667, - pop: 153933, - }, - { - lat: 24.362, - lng: 56.7344, - pop: 151349, - }, - { - lat: 45.3619, - lng: 36.4711, - pop: 149566, - }, - { - lat: 42.25, - lng: 42.7, - pop: 147900, - }, - { - lat: 39.7186, - lng: 43.0508, - pop: 149188, - }, - { - lat: 32.3325, - lng: 35.7517, - pop: 148870, - }, - { - lat: 40.65, - lng: 35.8331, - pop: 149084, - }, - { - lat: 41.3764, - lng: 33.7764, - pop: 148931, - }, - { - lat: 29.8742, - lng: 52.8025, - pop: 148858, - }, - { - lat: 26.0031, - lng: 63.0544, - pop: 147791, - }, - { - lat: 44.05, - lng: 43.0667, - pop: 145836, - }, - { - lat: 32.6189, - lng: 36.1021, - pop: 146481, - }, - { - lat: 40.6556, - lng: 29.275, - pop: 144407, - }, - { - lat: 37.5167, - lng: 34.05, - pop: 145389, - }, - { - lat: 43.25, - lng: 46.5833, - pop: 141259, - }, - { - lat: 33.4472, - lng: 36.3361, - pop: 136427, - }, - { - lat: 37.332, - lng: 42.187, - pop: 143124, - }, - { - lat: 37.01, - lng: 37.7972, - pop: 142389, - }, - { - lat: 44.6, - lng: 40.0833, - pop: 141970, - }, - { - lat: 41.0247, - lng: 40.5222, - pop: 141143, - }, - { - lat: 28.5, - lng: 53.5606, - pop: 141634, - }, - { - lat: 41.1992, - lng: 36.7275, - pop: 138840, - }, - { - lat: 32.0167, - lng: 34.75, - pop: 128800, - }, - { - lat: 41.0736, - lng: 28.2478, - pop: 137861, - }, - { - lat: 39.9208, - lng: 44.0444, - pop: 137613, - }, - { - lat: 46.8403, - lng: 29.6433, - pop: 133807, - }, - { - lat: 40.9153, - lng: 38.3894, - pop: 135920, - }, - { - lat: 40.0528, - lng: 47.4614, - pop: 136000, - }, - { - lat: 32.46, - lng: 48.3592, - pop: 135116, - }, - { - lat: 32.0089, - lng: 51.8667, - pop: 134952, - }, - { - lat: 31.0286, - lng: 61.5011, - pop: 134950, - }, - { - lat: 33.8, - lng: 35.6, - pop: 130000, - }, - { - lat: 31.8981, - lng: 34.8081, - pop: 132671, - }, - { - lat: 33.4833, - lng: 36.35, - pop: 114363, - }, - { - lat: 30.4397, - lng: 48.1664, - pop: 133097, - }, - { - lat: 41.1986, - lng: 32.6264, - pop: 131989, - }, - { - lat: 41.5472, - lng: 45.0111, - pop: 128680, - }, - { - lat: 43.9167, - lng: 42.7167, - pop: 128779, - }, - { - lat: 37.45, - lng: 35.8, - pop: 130495, - }, - { - lat: 28.6781, - lng: 57.7406, - pop: 130429, - }, - { - lat: 40.8265, - lng: 29.3745, - pop: 129655, - }, - { - lat: 47.1667, - lng: 39.7333, - pop: 126769, - }, - { - lat: 33.464, - lng: 36.3044, - pop: 101827, - }, - { - lat: 37.0758, - lng: -113.5752, - pop: 127890, - }, - { - lat: 42.8803, - lng: 47.6383, - pop: 123988, - }, - { - lat: 29.1061, - lng: 58.3569, - pop: 127396, - }, - { - lat: 41.1333, - lng: 37.2833, - pop: 126702, - }, - { - lat: 33.5711, - lng: 36.4011, - pop: 123494, - }, - { - lat: 40.7894, - lng: 43.8475, - pop: 121976, - }, - { - lat: 36.9167, - lng: 31.1, - pop: 124335, - }, - { - lat: 37.3697, - lng: 36.1, - pop: 124053, - }, - { - lat: 44.2222, - lng: 42.0575, - pop: 122395, - }, - { - lat: 42.05, - lng: 48.3, - pop: 123720, - }, - { - lat: 30.5958, - lng: 50.2417, - pop: 122604, - }, - { - lat: 13.4692, - lng: 144.7332, - pop: 122411, - }, - { - lat: 36.085, - lng: 35.9806, - pop: 121109, - }, - { - lat: 41.4564, - lng: 31.7986, - pop: 120395, - }, - { - lat: 23.3908, - lng: 57.4244, - pop: 120000, - }, - { - lat: 43.2167, - lng: 44.7667, - pop: 117936, - }, - { - lat: 36.3761, - lng: 33.9322, - pop: 119303, - }, - { - lat: 44.6333, - lng: 41.9333, - pop: 117446, - }, - { - lat: 36.8278, - lng: -119.683, - pop: 118488, - }, - { - lat: 40.7333, - lng: 29.9667, - pop: 118066, - }, - { - lat: 41.3333, - lng: 27.9667, - pop: 116882, - }, - { - lat: 33.9697, - lng: 35.6156, - pop: 102221, - }, - { - lat: 41.0333, - lng: 37.5, - pop: 116154, - }, - { - lat: 25.7667, - lng: 55.95, - pop: 115949, - }, - { - lat: 36.7167, - lng: 37.1167, - pop: 116034, - }, - { - lat: 26.475, - lng: 50.0417, - pop: 115000, - }, - { - lat: 40.6078, - lng: 43.0958, - pop: 115891, - }, - { - lat: 46.7556, - lng: 36.7889, - pop: 114205, - }, - { - lat: 44.0333, - lng: 42.85, - pop: 113056, - }, - { - lat: 22.5667, - lng: 58.1167, - pop: 115040, - }, - { - lat: 31.5231, - lng: 49.8861, - pop: 114343, - }, - { - lat: 27.2025, - lng: 60.6847, - pop: 113750, - }, - { - lat: 37.4167, - lng: 41.3697, - pop: 113367, - }, - { - lat: 36.7108, - lng: 38.9478, - pop: 113194, - }, - { - lat: 33.8333, - lng: 35.9167, - pop: 100000, - }, - { - lat: 24.1456, - lng: 49.0653, - pop: 111214, - }, - { - lat: 28.9383, - lng: 53.6483, - pop: 110825, - }, - { - lat: 35.0118, - lng: 37.0525, - pop: 110683, - }, - { - lat: 45.1939, - lng: 33.3681, - pop: 108248, - }, - { - lat: 30.6683, - lng: 51.5881, - pop: 108505, - }, - { - lat: 30.0342, - lng: 47.9294, - pop: 107620, - }, - { - lat: 32.1714, - lng: 34.9083, - pop: 100800, - }, - { - lat: 39.5736, - lng: -119.7161, - pop: 106900, - }, - { - lat: 36.0778, - lng: 37.3733, - pop: 106460, - }, - { - lat: 25.2919, - lng: 60.6431, - pop: 106739, - }, - { - lat: 40.77, - lng: 47.0489, - pop: 106100, - }, - { - lat: 37.075, - lng: 41.2153, - pop: 105856, - }, - { - lat: 39.8208, - lng: 34.8083, - pop: 105167, - }, - { - lat: 29.9758, - lng: 48.4722, - pop: 105080, - }, - { - lat: 36.9764, - lng: 38.4269, - pop: 104302, - }, - { - lat: 46.3167, - lng: 44.2667, - pop: 103535, - }, - { - lat: 32.0456, - lng: 48.8567, - pop: 101878, - }, - { - lat: 23.2325, - lng: 56.4973, - pop: 101640, - }, - { - lat: 31.9364, - lng: 49.3039, - pop: 100497, - }, - { - lat: 32.1653, - lng: 34.8458, - pop: 93989, - }, - { - lat: 40.5986, - lng: 33.6192, - pop: 96025, - }, - { - lat: 40.8128, - lng: 44.4883, - pop: 90525, - }, - { - lat: 25.1223, - lng: 56.3342, - pop: 93673, - }, - { - lat: 46.8333, - lng: 29.4833, - pop: 91882, - }, - { - lat: 43.5667, - lng: 27.8333, - pop: 90419, - }, - { - lat: 34.8833, - lng: 35.8833, - pop: 89457, - }, - { - lat: 32.0333, - lng: 35.7333, - pop: 88900, - }, - { - lat: 33.3833, - lng: 35.45, - pop: 80000, - }, - { - lat: 25.18, - lng: 51.61, - pop: 87970, - }, - { - lat: 33.45, - lng: 36.25, - pop: 84044, - }, - { - lat: 37.3131, - lng: 40.735, - pop: 86948, - }, - { - lat: 34.9167, - lng: 33.6333, - pop: 84900, - }, - { - lat: 35.8367, - lng: 38.5481, - pop: 84000, - }, - { - lat: 32.7019, - lng: 35.3033, - pop: 83400, - }, - { - lat: 40.1431, - lng: 29.9792, - pop: 81723, - }, - { - lat: 32.1833, - lng: 34.8667, - pop: 74000, - }, - { - lat: 26.25, - lng: 50.6167, - pop: 75000, - }, - { - lat: 31.9275, - lng: 34.8625, - pop: 75500, - }, - { - lat: 45.19, - lng: 28.8, - pop: 73707, - }, - { - lat: 39.9323, - lng: 48.9203, - pop: 70684, - }, - { - lat: 22.9333, - lng: 57.5333, - pop: 72076, - }, - { - lat: 32.0714, - lng: 34.81, - pop: 59518, - }, - { - lat: 33.2631, - lng: 35.2389, - pop: 61973, - }, - { - lat: 44.15, - lng: 43.4667, - pop: 67054, - }, - { - lat: 24.2592, - lng: 55.7839, - pop: 67963, - }, - { - lat: 41.2, - lng: 47.1667, - pop: 68360, - }, - { - lat: 42.8167, - lng: 47.1167, - pop: 65080, - }, - { - lat: 40.2597, - lng: 40.2278, - pop: 66633, - }, - { - lat: 34.7667, - lng: 32.4167, - pop: 63600, - }, - { - lat: 32.7125, - lng: 36.5667, - pop: 64730, - }, - { - lat: 43, - lng: 41.0167, - pop: 64441, - }, - { - lat: 46.6383, - lng: 27.7292, - pop: 63035, - }, - { - lat: 46.3017, - lng: 30.6569, - pop: 59800, - }, - { - lat: 26.219, - lng: 50.538, - pop: 44769, - }, - { - lat: 32.15, - lng: 34.8833, - pop: 56659, - }, - { - lat: 40.6172, - lng: 47.15, - pop: 59036, - }, - { - lat: 32.8, - lng: 35.1, - pop: 55464, - }, - { - lat: 32.0956, - lng: 34.9567, - pop: 56300, - }, - { - lat: 46.2167, - lng: 27.6667, - pop: 55837, - }, - { - lat: 33.4728, - lng: 36.3344, - pop: 50880, - }, - { - lat: 39.1511, - lng: -119.7476, - pop: 57957, - }, - { - lat: 32.5194, - lng: 35.1536, - pop: 55300, - }, - { - lat: 40.4597, - lng: 39.4778, - pop: 57269, - }, - { - lat: 26.1128, - lng: 50.5139, - pop: 52700, - }, - { - lat: 32.1903, - lng: 34.9686, - pop: 51683, - }, - { - lat: 31.9333, - lng: 34.8, - pop: 50200, - }, - { - lat: 33.5186, - lng: 35.3661, - pop: 50000, - }, - { - lat: 40.5, - lng: 44.7667, - pop: 52808, - }, - { - lat: 32.0636, - lng: 34.8553, - pop: 41900, - }, - { - lat: 33.5667, - lng: 36.3667, - pop: 45974, - }, - { - lat: 32.2723, - lng: 35.8914, - pop: 50745, - }, - { - lat: 41.9817, - lng: 44.1124, - pop: 48143, - }, - { - lat: 40.2739, - lng: 44.6256, - pop: 44400, - }, - { - lat: 32.0522, - lng: 34.9511, - pop: 46896, - }, - { - lat: 34.1236, - lng: 35.6511, - pop: 40000, - }, - { - lat: 32.15, - lng: 34.8333, - pop: 46700, - }, - { - lat: 26.1736, - lng: 50.5478, - pop: 40000, - }, - { - lat: 32.9136, - lng: 35.2961, - pop: 45300, - }, - { - lat: 31.8558, - lng: 34.73, - pop: 42314, - }, - { - lat: 36.1008, - lng: -115.0379, - pop: 45105, - }, - { - lat: 42.5083, - lng: 41.8667, - pop: 42998, - }, - { - lat: 32.8333, - lng: 35.0833, - pop: 39900, - }, - { - lat: 25.5533, - lng: 55.5475, - pop: 44411, - }, - { - lat: 32.0333, - lng: 34.85, - pop: 36706, - }, - { - lat: 32.8056, - lng: 35.1694, - pop: 41600, - }, - { - lat: 40.15, - lng: 44.04, - pop: 38635, - }, - { - lat: 41.1111, - lng: 42.7022, - pop: 42226, - }, - { - lat: 34.4, - lng: 35.9, - pop: 30000, - }, - { - lat: 35.125, - lng: 33.9417, - pop: 40920, - }, - { - lat: 40.8297, - lng: 46.0189, - pop: 40600, - }, - { - lat: 41.4708, - lng: 48.8097, - pop: 39900, - }, - { - lat: 40.3744, - lng: 47.1267, - pop: 38500, - }, - { - lat: 33.1256, - lng: 35.8239, - pop: 37022, - }, - { - lat: 40.6531, - lng: 47.7406, - pop: 36200, - }, - { - lat: 36.1365, - lng: -115.137, - pop: 36307, - }, - { - lat: 32.0333, - lng: 34.8833, - pop: 29146, - }, - { - lat: 41.0933, - lng: 45.3661, - pop: 35102, - }, - { - lat: 41.1833, - lng: 41.8181, - pop: 35081, - }, - { - lat: 32.0781, - lng: 34.8475, - pop: 25298, - }, - { - lat: 22.6833, - lng: 58.55, - pop: 35000, - }, - { - lat: 33.92, - lng: 35.67, - pop: 28000, - }, - { - lat: 25.2525, - lng: 51.5592, - pop: 29703, - }, - { - lat: 35.3403, - lng: 33.3192, - pop: 33207, - }, - { - lat: 33.1258, - lng: 35.4428, - pop: 30000, - }, - { - lat: 42.1167, - lng: 48.1833, - pop: 29716, - }, - { - lat: 41.6336, - lng: 46.6433, - pop: 31300, - }, - { - lat: 39.8697, - lng: 48.06, - pop: 31310, - }, - { - lat: 40.0128, - lng: 48.4789, - pop: 30918, - }, - { - lat: 47.2167, - lng: 27.8167, - pop: 30804, - }, - { - lat: 32.3171, - lng: 34.9358, - pop: 28025, - }, - { - lat: 33.5, - lng: 36.3667, - pop: 22535, - }, - { - lat: 45.9167, - lng: 28.1836, - pop: 30018, - }, - { - lat: 40.4097, - lng: 44.6431, - pop: 25039, - }, - { - lat: 40.65, - lng: 47.4761, - pop: 29600, - }, - { - lat: 35.3683, - lng: -118.9225, - pop: 29110, - }, - { - lat: 32.2322, - lng: 34.9483, - pop: 26200, - }, - { - lat: 26.12, - lng: 50.65, - pop: 20000, - }, - { - lat: 40.5905, - lng: 46.3239, - pop: 25758, - }, - { - lat: 34.0063, - lng: 36.2073, - pop: 24000, - }, - { - lat: 32.1151, - lng: 34.9751, - pop: 21848, - }, - { - lat: 33.8333, - lng: 35.5333, - pop: 9000, - }, - { - lat: 32.2822, - lng: 34.9833, - pop: 21451, - }, - { - lat: 39.9539, - lng: 44.5506, - pop: 21300, - }, - { - lat: 40.2975, - lng: 44.3617, - pop: 21600, - }, - { - lat: 40.8792, - lng: 45.1472, - pop: 20509, - }, - { - lat: 40.0433, - lng: 48.9356, - pop: 21504, - }, - { - lat: 47.3833, - lng: 28.8167, - pop: 21065, - }, - { - lat: 26.1497, - lng: 50.4653, - pop: 18000, - }, - { - lat: 26.2186, - lng: 50.4756, - pop: 18000, - }, - { - lat: 40.3589, - lng: 45.1267, - pop: 20765, - }, - { - lat: 41.3597, - lng: 48.5125, - pop: 20791, - }, - { - lat: 40.79, - lng: 48.1519, - pop: 20660, - }, - { - lat: 40.5692, - lng: 48.4008, - pop: 20200, - }, - { - lat: 41.1189, - lng: 45.4539, - pop: 20200, - }, - { - lat: 46.3167, - lng: 28.6667, - pop: 20113, - }, - { - lat: 34.2264, - lng: 35.66, - pop: 9613, - }, - { - lat: 33.4292, - lng: 36.3611, - pop: 12330, - }, - { - lat: 33.8219, - lng: 35.5875, - pop: 13000, - }, - { - lat: 41.9167, - lng: 45.4833, - pop: 19629, - }, - { - lat: 41.0761, - lng: 49.1139, - pop: 18655, - }, - { - lat: 34.7024, - lng: 33.0453, - pop: 14477, - }, - { - lat: 47.1333, - lng: 28.6167, - pop: 18376, - }, - { - lat: 39.9311, - lng: 48.3697, - pop: 17900, - }, - { - lat: 26.1008, - lng: 50.4878, - pop: 14800, - }, - { - lat: 26.1833, - lng: 56.25, - pop: 17777, - }, - { - lat: 41.4264, - lng: 48.4356, - pop: 16500, - }, - { - lat: 46.6333, - lng: 29.4, - pop: 15939, - }, - { - lat: 40.3383, - lng: 48.1608, - pop: 15385, - }, - { - lat: 26.2306, - lng: 50.5108, - pop: 12000, - }, - { - lat: 41.9406, - lng: 41.9906, - pop: 14785, - }, - { - lat: 36.6087, - lng: -119.5434, - pop: 14666, - }, - { - lat: 40.5183, - lng: 47.6542, - pop: 14273, - }, - { - lat: 40.3019, - lng: 44.5831, - pop: 10198, - }, - { - lat: 33.4517, - lng: 35.2908, - pop: 10965, - }, - { - lat: 41.6389, - lng: 42.9861, - pop: 14000, - }, - { - lat: 40.9922, - lng: 45.6289, - pop: 13700, - }, - { - lat: 42.0267, - lng: 35.1511, - pop: 13354, - }, - { - lat: 46.95, - lng: 28.7833, - pop: 12515, - }, - { - lat: 33.2814, - lng: 35.3964, - pop: 10000, - }, - { - lat: 46.8167, - lng: 28.5833, - pop: 12491, - }, - { - lat: 40.345, - lng: 46.9289, - pop: 12563, - }, - { - lat: 45.9, - lng: 28.6689, - pop: 12355, - }, - { - lat: 39.7756, - lng: 47.6186, - pop: 12263, - }, - { - lat: 46.5167, - lng: 28.7833, - pop: 11997, - }, - { - lat: 40.3147, - lng: 44.5936, - pop: 9513, - }, - { - lat: 41.4225, - lng: 46.9242, - pop: 11415, - }, - { - lat: 35.379, - lng: -118.9578, - pop: 11443, - }, - { - lat: 43.1667, - lng: 44.8, - pop: 10333, - }, - { - lat: 35.3832, - lng: -118.9743, - pop: 11025, - }, - { - lat: 46.8833, - lng: 29.2167, - pop: 10872, - }, - { - lat: 47.25, - lng: 28.3, - pop: 10808, - }, - { - lat: 40.5244, - lng: 46.1069, - pop: 10700, - }, - { - lat: 35.3636, - lng: -118.965, - pop: 10517, - }, - { - lat: 40.5067, - lng: 46.825, - pop: 10100, - }, - { - lat: 40.77, - lng: 46.4111, - pop: 10228, - }, - { - lat: 47.0833, - lng: 28.1833, - pop: 10063, - }, - { - lat: 36.6211, - lng: -119.3188, - pop: 9680, - }, - { - lat: 40.5656, - lng: 45.8161, - pop: 8657, - }, - { - lat: 35.3972, - lng: -118.9892, - pop: 8726, - }, - { - lat: 46.3336, - lng: 28.9614, - pop: 8471, - }, - { - lat: 40.2183, - lng: 47.7083, - pop: 8450, - }, - { - lat: 41.7258, - lng: 46.4083, - pop: 8134, - }, - { - lat: 41.8464, - lng: 44.7194, - pop: 7940, - }, - { - lat: 39.7611, - lng: 45.3333, - pop: 7633, - }, - { - lat: 46.4833, - lng: 28.25, - pop: 7443, - }, - { - lat: 40.6103, - lng: 46.7897, - pop: 7400, - }, - { - lat: 40.1067, - lng: 46.0383, - pop: 7246, - }, - { - lat: 46.5153, - lng: 29.6631, - pop: 7078, - }, - { - lat: 41.0708, - lng: 47.4583, - pop: 6876, - }, - { - lat: 47.2167, - lng: 29.1667, - pop: 6708, - }, - { - lat: 34.5506, - lng: 36.0781, - pop: 4730, - }, - { - lat: 39.7953, - lng: 47.1131, - pop: 5700, - }, - { - lat: 39.7583, - lng: 46.7483, - pop: 4446, - }, - { - lat: 40.5367, - lng: 48.9328, - pop: 3945, - }, - { - lat: 39.9833, - lng: 46.9167, - pop: 3770, - }, - { - lat: 46.2667, - lng: 28.2167, - pop: 3429, - }, - { - lat: 39.6408, - lng: 46.5469, - pop: 2190, - }, - { - lat: 39.7203, - lng: 44.8531, - pop: 2000, - }, - { - lat: 42.5194, - lng: 43.15, - pop: 2047, - }, - { - lat: 40.9078, - lng: 49.0733, - pop: 1600, - }, - { - lat: 39.9111, - lng: 46.7892, - pop: 1397, - }, - { - lat: 36.2692, - lng: 36.5672, - pop: 98534, - }, - { - lat: 34.598, - lng: -112.3185, - pop: 97901, - }, - { - lat: 41.2719, - lng: 36.3508, - pop: 97564, - }, - { - lat: 32.45, - lng: 34.9167, - pop: 95700, - }, - { - lat: 41.5722, - lng: 35.9147, - pop: 97452, - }, - { - lat: 30.3586, - lng: 50.7981, - pop: 96728, - }, - { - lat: 29.6194, - lng: 51.6542, - pop: 96683, - }, - { - lat: 36.3274, - lng: -119.6549, - pop: 95459, - }, - { - lat: 40.6667, - lng: 36.5667, - pop: 95361, - }, - { - lat: 37.025, - lng: 37.9769, - pop: 95149, - }, - { - lat: 45.1333, - lng: 42.0333, - pop: 93658, - }, - { - lat: 41.4267, - lng: 32.0758, - pop: 91569, - }, - { - lat: 31.9077, - lng: 35.0076, - pop: 90013, - }, - { - lat: 25.1264, - lng: 62.3225, - pop: 90762, - }, - { - lat: 36.4128, - lng: 35.8867, - pop: 90456, - }, - { - lat: 35.6386, - lng: 36.6717, - pop: 90000, - }, - { - lat: 40.6833, - lng: 30.6253, - pop: 89301, - }, - { - lat: 36.3225, - lng: 41.8642, - pop: 88023, - }, - { - lat: 36.8461, - lng: 40.0489, - pop: 87684, - }, - { - lat: 32.6539, - lng: 51.7553, - pop: 86063, - }, - { - lat: 46.7111, - lng: 38.2733, - pop: 85760, - }, - { - lat: 22.968, - lng: 57.298, - pop: 85000, - }, - { - lat: 47.1, - lng: 39.4167, - pop: 80721, - }, - { - lat: 44.8667, - lng: 37.3667, - pop: 81447, - }, - { - lat: 32.2444, - lng: 54.0186, - pop: 80712, - }, - { - lat: 23.3, - lng: 57.9833, - pop: 80538, - }, - { - lat: 40.39, - lng: 36.09, - pop: 79916, - }, - { - lat: 44.4994, - lng: 34.17, - pop: 79458, - }, - { - lat: 39.8144, - lng: 35.1903, - pop: 79314, - }, - { - lat: 32.4797, - lng: 51.7753, - pop: 79023, - }, - { - lat: 30.4356, - lng: 49.1056, - pop: 78353, - }, - { - lat: 36.5275, - lng: 37.9553, - pop: 78255, - }, - { - lat: 45.4333, - lng: 40.5667, - pop: 78149, - }, - { - lat: 44.575, - lng: 38.0725, - pop: 77212, - }, - { - lat: 32.1942, - lng: 48.2436, - pop: 77148, - }, - { - lat: 35.35, - lng: 35.9167, - pop: 75505, - }, - { - lat: 37.341, - lng: 41.894, - pop: 76523, - }, - { - lat: 31.9519, - lng: 34.8881, - pop: 75700, - }, - { - lat: 34.5352, - lng: -117.2109, - pop: 75311, - }, - { - lat: 32.3061, - lng: 54.0081, - pop: 75271, - }, - { - lat: 39.9078, - lng: 30.0367, - pop: 74441, - }, - { - lat: 31.28, - lng: 49.6036, - pop: 74285, - }, - { - lat: 44.2167, - lng: 43.1333, - pop: 74141, - }, - { - lat: 34.3688, - lng: 41.0945, - pop: 74100, - }, - { - lat: 37.3914, - lng: 36.8522, - pop: 73770, - }, - { - lat: 37.6764, - lng: 31.7261, - pop: 73768, - }, - { - lat: 29.5839, - lng: 50.5189, - pop: 73472, - }, - { - lat: 36.85, - lng: 31.05, - pop: 73260, - }, - { - lat: 41.1417, - lng: 28.4631, - pop: 72966, - }, - { - lat: 40.875, - lng: 35.4633, - pop: 71916, - }, - { - lat: 45.3517, - lng: 28.8364, - pop: 71411, - }, - { - lat: 23.5242, - lng: 58.4989, - pop: 70000, - }, - { - lat: 45.0489, - lng: 35.3792, - pop: 69145, - }, - { - lat: 29.0769, - lng: 48.0838, - pop: 68763, - }, - { - lat: 36.1995, - lng: -119.34, - pop: 68395, - }, - { - lat: 36.0643, - lng: -119.0338, - pop: 67887, - }, - { - lat: 37.025, - lng: 36.6345, - pop: 67674, - }, - { - lat: 30.7458, - lng: 49.7086, - pop: 67427, - }, - { - lat: 40.9333, - lng: 38.2333, - pop: 66736, - }, - { - lat: 37.575, - lng: 32.7747, - pop: 66794, - }, - { - lat: 45.25, - lng: 38.1167, - pop: 66285, - }, - { - lat: 36.0243, - lng: 32.8026, - pop: 65920, - }, - { - lat: 28.4167, - lng: 48.5, - pop: 65000, - }, - { - lat: 37.4592, - lng: 30.5953, - pop: 64943, - }, - { - lat: 37.4183, - lng: 31.8506, - pop: 64687, - }, - { - lat: 32.8889, - lng: 36.0431, - pop: 63676, - }, - { - lat: 33.6, - lng: 36.3, - pop: 63554, - }, - { - lat: 40.5833, - lng: 36.9667, - pop: 64119, - }, - { - lat: 30.8128, - lng: 56.5639, - pop: 63744, - }, - { - lat: 44.7833, - lng: 44.1667, - pop: 62495, - }, - { - lat: 23.32, - lng: 58.908, - pop: 63133, - }, - { - lat: 36.6333, - lng: 33.4333, - pop: 62853, - }, - { - lat: 32.3464, - lng: 51.5044, - pop: 62454, - }, - { - lat: 32.3117, - lng: 35.0272, - pop: 61941, - }, - { - lat: 44.1044, - lng: 39.0772, - pop: 62269, - }, - { - lat: 34.9167, - lng: 36.7333, - pop: 61176, - }, - { - lat: 37.3658, - lng: 40.2697, - pop: 61830, - }, - { - lat: 33.0058, - lng: 35.0989, - pop: 60000, - }, - { - lat: 35.0025, - lng: 40.5117, - pop: 60780, - }, - { - lat: 27.8389, - lng: 52.0619, - pop: 60187, - }, - { - lat: 32.6064, - lng: 35.2881, - pop: 60000, - }, - { - lat: 44.6333, - lng: 40.7333, - pop: 60164, - }, - { - lat: 43.1333, - lng: 45.55, - pop: 59954, - }, - { - lat: 27.3708, - lng: 62.3342, - pop: 60114, - }, - { - lat: 43.75, - lng: 44.0333, - pop: 57883, - }, - { - lat: 46.1833, - lng: 30.35, - pop: 57210, - }, - { - lat: 42.5633, - lng: 47.8636, - pop: 58690, - }, - { - lat: 40.1728, - lng: 44.2925, - pop: 57500, - }, - { - lat: 39.9319, - lng: 48.9203, - pop: 58253, - }, - { - lat: 45.8667, - lng: 40.1333, - pop: 57771, - }, - { - lat: 46.4833, - lng: 41.5333, - pop: 57622, - }, - { - lat: 44.9233, - lng: 37.9806, - pop: 57229, - }, - { - lat: 31.9678, - lng: 51.2894, - pop: 57071, - }, - { - lat: 30.795, - lng: 50.5644, - pop: 57036, - }, - { - lat: 29.1489, - lng: 48.1057, - pop: 56554, - }, - { - lat: 28.2211, - lng: 61.2158, - pop: 56584, - }, - { - lat: 34.5006, - lng: -114.3113, - pop: 56510, - }, - { - lat: 36.7994, - lng: 36.5178, - pop: 56409, - }, - { - lat: 31.2414, - lng: 48.6525, - pop: 56252, - }, - { - lat: 43.15, - lng: 45.9, - pop: 56226, - }, - { - lat: 32.3897, - lng: 51.3767, - pop: 55984, - }, - { - lat: 35.4389, - lng: 36.6511, - pop: 55843, - }, - { - lat: 40.3, - lng: 35.8833, - pop: 55673, - }, - { - lat: 41.2667, - lng: 27.9667, - pop: 54268, - }, - { - lat: 25.25, - lng: 51.3732, - pop: 54339, - }, - { - lat: 43.35, - lng: 46.1, - pop: 52908, - }, - { - lat: 27.95, - lng: 57.7, - pop: 52624, - }, - { - lat: 34.0167, - lng: 36.7167, - pop: 52502, - }, - { - lat: 41.2125, - lng: 36.4569, - pop: 52258, - }, - { - lat: 35.7662, - lng: -119.2635, - pop: 52206, - }, - { - lat: 44.7686, - lng: 39.8733, - pop: 52082, - }, - { - lat: 34.4536, - lng: 40.9367, - pop: 52020, - }, - { - lat: 45.6167, - lng: 38.9333, - pop: 51925, - }, - { - lat: 30.1164, - lng: 55.1186, - pop: 51620, - }, - { - lat: 33.7986, - lng: 35.825, - pop: 50000, - }, - { - lat: 31.5608, - lng: 48.1831, - pop: 51431, - }, - { - lat: 34.56, - lng: 38.2672, - pop: 51323, - }, - { - lat: 43.85, - lng: 46.7167, - pop: 49247, - }, - { - lat: 34.1794, - lng: 36.4208, - pop: 50000, - }, - { - lat: 39.8153, - lng: 46.7519, - pop: 49848, - }, - { - lat: 32.9278, - lng: 35.0817, - pop: 48900, - }, - { - lat: 37.7147, - lng: 33.5508, - pop: 49766, - }, - { - lat: 40.9333, - lng: 40.05, - pop: 49496, - }, - { - lat: 41.4411, - lng: 27.9216, - pop: 49106, - }, - { - lat: 40.161, - lng: 34.377, - pop: 49082, - }, - { - lat: 35.0183, - lng: 40.4533, - pop: 48922, - }, - { - lat: 41.4333, - lng: 31.75, - pop: 48381, - }, - { - lat: 30.6497, - lng: 48.6647, - pop: 48642, - }, - { - lat: 44.8667, - lng: 40.6167, - pop: 48439, - }, - { - lat: 34.9833, - lng: 36.0833, - pop: 47982, - }, - { - lat: 40.1703, - lng: 31.9211, - pop: 48274, - }, - { - lat: 36.3, - lng: 30.15, - pop: 48131, - }, - { - lat: 23.3103, - lng: 57.9455, - pop: 47718, - }, - { - lat: 37.1042, - lng: 40.93, - pop: 47580, - }, - { - lat: 33.9667, - lng: 36.6667, - pop: 47136, - }, - { - lat: 34.5119, - lng: 36.5764, - pop: 46772, - }, - { - lat: 40.8333, - lng: 35.65, - pop: 46608, - }, - { - lat: 34.8333, - lng: 36.7333, - pop: 45853, - }, - { - lat: 34.25, - lng: 35.65, - pop: 45000, - }, - { - lat: 31.6033, - lng: 55.4003, - pop: 45453, - }, - { - lat: 47.25, - lng: 39.8667, - pop: 45078, - }, - { - lat: 46.755, - lng: 33.375, - pop: 45069, - }, - { - lat: 34.5849, - lng: -112.4473, - pop: 45063, - }, - { - lat: 32.6153, - lng: 51.5556, - pop: 43183, - }, - { - lat: 36.891, - lng: 38.3536, - pop: 44821, - }, - { - lat: 35.8126, - lng: 36.3176, - pop: 44322, - }, - { - lat: 41.1833, - lng: 31.3833, - pop: 44286, - }, - { - lat: 32.7944, - lng: 35.5333, - pop: 44200, - }, - { - lat: 41.088, - lng: 40.7232, - pop: 44304, - }, - { - lat: 30.8989, - lng: 52.6867, - pop: 44341, - }, - { - lat: 41.4653, - lng: 34.7708, - pop: 44004, - }, - { - lat: 25.935, - lng: 49.6661, - pop: 44000, - }, - { - lat: 32.4711, - lng: 34.9675, - pop: 42100, - }, - { - lat: 32.8333, - lng: 35.0833, - pop: 42000, - }, - { - lat: 35.1822, - lng: 35.9403, - pop: 43151, - }, - { - lat: 33.35, - lng: 36.2333, - pop: 43456, - }, - { - lat: 25.3603, - lng: 60.3994, - pop: 43732, - }, - { - lat: 32.2667, - lng: 35.0103, - pop: 43100, - }, - { - lat: 40.945, - lng: 40.2644, - pop: 43499, - }, - { - lat: 24.75, - lng: 56.4667, - pop: 43312, - }, - { - lat: 36.6, - lng: 30.55, - pop: 43226, - }, - { - lat: 39.6658, - lng: 35.8836, - pop: 42919, - }, - { - lat: 40.3381, - lng: 42.5731, - pop: 42683, - }, - { - lat: 36.2235, - lng: -115.9974, - pop: 42471, - }, - { - lat: 43.7333, - lng: 44.7, - pop: 42155, - }, - { - lat: 42.15, - lng: 41.6667, - pop: 41465, - }, - { - lat: 45.4686, - lng: 39.4519, - pop: 42019, - }, - { - lat: 36.3481, - lng: 37.5308, - pop: 41786, - }, - { - lat: 36.75, - lng: 36.2167, - pop: 41409, - }, - { - lat: 43.3167, - lng: 44.9167, - pop: 41469, - }, - { - lat: 36.9533, - lng: 36.2033, - pop: 41368, - }, - { - lat: 32.8333, - lng: 35.0667, - pop: 39416, - }, - { - lat: 45.2667, - lng: 37.3667, - pop: 41133, - }, - { - lat: 37.2489, - lng: 37.8658, - pop: 41142, - }, - { - lat: 41.19, - lng: 40.9831, - pop: 41084, - }, - { - lat: 35.1205, - lng: -114.5461, - pop: 41064, - }, - { - lat: 32.7997, - lng: 51.6956, - pop: 40945, - }, - { - lat: 33.775, - lng: 35.9, - pop: 40000, - }, - { - lat: 26.9581, - lng: 56.2719, - pop: 40678, - }, - { - lat: 28.6489, - lng: 51.3794, - pop: 40722, - }, - { - lat: 25.3333, - lng: 56.35, - pop: 39515, - }, - { - lat: 31.5081, - lng: 50.8319, - pop: 40528, - }, - { - lat: 39.9458, - lng: 41.1053, - pop: 40350, - }, - { - lat: 41.0172, - lng: 34.0383, - pop: 40245, - }, - { - lat: 35.8636, - lng: 36.8006, - pop: 39901, - }, - { - lat: 40.9667, - lng: 35.6667, - pop: 40194, - }, - { - lat: 44.2503, - lng: 28.2614, - pop: 39780, - }, - { - lat: 43.6825, - lng: 43.5339, - pop: 38192, - }, - { - lat: 32.4611, - lng: 35.3, - pop: 39004, - }, - { - lat: 33.8067, - lng: 36.7403, - pop: 39903, - }, - { - lat: 26.5578, - lng: 54.0194, - pop: 39853, - }, - { - lat: 44.4667, - lng: 39.7333, - pop: 39762, - }, - { - lat: 40.9667, - lng: 39.9, - pop: 39624, - }, - { - lat: 31.9, - lng: 35.2, - pop: 38998, - }, - { - lat: 40.6603, - lng: 29.3236, - pop: 39110, - }, - { - lat: 44.8667, - lng: 38.15, - pop: 39374, - }, - { - lat: 37.1667, - lng: 42.1333, - pop: 39000, - }, - { - lat: 39.9139, - lng: 28.1603, - pop: 39058, - }, - { - lat: 41.0833, - lng: 31.1167, - pop: 38846, - }, - { - lat: 41.5097, - lng: 34.2142, - pop: 38849, - }, - { - lat: 45.7086, - lng: 34.3933, - pop: 38714, - }, - { - lat: 32.5553, - lng: 51.5097, - pop: 37740, - }, - { - lat: 45.3667, - lng: 41.7167, - pop: 38100, - }, - { - lat: 34.6894, - lng: 40.8308, - pop: 37935, - }, - { - lat: 43.2944, - lng: 45.8839, - pop: 37373, - }, - { - lat: 36, - lng: 36.6667, - pop: 37490, - }, - { - lat: 34.2283, - lng: 37.2406, - pop: 37820, - }, - { - lat: 25.0742, - lng: 56.3553, - pop: 37545, - }, - { - lat: 44.6333, - lng: 39.1333, - pop: 37475, - }, - { - lat: 43.5, - lng: 44.75, - pop: 37442, - }, - { - lat: 37.6942, - lng: 37.8614, - pop: 37323, - }, - { - lat: 35.0653, - lng: 36.3422, - pop: 37109, - }, - { - lat: 43.2167, - lng: 46.8667, - pop: 37171, - }, - { - lat: 34.5814, - lng: -117.4397, - pop: 37229, - }, - { - lat: 43.1833, - lng: 44.55, - pop: 37029, - }, - { - lat: 43.8172, - lng: 28.5828, - pop: 36364, - }, - { - lat: 36.5083, - lng: 36.8692, - pop: 36562, - }, - { - lat: 41.1764, - lng: 29.6128, - pop: 36516, - }, - { - lat: 33.0167, - lng: 35.2708, - pop: 36000, - }, - { - lat: 29.1267, - lng: 54.0422, - pop: 36410, - }, - { - lat: 32.6368, - lng: 35.99, - pop: 34948, - }, - { - lat: 46.8, - lng: 33.4667, - pop: 35900, - }, - { - lat: 36.1569, - lng: 37.7078, - pop: 35409, - }, - { - lat: 34.6683, - lng: 36.2597, - pop: 35445, - }, - { - lat: 32.9658, - lng: 35.4983, - pop: 35700, - }, - { - lat: 32.4583, - lng: 35.8583, - pop: 35085, - }, - { - lat: 45.35, - lng: 42.85, - pop: 35745, - }, - { - lat: 41.4833, - lng: 31.8333, - pop: 35323, - }, - { - lat: 39.6381, - lng: 34.4672, - pop: 35561, - }, - { - lat: 35.9025, - lng: 36.0606, - pop: 35460, - }, - { - lat: 33.5333, - lng: 36.2167, - pop: 33571, - }, - { - lat: 35.2609, - lng: 36.3822, - pop: 35000, - }, - { - lat: 22.9339, - lng: 57.775, - pop: 35173, - }, - { - lat: 32.8536, - lng: 35.1978, - pop: 34000, - }, - { - lat: 40.95, - lng: 39.9333, - pop: 34831, - }, - { - lat: 44.3211, - lng: 28.6133, - pop: 34398, - }, - { - lat: 40.95, - lng: 38.7333, - pop: 34592, - }, - { - lat: 44.4167, - lng: 43.9167, - pop: 34690, - }, - { - lat: 29.2331, - lng: 56.6022, - pop: 34517, - }, - { - lat: 27.6672, - lng: 54.1411, - pop: 34469, - }, - { - lat: 35.4293, - lng: -119.0306, - pop: 34350, - }, - { - lat: 45.3594, - lng: 40.7072, - pop: 34215, - }, - { - lat: 40.6422, - lng: 29.1203, - pop: 34076, - }, - { - lat: 37.6834, - lng: -113.0956, - pop: 34246, - }, - { - lat: 31.5775, - lng: 54.4369, - pop: 34237, - }, - { - lat: 36.4889, - lng: 36.1944, - pop: 33540, - }, - { - lat: 41.0656, - lng: 37.7714, - pop: 33253, - }, - { - lat: 37.5764, - lng: 36.3506, - pop: 33193, - }, - { - lat: 32.7356, - lng: 36.0669, - pop: 32236, - }, - { - lat: 37.4247, - lng: 37.6928, - pop: 32846, - }, - { - lat: 34.8167, - lng: 36.1167, - pop: 32213, - }, - { - lat: 36.965, - lng: 37.5092, - pop: 32653, - }, - { - lat: 29.7742, - lng: 52.7236, - pop: 32261, - }, - { - lat: 26.2667, - lng: 50.15, - pop: 32067, - }, - { - lat: 46.05, - lng: 38.1667, - pop: 32180, - }, - { - lat: 35.217, - lng: -114.0105, - pop: 32204, - }, - { - lat: 32.9667, - lng: 36.0667, - pop: 31683, - }, - { - lat: 46.63, - lng: 31.1, - pop: 32100, - }, - { - lat: 41.0056, - lng: 38.8167, - pop: 32008, - }, - { - lat: 32.8667, - lng: 35.3, - pop: 31100, - }, - { - lat: 36.5866, - lng: 37.0463, - pop: 31534, - }, - { - lat: 32.2714, - lng: 50.9775, - pop: 31739, - }, - { - lat: 29.205, - lng: 52.69, - pop: 31711, - }, - { - lat: 39.8642, - lng: 36.5983, - pop: 31748, - }, - { - lat: 33.9667, - lng: 36.0167, - pop: 30000, - }, - { - lat: 33, - lng: 36.1167, - pop: 31258, - }, - { - lat: 23.5889, - lng: 58.4083, - pop: 31409, - }, - { - lat: 27.8236, - lng: 52.3303, - pop: 31436, - }, - { - lat: 39.8503, - lng: 33.4536, - pop: 31308, - }, - { - lat: 36.4731, - lng: 41.6161, - pop: 31161, - }, - { - lat: 47.2514, - lng: 35.7058, - pop: 31016, - }, - { - lat: 43.55, - lng: 43.85, - pop: 30832, - }, - { - lat: 32.5767, - lng: 51.455, - pop: 30002, - }, - { - lat: 32.2553, - lng: 50.5711, - pop: 30504, - }, - { - lat: 45.1, - lng: 43.45, - pop: 30530, - }, - { - lat: 33.7436, - lng: 36.7012, - pop: 30450, - }, - { - lat: 33.8667, - lng: 35.5667, - pop: 30000, - }, - { - lat: 44.415, - lng: 27.8236, - pop: 30217, - }, - { - lat: 42.2257, - lng: 43.9701, - pop: 30432, - }, - { - lat: 26.5581, - lng: 54.8806, - pop: 30435, - }, - { - lat: 44.0872, - lng: 41.9733, - pop: 30369, - }, - { - lat: 35.6139, - lng: 36.5611, - pop: 30000, - }, - { - lat: 32.5025, - lng: 35.6922, - pop: 29590, - }, - { - lat: 40.75, - lng: 36.3167, - pop: 30123, - }, - { - lat: 27.76, - lng: 54.0072, - pop: 29987, - }, - { - lat: 44.6672, - lng: 34.3978, - pop: 29668, - }, - { - lat: 27.3423, - lng: 53.1768, - pop: 29380, - }, - { - lat: 36.8503, - lng: 40.0706, - pop: 29347, - }, - { - lat: 35.2897, - lng: 36.7433, - pop: 29100, - }, - { - lat: 23.6522, - lng: 53.6536, - pop: 29095, - }, - { - lat: 30.2364, - lng: 49.7119, - pop: 29015, - }, - { - lat: 36.1242, - lng: -115.3324, - pop: 28861, - }, - { - lat: 37.486, - lng: 37.297, - pop: 28582, - }, - { - lat: 47.2667, - lng: 29.1667, - pop: 28500, - }, - { - lat: 40.3748, - lng: 36.9031, - pop: 28413, - }, - { - lat: 32.6417, - lng: 35.9417, - pop: 27902, - }, - { - lat: 33.0381, - lng: 40.2844, - pop: 28400, - }, - { - lat: 40.6975, - lng: 29.5114, - pop: 28232, - }, - { - lat: 41.0475, - lng: 39.2798, - pop: 28209, - }, - { - lat: 23.3833, - lng: 57.8167, - pop: 28088, - }, - { - lat: 40.1836, - lng: 31.3506, - pop: 28091, - }, - { - lat: 41.1158, - lng: 45.4853, - pop: 28000, - }, - { - lat: 35.6308, - lng: -117.6622, - pop: 27989, - }, - { - lat: 37.1303, - lng: -113.4878, - pop: 27689, - }, - { - lat: 41.8111, - lng: 41.7753, - pop: 27546, - }, - { - lat: 31.8889, - lng: 35.1675, - pop: 26604, - }, - { - lat: 31.1267, - lng: 53.2592, - pop: 27524, - }, - { - lat: 40.9142, - lng: 40.1125, - pop: 27428, - }, - { - lat: 35.5938, - lng: -119.3671, - pop: 27505, - }, - { - lat: 44.7167, - lng: 43, - pop: 27471, - }, - { - lat: 36.4733, - lng: 37.0972, - pop: 27086, - }, - { - lat: 44.7528, - lng: 33.8608, - pop: 27351, - }, - { - lat: 37.5375, - lng: 40.8892, - pop: 27304, - }, - { - lat: 43.65, - lng: 44.0667, - pop: 27074, - }, - { - lat: 29.8519, - lng: 51.5869, - pop: 26918, - }, - { - lat: 30.6131, - lng: 53.1953, - pop: 26933, - }, - { - lat: 33.7389, - lng: 36.6, - pop: 26671, - }, - { - lat: 36.1, - lng: 32.9667, - pop: 26840, - }, - { - lat: 40.8761, - lng: 37.7406, - pop: 26737, - }, - { - lat: 45.5, - lng: 41.2333, - pop: 26761, - }, - { - lat: 46.6742, - lng: 28.0597, - pop: 26266, - }, - { - lat: 33.725, - lng: 36.0972, - pop: 26285, - }, - { - lat: 40.8167, - lng: 39.6167, - pop: 26626, - }, - { - lat: 33.0711, - lng: 36.1842, - pop: 26268, - }, - { - lat: 32.6714, - lng: 35.2406, - pop: 25600, - }, - { - lat: 26.5833, - lng: 49.9833, - pop: 25500, - }, - { - lat: 36.699, - lng: -119.5575, - pop: 26424, - }, - { - lat: 41.4944, - lng: 36.0789, - pop: 26337, - }, - { - lat: 43.2044, - lng: 46.0911, - pop: 25672, - }, - { - lat: 37.1886, - lng: 32.2456, - pop: 26287, - }, - { - lat: 31.4142, - lng: 51.5694, - pop: 26260, - }, - { - lat: 41.9975, - lng: 43.5986, - pop: 26135, - }, - { - lat: 43.95, - lng: 43.6333, - pop: 26106, - }, - { - lat: 39.645, - lng: 41.5083, - pop: 25969, - }, - { - lat: 40.9697, - lng: 27.9553, - pop: 25873, - }, - { - lat: 36.2472, - lng: 29.9828, - pop: 25893, - }, - { - lat: 41.61, - lng: 35.595, - pop: 25854, - }, - { - lat: 45.9675, - lng: 33.8003, - pop: 25769, - }, - { - lat: 36.1167, - lng: 36.1333, - pop: 25118, - }, - { - lat: 30.0542, - lng: 50.1639, - pop: 25730, - }, - { - lat: 32.7711, - lng: 35.0394, - pop: 23700, - }, - { - lat: 32.61, - lng: 35.6081, - pop: 25000, - }, - { - lat: 41.0547, - lng: 30.8503, - pop: 25497, - }, - { - lat: 41.3636, - lng: 41.6792, - pop: 25500, - }, - { - lat: 44.1394, - lng: 43.0169, - pop: 24919, - }, - { - lat: 42.1625, - lng: 42.3417, - pop: 25318, - }, - { - lat: 40.1658, - lng: 38.0942, - pop: 25404, - }, - { - lat: 33.6967, - lng: 36.3739, - pop: 25194, - }, - { - lat: 44.6706, - lng: 41.838, - pop: 25279, - }, - { - lat: 40.7928, - lng: 42.6086, - pop: 25187, - }, - { - lat: 36.5988, - lng: -119.4471, - pop: 25168, - }, - { - lat: 29.9275, - lng: 56.5722, - pop: 25152, - }, - { - lat: 34.8661, - lng: -117.0471, - pop: 25123, - }, - { - lat: 45.1336, - lng: 33.5772, - pop: 24282, - }, - { - lat: 30.8947, - lng: 49.4092, - pop: 25009, - }, - { - lat: 34.2919, - lng: 35.9546, - pop: 25000, - }, - { - lat: 32.4097, - lng: 35.2808, - pop: 24439, - }, - { - lat: 40.6303, - lng: 48.6414, - pop: 24681, - }, - { - lat: 46.6333, - lng: 32.5833, - pop: 24639, - }, - { - lat: 36.5625, - lng: 35.3803, - pop: 24559, - }, - { - lat: 46.85, - lng: 40.3167, - pop: 24561, - }, - { - lat: 40.0494, - lng: 43.6608, - pop: 24560, - }, - { - lat: 36.5715, - lng: -119.6143, - pop: 24545, - }, - { - lat: 40.555, - lng: 44.9536, - pop: 23200, - }, - { - lat: 44.7506, - lng: 44.9797, - pop: 24472, - }, - { - lat: 32.8022, - lng: 51.6211, - pop: 24433, - }, - { - lat: 35.2661, - lng: 36.7114, - pop: 24105, - }, - { - lat: 36.5453, - lng: -119.3987, - pop: 24383, - }, - { - lat: 32.2514, - lng: 48.8161, - pop: 24216, - }, - { - lat: 36.1333, - lng: 36.45, - pop: 23700, - }, - { - lat: 27.8417, - lng: 51.9394, - pop: 24083, - }, - { - lat: 32.5542, - lng: 51.525, - pop: 23203, - }, - { - lat: 34.2511, - lng: 36.0111, - pop: 24000, - }, - { - lat: 41.0096, - lng: 44.3841, - pop: 23782, - }, - { - lat: 41.3903, - lng: 41.4194, - pop: 23846, - }, - { - lat: 33.7075, - lng: 35.9039, - pop: 23000, - }, - { - lat: 41.95, - lng: 34.5833, - pop: 23720, - }, - { - lat: 44.1167, - lng: 42.9833, - pop: 22891, - }, - { - lat: 34.6931, - lng: 32.9992, - pop: 22369, - }, - { - lat: 39.9211, - lng: 40.6947, - pop: 23589, - }, - { - lat: 45.7167, - lng: 42.9, - pop: 23579, - }, - { - lat: 40.8006, - lng: 32.1986, - pop: 23547, - }, - { - lat: 32.7667, - lng: 34.9667, - pop: 22200, - }, - { - lat: 23.55, - lng: 56.25, - pop: 23466, - }, - { - lat: 40.7931, - lng: 37.0164, - pop: 23369, - }, - { - lat: 43.1939, - lng: 45.2833, - pop: 23282, - }, - { - lat: 33.4333, - lng: 36.0833, - pop: 22831, - }, - { - lat: 41.8922, - lng: 33.0044, - pop: 23329, - }, - { - lat: 31.4086, - lng: 48.7942, - pop: 23211, - }, - { - lat: 33.2075, - lng: 35.5697, - pop: 23076, - }, - { - lat: 37.5467, - lng: 34.4844, - pop: 23252, - }, - { - lat: 40.4633, - lng: 42.7858, - pop: 23231, - }, - { - lat: 28.9306, - lng: 51.0689, - pop: 23178, - }, - { - lat: 32.0167, - lng: 35.7667, - pop: 21908, - }, - { - lat: 34.3914, - lng: 36.3958, - pop: 22250, - }, - { - lat: 40.6867, - lng: 37.3992, - pop: 22783, - }, - { - lat: 43.7731, - lng: 41.9169, - pop: 21067, - }, - { - lat: 32.6267, - lng: 51.4392, - pop: 22693, - }, - { - lat: 29.0664, - lng: 58.4047, - pop: 22761, - }, - { - lat: 41.0494, - lng: 39.2353, - pop: 22630, - }, - { - lat: 31.2656, - lng: 56.8056, - pop: 22729, - }, - { - lat: 39.6104, - lng: -119.777, - pop: 22622, - }, - { - lat: 36.0841, - lng: -119.5613, - pop: 22616, - }, - { - lat: 44.4256, - lng: 39.5319, - pop: 22468, - }, - { - lat: 40.0633, - lng: 44.4408, - pop: 21376, - }, - { - lat: 35.95, - lng: 36.7, - pop: 21848, - }, - { - lat: 41.5861, - lng: 32.6406, - pop: 22333, - }, - { - lat: 36.2, - lng: 36.5167, - pop: 21934, - }, - { - lat: 39.5627, - lng: -119.1906, - pop: 22343, - }, - { - lat: 37.2, - lng: 36.5833, - pop: 22242, - }, - { - lat: 40.3756, - lng: 43.4125, - pop: 22282, - }, - { - lat: 31.485, - lng: 48.2686, - pop: 22057, - }, - { - lat: 37.3192, - lng: 37.5686, - pop: 22192, - }, - { - lat: 40.8667, - lng: 35.2167, - pop: 22179, - }, - { - lat: 33.6, - lng: 36.515, - pop: 20559, - }, - { - lat: 32.6594, - lng: 35.11, - pop: 21383, - }, - { - lat: 41.0036, - lng: 36.6319, - pop: 21847, - }, - { - lat: 39.895, - lng: 37.7531, - pop: 21753, - }, - { - lat: 41.3081, - lng: 32.1417, - pop: 21655, - }, - { - lat: 41.0736, - lng: 36.0403, - pop: 21692, - }, - { - lat: 30.4775, - lng: 54.2128, - pop: 21690, - }, - { - lat: 41.2, - lng: 32.3292, - pop: 21625, - }, - { - lat: 42.2689, - lng: 42.0678, - pop: 21596, - }, - { - lat: 36.3756, - lng: 36.9942, - pop: 21039, - }, - { - lat: 33.2539, - lng: 35.2717, - pop: 20000, - }, - { - lat: 32.2269, - lng: 50.7931, - pop: 21352, - }, - { - lat: 39.8728, - lng: 44.5192, - pop: 21311, - }, - { - lat: 36.5667, - lng: 36.1333, - pop: 20459, - }, - { - lat: 40.35, - lng: 30.0167, - pop: 20976, - }, - { - lat: 34.3722, - lng: 41.9875, - pop: 21000, - }, - { - lat: 31.8711, - lng: 35.4442, - pop: 20300, - }, - { - lat: 39.8303, - lng: 44.7025, - pop: 20800, - }, - { - lat: 42.1083, - lng: 43.0417, - pop: 20814, - }, - { - lat: 43.65, - lng: 43.55, - pop: 20718, - }, - { - lat: 28.6689, - lng: 59.0733, - pop: 20720, - }, - { - lat: 37.4278, - lng: 34.8711, - pop: 20683, - }, - { - lat: 41.3333, - lng: 41.3, - pop: 20565, - }, - { - lat: 34.8658, - lng: -118.2155, - pop: 20574, - }, - { - lat: 43.4, - lng: 42.9167, - pop: 20513, - }, - { - lat: 39.6333, - lng: 43.3778, - pop: 20450, - }, - { - lat: 40.7333, - lng: 38.4333, - pop: 20405, - }, - { - lat: 40.9, - lng: 31.05, - pop: 20266, - }, - { - lat: 32.7781, - lng: 51.6461, - pop: 20301, - }, - { - lat: 43.2419, - lng: 46, - pop: 20013, - }, - { - lat: 28.8714, - lng: 52.0917, - pop: 20320, - }, - { - lat: 41.4969, - lng: 44.8108, - pop: 20211, - }, - { - lat: 32.555, - lng: 51.5731, - pop: 19406, - }, - { - lat: 37.0669, - lng: 36.1464, - pop: 20127, - }, - { - lat: 34.2028, - lng: 35.6544, - pop: 20000, - }, - { - lat: 40.1894, - lng: 39.1267, - pop: 20084, - }, - { - lat: 40.6172, - lng: 43.9758, - pop: 19543, - }, - { - lat: 46.7333, - lng: 29.7, - pop: 20000, - }, - { - lat: 43.2242, - lng: 46.1942, - pop: 19727, - }, - { - lat: 43.0333, - lng: 44.2333, - pop: 20043, - }, - { - lat: 36.8032, - lng: -114.133, - pop: 20019, - }, - { - lat: 35.4794, - lng: -119.2013, - pop: 19897, - }, - { - lat: 30.895, - lng: 50.0931, - pop: 19857, - }, - { - lat: 32.5167, - lng: 36.4833, - pop: 19683, - }, - { - lat: 40.0731, - lng: 35.4947, - pop: 19786, - }, - { - lat: 41.4167, - lng: 35.05, - pop: 19650, - }, - { - lat: 34.4686, - lng: 41.9167, - pop: 19629, - }, - { - lat: 35.1944, - lng: -118.8306, - pop: 19568, - }, - { - lat: 43.4833, - lng: 44.1333, - pop: 19494, - }, - { - lat: 34.9478, - lng: 33.5881, - pop: 19199, - }, - { - lat: 40.8106, - lng: 41.5269, - pop: 19510, - }, - { - lat: 37.1487, - lng: -113.3517, - pop: 19501, - }, - { - lat: 43.1833, - lng: 44.3167, - pop: 19412, - }, - { - lat: 31.4619, - lng: 48.0739, - pop: 19481, - }, - { - lat: 36.7678, - lng: 35.7922, - pop: 18587, - }, - { - lat: 32.8454, - lng: 36.2499, - pop: 19158, - }, - { - lat: 27.8914, - lng: 53.4344, - pop: 19347, - }, - { - lat: 46.1667, - lng: 34.8, - pop: 19253, - }, - { - lat: 40.0186, - lng: 30.1814, - pop: 19244, - }, - { - lat: 27.5014, - lng: 52.5858, - pop: 18837, - }, - { - lat: 26.5992, - lng: 54.9361, - pop: 19213, - }, - { - lat: 32.0006, - lng: 54.2075, - pop: 19123, - }, - { - lat: 46.7, - lng: 41.7333, - pop: 19032, - }, - { - lat: 29.3606, - lng: 51.0683, - pop: 18913, - }, - { - lat: 28.9864, - lng: 51.0375, - pop: 18702, - }, - { - lat: 31.9436, - lng: 34.8392, - pop: 18401, - }, - { - lat: 35.3886, - lng: -119.2058, - pop: 18875, - }, - { - lat: 41.2422, - lng: 33.3283, - pop: 18863, - }, - { - lat: 31.9833, - lng: 35.7667, - pop: 17754, - }, - { - lat: 32.5, - lng: 34.9167, - pop: 17759, - }, - { - lat: 45.45, - lng: 29.2667, - pop: 18745, - }, - { - lat: 43.1642, - lng: 45.6228, - pop: 18534, - }, - { - lat: 40.8139, - lng: 32.8908, - pop: 18694, - }, - { - lat: 39.6731, - lng: 33.6136, - pop: 18139, - }, - { - lat: 34.4398, - lng: -117.5248, - pop: 18599, - }, - { - lat: 36.65, - lng: 36.2167, - pop: 17925, - }, - { - lat: 31.9408, - lng: 54.2736, - pop: 18309, - }, - { - lat: 31.9414, - lng: 54.2828, - pop: 18309, - }, - { - lat: 29.5, - lng: 53.3167, - pop: 18477, - }, - { - lat: 47.3907, - lng: 35.0027, - pop: 18468, - }, - { - lat: 31.7492, - lng: 54.2103, - pop: 18464, - }, - { - lat: 47.0333, - lng: 28.95, - pop: 17210, - }, - { - lat: 43.2081, - lng: 44.8186, - pop: 17734, - }, - { - lat: 32.5, - lng: 35.5, - pop: 18200, - }, - { - lat: 31.5419, - lng: 60.0364, - pop: 18304, - }, - { - lat: 40.5619, - lng: 42.3464, - pop: 18281, - }, - { - lat: 29.2736, - lng: 53.2203, - pop: 18187, - }, - { - lat: 33.6797, - lng: 35.5583, - pop: 17000, - }, - { - lat: 28.4758, - lng: 57.8481, - pop: 18185, - }, - { - lat: 43.1667, - lng: 46, - pop: 17970, - }, - { - lat: 33.6422, - lng: 36.2978, - pop: 17521, - }, - { - lat: 32.3836, - lng: 51.5147, - pop: 17966, - }, - { - lat: 35.25, - lng: 36.5833, - pop: 17578, - }, - { - lat: 36.1231, - lng: 37.3369, - pop: 17767, - }, - { - lat: 36.2583, - lng: 41.9431, - pop: 18000, - }, - { - lat: 34.4818, - lng: -118.6316, - pop: 18017, - }, - { - lat: 35.1, - lng: 33.3667, - pop: 16774, - }, - { - lat: 45.4575, - lng: 28.2711, - pop: 17736, - }, - { - lat: 26.2258, - lng: 60.2142, - pop: 17732, - }, - { - lat: 41.0314, - lng: 36.2683, - pop: 17628, - }, - { - lat: 34.9203, - lng: 40.5594, - pop: 17537, - }, - { - lat: 29.9125, - lng: 53.3086, - pop: 17642, - }, - { - lat: 35.3697, - lng: 36.38, - pop: 17313, - }, - { - lat: 41.2433, - lng: 42.3639, - pop: 17606, - }, - { - lat: 32.3781, - lng: 51.3181, - pop: 17335, - }, - { - lat: 44.1778, - lng: 43.5, - pop: 17451, - }, - { - lat: 46.1167, - lng: 32.9167, - pop: 17344, - }, - { - lat: 45.6855, - lng: 28.6134, - pop: 17400, - }, - { - lat: 35.3736, - lng: 36.6017, - pop: 17052, - }, - { - lat: 40.8483, - lng: 43.3317, - pop: 17373, - }, - { - lat: 30.0775, - lng: 53.1331, - pop: 17131, - }, - { - lat: 46.9753, - lng: 28.8194, - pop: 15934, - }, - { - lat: 40.07, - lng: 47.2047, - pop: 16998, - }, - { - lat: 40.2981, - lng: 41.6325, - pop: 17054, - }, - { - lat: 40.9833, - lng: 39.8, - pop: 15503, - }, - { - lat: 44.3381, - lng: 28.0336, - pop: 17022, - }, - { - lat: 41.1333, - lng: 41.0167, - pop: 16902, - }, - { - lat: 31.94, - lng: 51.6478, - pop: 16899, - }, - { - lat: 32.8542, - lng: 36.6292, - pop: 16745, - }, - { - lat: 40.7408, - lng: 44.8631, - pop: 16600, - }, - { - lat: 25.6439, - lng: 57.7744, - pop: 16860, - }, - { - lat: 46.0833, - lng: 40.8583, - pop: 16838, - }, - { - lat: 36.4575, - lng: 41.7061, - pop: 16798, - }, - { - lat: 44.8514, - lng: 34.9725, - pop: 16784, - }, - { - lat: 40.9, - lng: 38.4167, - pop: 16758, - }, - { - lat: 46.05, - lng: 28.8333, - pop: 16605, - }, - { - lat: 32.4725, - lng: 35.7928, - pop: 16000, - }, - { - lat: 32.7333, - lng: 36.2, - pop: 16240, - }, - { - lat: 37.5465, - lng: 35.3987, - pop: 16653, - }, - { - lat: 35.3208, - lng: 36.6225, - pop: 16267, - }, - { - lat: 41.0833, - lng: 39.3833, - pop: 16335, - }, - { - lat: 43.9333, - lng: 42.5167, - pop: 16512, - }, - { - lat: 40.646, - lng: 34.261, - pop: 16525, - }, - { - lat: 45.0544, - lng: 34.6022, - pop: 16428, - }, - { - lat: 32.8353, - lng: 35.9714, - pop: 15985, - }, - { - lat: 36.2333, - lng: 36.2167, - pop: 15692, - }, - { - lat: 32.3936, - lng: 51.3408, - pop: 16086, - }, - { - lat: 40.7475, - lng: 40.2419, - pop: 16213, - }, - { - lat: 39.9756, - lng: 41.8711, - pop: 16178, - }, - { - lat: 43.6003, - lng: 46.7789, - pop: 16165, - }, - { - lat: 34.8868, - lng: 38.8721, - pop: 16173, - }, - { - lat: 39.6568, - lng: -119.6694, - pop: 16131, - }, - { - lat: 40.4425, - lng: 47.6767, - pop: 16018, - }, - { - lat: 33.2692, - lng: 35.7706, - pop: 15973, - }, - { - lat: 41.9486, - lng: 34.3367, - pop: 16004, - }, - { - lat: 46.7006, - lng: 32.5478, - pop: 15984, - }, - { - lat: 27.7975, - lng: 53.685, - pop: 16000, - }, - { - lat: 46.1167, - lng: 48.0833, - pop: 15984, - }, - { - lat: 33.1047, - lng: 50.9589, - pop: 15828, - }, - { - lat: 37.0164, - lng: 41.9544, - pop: 15759, - }, - { - lat: 35.0004, - lng: -114.5748, - pop: 15872, - }, - { - lat: 43.4333, - lng: 28.3333, - pop: 15834, - }, - { - lat: 36.0561, - lng: 40.7303, - pop: 15806, - }, - { - lat: 31.4469, - lng: 49.5294, - pop: 15802, - }, - { - lat: 32.7025, - lng: 51.1536, - pop: 15673, - }, - { - lat: 35.9632, - lng: 38.0356, - pop: 15477, - }, - { - lat: 33.0528, - lng: 51.0825, - pop: 15550, - }, - { - lat: 33.4242, - lng: 36.2244, - pop: 13993, - }, - { - lat: 40.52, - lng: 35.2953, - pop: 15655, - }, - { - lat: 27.4064, - lng: 57.5014, - pop: 15634, - }, - { - lat: 34.7087, - lng: 33.0504, - pop: 14578, - }, - { - lat: 40.9667, - lng: 31.45, - pop: 15573, - }, - { - lat: 32.8019, - lng: 51.6636, - pop: 15524, - }, - { - lat: 30.8733, - lng: 55.2706, - pop: 15532, - }, - { - lat: 40.0172, - lng: 32.3483, - pop: 15540, - }, - { - lat: 36.3667, - lng: 36.2, - pop: 14751, - }, - { - lat: 31.4825, - lng: 48.8747, - pop: 15312, - }, - { - lat: 47.2848, - lng: 39.4823, - pop: 15334, - }, - { - lat: 40.879, - lng: 37.4532, - pop: 14954, - }, - { - lat: 40.8372, - lng: 44.2675, - pop: 15000, - }, - { - lat: 35.0333, - lng: 33.9833, - pop: 14963, - }, - { - lat: 30.7461, - lng: 50.7461, - pop: 15218, - }, - { - lat: 28.8833, - lng: 51.275, - pop: 15198, - }, - { - lat: 39.8033, - lng: 29.6178, - pop: 15181, - }, - { - lat: 46.5167, - lng: 32.5167, - pop: 15163, - }, - { - lat: 41.45, - lng: 45.1, - pop: 15100, - }, - { - lat: 34.2597, - lng: 36.4236, - pop: 15000, - }, - { - lat: 33.6333, - lng: 35.7833, - pop: 14728, - }, - { - lat: 32.4917, - lng: 36.7111, - pop: 15000, - }, - { - lat: 39.2592, - lng: -119.5653, - pop: 15036, - }, - { - lat: 32.5379, - lng: 34.9122, - pop: 13962, - }, - { - lat: 37.544, - lng: 41.72, - pop: 14976, - }, - { - lat: 35.9033, - lng: 36.7258, - pop: 14530, - }, - { - lat: 40.8736, - lng: 30.9508, - pop: 14895, - }, - { - lat: 36.6975, - lng: 38.9567, - pop: 14825, - }, - { - lat: 35.1578, - lng: -117.8721, - pop: 14914, - }, - { - lat: 35.8407, - lng: -114.9257, - pop: 14868, - }, - { - lat: 41.7494, - lng: 32.3864, - pop: 14776, - }, - { - lat: 43.17, - lng: 45.3711, - pop: 14720, - }, - { - lat: 42.4167, - lng: 27.7, - pop: 14789, - }, - { - lat: 45.85, - lng: 41.5167, - pop: 14761, - }, - { - lat: 40.9333, - lng: 38.1333, - pop: 14659, - }, - { - lat: 45.3694, - lng: 44.2281, - pop: 14722, - }, - { - lat: 46.6186, - lng: 31.5392, - pop: 14705, - }, - { - lat: 30.2625, - lng: 51.9833, - pop: 14633, - }, - { - lat: 33.6497, - lng: 35.4433, - pop: 12888, - }, - { - lat: 40.8856, - lng: 39.2922, - pop: 14592, - }, - { - lat: 26.9636, - lng: 56.0622, - pop: 14525, - }, - { - lat: 35.3753, - lng: 36.6872, - pop: 14307, - }, - { - lat: 33.7421, - lng: 36.6435, - pop: 14228, - }, - { - lat: 37.4025, - lng: 40.9561, - pop: 14233, - }, - { - lat: 43.2162, - lng: 46.0381, - pop: 14111, - }, - { - lat: 40.262, - lng: 36.313, - pop: 14335, - }, - { - lat: 39.71, - lng: 39.7017, - pop: 14390, - }, - { - lat: 46.1269, - lng: 30.385, - pop: 14321, - }, - { - lat: 37.4431, - lng: 36.0322, - pop: 14308, - }, - { - lat: 32.4467, - lng: 35.1703, - pop: 13640, - }, - { - lat: 35.2659, - lng: -118.9159, - pop: 14269, - }, - { - lat: 39.6936, - lng: 35.5111, - pop: 14198, - }, - { - lat: 34.7181, - lng: 33.0856, - pop: 13421, - }, - { - lat: 43.2036, - lng: 46.1322, - pop: 13824, - }, - { - lat: 41.05, - lng: 39.1333, - pop: 13955, - }, - { - lat: 42.65, - lng: 27.7333, - pop: 14146, - }, - { - lat: 44.27, - lng: 28.56, - pop: 13968, - }, - { - lat: 40.9, - lng: 30.4833, - pop: 13973, - }, - { - lat: 35.6781, - lng: -119.2413, - pop: 14085, - }, - { - lat: 25.7347, - lng: 51.5475, - pop: 13511, - }, - { - lat: 43.1636, - lng: 45.4725, - pop: 13836, - }, - { - lat: 36.255, - lng: 42.0164, - pop: 14000, - }, - { - lat: 41.0333, - lng: 37.1, - pop: 13922, - }, - { - lat: 27.4744, - lng: 52.6114, - pop: 13557, - }, - { - lat: 39.9106, - lng: 44.7278, - pop: 13600, - }, - { - lat: 46.6791, - lng: 32.7228, - pop: 12812, - }, - { - lat: 28.4061, - lng: 54.1881, - pop: 13809, - }, - { - lat: 36.3947, - lng: 36.6889, - pop: 13661, - }, - { - lat: 36.2828, - lng: 36.8519, - pop: 13525, - }, - { - lat: 40.5572, - lng: 39.2919, - pop: 13771, - }, - { - lat: 43.25, - lng: 46.1333, - pop: 13405, - }, - { - lat: 32.4167, - lng: 35.6833, - pop: 13056, - }, - { - lat: 47.0833, - lng: 39.5667, - pop: 13692, - }, - { - lat: 36.5833, - lng: 31.8833, - pop: 13563, - }, - { - lat: 34.8969, - lng: 36.1346, - pop: 13244, - }, - { - lat: 27.2856, - lng: 61.9964, - pop: 13580, - }, - { - lat: 32.8425, - lng: 36.34, - pop: 13315, - }, - { - lat: 32.9411, - lng: 50.1211, - pop: 13475, - }, - { - lat: 41.925, - lng: 44.4222, - pop: 13423, - }, - { - lat: 30.8939, - lng: 61.6803, - pop: 13357, - }, - { - lat: 33.93, - lng: 35.745, - pop: 12000, - }, - { - lat: 41.4647, - lng: 47.74, - pop: 13405, - }, - { - lat: 32.0078, - lng: 51.2156, - pop: 13317, - }, - { - lat: 35.41, - lng: 36.39, - pop: 12925, - }, - { - lat: 34.7833, - lng: 36.4333, - pop: 13020, - }, - { - lat: 35.1276, - lng: -118.4744, - pop: 13346, - }, - { - lat: 41.6667, - lng: 48.1333, - pop: 13232, - }, - { - lat: 36.5133, - lng: 41.9542, - pop: 13281, - }, - { - lat: 41.3228, - lng: 47.1133, - pop: 13260, - }, - { - lat: 32.7109, - lng: 36.0266, - pop: 12640, - }, - { - lat: 31.19, - lng: 50.4419, - pop: 13269, - }, - { - lat: 29.2911, - lng: 56.9131, - pop: 13263, - }, - { - lat: 40.9814, - lng: 47.8458, - pop: 13190, - }, - { - lat: 41.1333, - lng: 44.65, - pop: 13000, - }, - { - lat: 40.16, - lng: 47.1722, - pop: 13002, - }, - { - lat: 27.5236, - lng: 57.8811, - pop: 13169, - }, - { - lat: 46.3425, - lng: 30.5653, - pop: 13036, - }, - { - lat: 37.4711, - lng: 41.9139, - pop: 13091, - }, - { - lat: 37.4792, - lng: 40.4864, - pop: 13117, - }, - { - lat: 44.8832, - lng: 39.1902, - pop: 12745, - }, - { - lat: 45.3908, - lng: 47.3658, - pop: 13125, - }, - { - lat: 43.5167, - lng: 43.7, - pop: 12813, - }, - { - lat: 32.6428, - lng: 51.5, - pop: 11264, - }, - { - lat: 25.6208, - lng: 51.0819, - pop: 13085, - }, - { - lat: 37.0519, - lng: 31.7842, - pop: 13084, - }, - { - lat: 26.5758, - lng: 59.6397, - pop: 13070, - }, - { - lat: 32.5444, - lng: 50.7461, - pop: 12971, - }, - { - lat: 30.0547, - lng: 54.3717, - pop: 13032, - }, - { - lat: 45.9072, - lng: 43.3558, - pop: 12998, - }, - { - lat: 35.0611, - lng: 36.6972, - pop: 12194, - }, - { - lat: 34.5596, - lng: -117.9558, - pop: 12961, - }, - { - lat: 34.3436, - lng: 36.4756, - pop: 12000, - }, - { - lat: 32.4197, - lng: 52.6483, - pop: 12714, - }, - { - lat: 43.1878, - lng: 44.9036, - pop: 12734, - }, - { - lat: 42.2903, - lng: 43.2819, - pop: 12803, - }, - { - lat: 35.2645, - lng: -114.0091, - pop: 12858, - }, - { - lat: 34.7594, - lng: -112.412, - pop: 12854, - }, - { - lat: 43.2906, - lng: 45.3014, - pop: 12738, - }, - { - lat: 35.6342, - lng: 36.6322, - pop: 12276, - }, - { - lat: 45.1142, - lng: 34.0142, - pop: 12711, - }, - { - lat: 30.9817, - lng: 50.4233, - pop: 12772, - }, - { - lat: 43.4847, - lng: 44.5881, - pop: 12614, - }, - { - lat: 43.4333, - lng: 43.575, - pop: 10829, - }, - { - lat: 40.1806, - lng: 45.72, - pop: 12363, - }, - { - lat: 40.3053, - lng: 37.8306, - pop: 12637, - }, - { - lat: 36.6964, - lng: 32.6203, - pop: 12601, - }, - { - lat: 40.9667, - lng: 39.7333, - pop: 11077, - }, - { - lat: 44.1128, - lng: 28.5558, - pop: 12333, - }, - { - lat: 34.1542, - lng: 36.7442, - pop: 12508, - }, - { - lat: 44.165, - lng: 28.455, - pop: 12376, - }, - { - lat: 43.3458, - lng: 44.2028, - pop: 12501, - }, - { - lat: 43.2586, - lng: 45.5392, - pop: 12340, - }, - { - lat: 36.2082, - lng: -119.0897, - pop: 12551, - }, - { - lat: 32.3378, - lng: 51.1961, - pop: 12292, - }, - { - lat: 34.6097, - lng: -117.8339, - pop: 12497, - }, - { - lat: 34.2419, - lng: 35.9794, - pop: 12000, - }, - { - lat: 43.4269, - lng: 28.1617, - pop: 12429, - }, - { - lat: 39.9011, - lng: 38.7686, - pop: 12456, - }, - { - lat: 26.5583, - lng: 49.9503, - pop: 11460, - }, - { - lat: 43.2514, - lng: 45.9072, - pop: 12224, - }, - { - lat: 36.5244, - lng: -119.5602, - pop: 12413, - }, - { - lat: 32.3156, - lng: 50.6783, - pop: 12308, - }, - { - lat: 36.2936, - lng: 37.0444, - pop: 11918, - }, - { - lat: 40.14, - lng: 45.3064, - pop: 11987, - }, - { - lat: 43.3117, - lng: 45.1594, - pop: 12221, - }, - { - lat: 32.2854, - lng: 35.8113, - pop: 11586, - }, - { - lat: 43.1126, - lng: 45.7339, - pop: 12092, - }, - { - lat: 40.3167, - lng: 38.7667, - pop: 12250, - }, - { - lat: 42.665, - lng: 46.22, - pop: 12159, - }, - { - lat: 45.6833, - lng: 28.4028, - pop: 12185, - }, - { - lat: 27.1944, - lng: 60.4558, - pop: 12217, - }, - { - lat: 46.5331, - lng: 48.3456, - pop: 12214, - }, - { - lat: 34.5683, - lng: 36.2764, - pop: 12000, - }, - { - lat: 46.8333, - lng: 33.4167, - pop: 12123, - }, - { - lat: 32.6653, - lng: 35.7333, - pop: 11706, - }, - { - lat: 43.6756, - lng: 43.455, - pop: 12001, - }, - { - lat: 30.0042, - lng: 53.0067, - pop: 12000, - }, - { - lat: 41.8833, - lng: 34.9167, - pop: 12049, - }, - { - lat: 47.0708, - lng: 32.7997, - pop: 12045, - }, - { - lat: 31.9383, - lng: 51.0533, - pop: 11980, - }, - { - lat: 33.1839, - lng: 36.2264, - pop: 11802, - }, - { - lat: 40.9131, - lng: 37.5169, - pop: 11851, - }, - { - lat: 36.9883, - lng: 32.4569, - pop: 11970, - }, - { - lat: 36.6667, - lng: 34.4167, - pop: 11923, - }, - { - lat: 47.2717, - lng: 35.2248, - pop: 11949, - }, - { - lat: 40.9422, - lng: 39.1942, - pop: 11934, - }, - { - lat: 31.9911, - lng: 54.2322, - pop: 11691, - }, - { - lat: 42.4333, - lng: 47.3167, - pop: 11862, - }, - { - lat: 47.1708, - lng: 37.6954, - pop: 10350, - }, - { - lat: 46.2667, - lng: 30.4333, - pop: 11741, - }, - { - lat: 47.1333, - lng: 28.8667, - pop: 10669, - }, - { - lat: 33.2092, - lng: 35.2992, - pop: 10000, - }, - { - lat: 33.5581, - lng: 36.2222, - pop: 10045, - }, - { - lat: 43.6119, - lng: 43.3269, - pop: 11717, - }, - { - lat: 36.8175, - lng: 38.0111, - pop: 11570, - }, - { - lat: 38.8957, - lng: -119.7492, - pop: 11761, - }, - { - lat: 46.7353, - lng: 36.3473, - pop: 11679, - }, - { - lat: 32.4689, - lng: 51.5578, - pop: 10851, - }, - { - lat: 43.5667, - lng: 43.5833, - pop: 11575, - }, - { - lat: 26.2483, - lng: 60.7525, - pop: 11605, - }, - { - lat: 43.4911, - lng: 43.5528, - pop: 9669, - }, - { - lat: 46.6977, - lng: 35.1554, - pop: 11481, - }, - { - lat: 36.8833, - lng: 36.2333, - pop: 11187, - }, - { - lat: 46.9122, - lng: 28.8839, - pop: 10175, - }, - { - lat: 42.3264, - lng: 42.6006, - pop: 11281, - }, - { - lat: 36.6389, - lng: 32.8925, - pop: 11332, - }, - { - lat: 29.9789, - lng: 48.5206, - pop: 11173, - }, - { - lat: 30.0617, - lng: 48.4508, - pop: 11173, - }, - { - lat: 39.7667, - lng: 30.95, - pop: 11242, - }, - { - lat: 44.45, - lng: 42.5, - pop: 11215, - }, - { - lat: 46.1083, - lng: 28.5972, - pop: 11123, - }, - { - lat: 36.1417, - lng: 33.3178, - pop: 11088, - }, - { - lat: 34.2425, - lng: 37.0589, - pop: 11064, - }, - { - lat: 46.6833, - lng: 47.85, - pop: 11079, - }, - { - lat: 36.7667, - lng: 31.3889, - pop: 11000, - }, - { - lat: 47.0667, - lng: 28.6833, - pop: 10380, - }, - { - lat: 34.0833, - lng: 36.7667, - pop: 10984, - }, - { - lat: 44.4361, - lng: 34.1106, - pop: 10310, - }, - { - lat: 46.8678, - lng: 28.7689, - pop: 10907, - }, - { - lat: 45.5019, - lng: 32.7025, - pop: 11039, - }, - { - lat: 43.5261, - lng: 43.5594, - pop: 11004, - }, - { - lat: 36.1389, - lng: 36.83, - pop: 10657, - }, - { - lat: 41.6281, - lng: 48.6828, - pop: 10894, - }, - { - lat: 40.7444, - lng: 43.625, - pop: 10985, - }, - { - lat: 36.2167, - lng: 36.1667, - pop: 10354, - }, - { - lat: 33.4861, - lng: 36.6011, - pop: 10548, - }, - { - lat: 33.7333, - lng: 35.45, - pop: 10000, - }, - { - lat: 32.6872, - lng: 34.9383, - pop: 10639, - }, - { - lat: 37.3406, - lng: 40.8258, - pop: 10846, - }, - { - lat: 41.3, - lng: 27.95, - pop: 10601, - }, - { - lat: 44.5528, - lng: 34.2875, - pop: 9117, - }, - { - lat: 36.6667, - lng: 34.3833, - pop: 10907, - }, - { - lat: 32.2983, - lng: 48.4289, - pop: 10858, - }, - { - lat: 40.1428, - lng: 44.1164, - pop: 9870, - }, - { - lat: 41.3833, - lng: 27.9333, - pop: 10072, - }, - { - lat: 41.7333, - lng: 45.3333, - pop: 10871, - }, - { - lat: 40.9368, - lng: 45.8258, - pop: 10797, - }, - { - lat: 34.3, - lng: 35.8, - pop: 10000, - }, - { - lat: 36.7167, - lng: 36.2333, - pop: 10574, - }, - { - lat: 40.3217, - lng: 44.4814, - pop: 10656, - }, - { - lat: 35.683, - lng: 36.533, - pop: 10353, - }, - { - lat: 36.2333, - lng: 36.8167, - pop: 10394, - }, - { - lat: 32.6872, - lng: 36.3508, - pop: 10510, - }, - { - lat: 45.4978, - lng: 34.295, - pop: 10766, - }, - { - lat: 35.0211, - lng: 33.42, - pop: 10466, - }, - { - lat: 41.4783, - lng: 46.6175, - pop: 10700, - }, - { - lat: 30.3611, - lng: 51.1572, - pop: 10764, - }, - { - lat: 36.8667, - lng: 36.2, - pop: 10482, - }, - { - lat: 44.6667, - lng: 45.65, - pop: 10641, - }, - { - lat: 33.85, - lng: 35.6667, - pop: 10000, - }, - { - lat: 32.6322, - lng: 36.3386, - pop: 10466, - }, - { - lat: 31.4103, - lng: 56.2825, - pop: 10761, - }, - { - lat: 45.0448, - lng: 42.1104, - pop: 10695, - }, - { - lat: 31.6325, - lng: 49.8897, - pop: 10698, - }, - { - lat: 44.7444, - lng: 44.2031, - pop: 10721, - }, - { - lat: 29.2417, - lng: 57.3253, - pop: 10670, - }, - { - lat: 37.4944, - lng: 30.9817, - pop: 10707, - }, - { - lat: 40.3833, - lng: 35.5167, - pop: 10703, - }, - { - lat: 44.6142, - lng: 33.6083, - pop: 10196, - }, - { - lat: 46.3544, - lng: 34.3361, - pop: 10647, - }, - { - lat: 41.9747, - lng: 33.7608, - pop: 10594, - }, - { - lat: 33.3986, - lng: 36.4531, - pop: 10473, - }, - { - lat: 43.0878, - lng: 46.5631, - pop: 10532, - }, - { - lat: 40.2847, - lng: 30.3172, - pop: 10591, - }, - { - lat: 46.1958, - lng: 41.0778, - pop: 10593, - }, - { - lat: 36.0833, - lng: 36.5, - pop: 10296, - }, - { - lat: 29.5636, - lng: 51.3369, - pop: 10508, - }, - { - lat: 41.8389, - lng: 43.3792, - pop: 10546, - }, - { - lat: 40, - lng: 29.9, - pop: 10527, - }, - { - lat: 46.6167, - lng: 29.9167, - pop: 10436, - }, - { - lat: 32.3772, - lng: 51.1883, - pop: 10279, - }, - { - lat: 40.78, - lng: 43.1353, - pop: 10497, - }, - { - lat: 40.0986, - lng: 44.4681, - pop: 9550, - }, - { - lat: 36.834, - lng: 37.999, - pop: 10436, - }, - { - lat: 23.4675, - lng: 58.1061, - pop: 10396, - }, - { - lat: 36.305, - lng: -119.2083, - pop: 10441, - }, - { - lat: 30.1811, - lng: 56.8019, - pop: 10407, - }, - { - lat: 40.1167, - lng: 35.2667, - pop: 10407, - }, - { - lat: 46.7559, - lng: 33.4247, - pop: 10360, - }, - { - lat: 36.2941, - lng: -119.1459, - pop: 10349, - }, - { - lat: 44.1736, - lng: 28.4083, - pop: 10216, - }, - { - lat: 25.7089, - lng: 55.7972, - pop: 10190, - }, - { - lat: 36.1164, - lng: 36.5147, - pop: 10084, - }, - { - lat: 40.8333, - lng: 33.25, - pop: 10307, - }, - { - lat: 29.5978, - lng: 57.4386, - pop: 10286, - }, - { - lat: 27.4753, - lng: 59.4717, - pop: 10292, - }, - { - lat: 32.6828, - lng: 36.2233, - pop: 9784, - }, - { - lat: 40.9419, - lng: 45.7358, - pop: 10130, - }, - { - lat: 46.5036, - lng: 30.3244, - pop: 10148, - }, - { - lat: 39.7981, - lng: 42.6744, - pop: 10191, - }, - { - lat: 45.6333, - lng: 27.8, - pop: 10126, - }, - { - lat: 28.8678, - lng: 52.7533, - pop: 10120, - }, - { - lat: 46.9139, - lng: 28.9708, - pop: 9966, - }, - { - lat: 31.9989, - lng: 50.6617, - pop: 10113, - }, - { - lat: 26.2361, - lng: 61.3986, - pop: 10115, - }, - { - lat: 37.471, - lng: 42.317, - pop: 10094, - }, - { - lat: 43.1581, - lng: 44.1569, - pop: 10075, - }, - { - lat: 43.071, - lng: 46.6345, - pop: 10014, - }, - { - lat: 34.6527, - lng: -118.2163, - pop: 10079, - }, - { - lat: 46.0903, - lng: 47.7306, - pop: 10036, - }, - { - lat: 40.8808, - lng: 45.3917, - pop: 9864, - }, - { - lat: 39.75, - lng: 28.9167, - pop: 10042, - }, - { - lat: 35.1409, - lng: -118.4968, - pop: 10051, - }, - { - lat: 27.6594, - lng: 52.6575, - pop: 9982, - }, - { - lat: 43.2317, - lng: 45.5722, - pop: 9783, - }, - { - lat: 40.1331, - lng: 45.4367, - pop: 9880, - }, - { - lat: 36.9667, - lng: 35.05, - pop: 8689, - }, - { - lat: 27.1992, - lng: 54.3667, - pop: 9959, - }, - { - lat: 31.9336, - lng: 51.3306, - pop: 9923, - }, - { - lat: 33.0736, - lng: 50.1647, - pop: 9933, - }, - { - lat: 35.6169, - lng: 36.5953, - pop: 9595, - }, - { - lat: 43.1328, - lng: 45.7797, - pop: 9738, - }, - { - lat: 32.7039, - lng: 51.8381, - pop: 9712, - }, - { - lat: 47.2039, - lng: 30.9125, - pop: 9845, - }, - { - lat: 41.1289, - lng: 43.1328, - pop: 9833, - }, - { - lat: 42.3503, - lng: 42.9983, - pop: 9770, - }, - { - lat: 28.745, - lng: 53.8033, - pop: 9719, - }, - { - lat: 40.1333, - lng: 38.7333, - pop: 9759, - }, - { - lat: 47.0189, - lng: 34.9212, - pop: 9719, - }, - { - lat: 32.8658, - lng: 51.5972, - pop: 9690, - }, - { - lat: 36.8708, - lng: 39.025, - pop: 9653, - }, - { - lat: 40.7814, - lng: 43.8964, - pop: 9668, - }, - { - lat: 43.1797, - lng: 45.4081, - pop: 9584, - }, - { - lat: 45.1278, - lng: 39.5725, - pop: 9617, - }, - { - lat: 40.5194, - lng: 28.8281, - pop: 9625, - }, - { - lat: 44.6833, - lng: 27.9519, - pop: 9642, - }, - { - lat: 46.3667, - lng: 28.5167, - pop: 9562, - }, - { - lat: 46.6317, - lng: 32.4452, - pop: 9565, - }, - { - lat: 33.6878, - lng: 36.1008, - pop: 9371, - }, - { - lat: 44.8228, - lng: 44.6592, - pop: 9602, - }, - { - lat: 39.7175, - lng: 44.8764, - pop: 9306, - }, - { - lat: 46.3629, - lng: 33.5302, - pop: 9539, - }, - { - lat: 43.3725, - lng: 46.445, - pop: 9442, - }, - { - lat: 43.2, - lng: 45.7889, - pop: 9300, - }, - { - lat: 45.4464, - lng: 34.7344, - pop: 9460, - }, - { - lat: 43.9625, - lng: 42.9875, - pop: 9427, - }, - { - lat: 47.1503, - lng: 29.2925, - pop: 9381, - }, - { - lat: 40.21, - lng: 39.6511, - pop: 9387, - }, - { - lat: 29.0147, - lng: 61.45, - pop: 9359, - }, - { - lat: 28.0842, - lng: 54.0483, - pop: 9318, - }, - { - lat: 44.5167, - lng: 34.1833, - pop: 8571, - }, - { - lat: 47.0875, - lng: 28.8703, - pop: 8694, - }, - { - lat: 44.8981, - lng: 28.7419, - pop: 9213, - }, - { - lat: 46.2581, - lng: 33.2843, - pop: 9224, - }, - { - lat: 31.8719, - lng: 56.0239, - pop: 9232, - }, - { - lat: 29.885, - lng: 57.7306, - pop: 9205, - }, - { - lat: 47.25, - lng: 28.7667, - pop: 9122, - }, - { - lat: 40, - lng: 36.22, - pop: 9154, - }, - { - lat: 45.85, - lng: 28.6944, - pop: 9138, - }, - { - lat: 27.8722, - lng: 52.0289, - pop: 8753, - }, - { - lat: 35.5464, - lng: 36.6431, - pop: 8817, - }, - { - lat: 29.6, - lng: 55.5369, - pop: 9112, - }, - { - lat: 43.3186, - lng: 45.9878, - pop: 8972, - }, - { - lat: 35.2477, - lng: -116.6834, - pop: 9100, - }, - { - lat: 41.6222, - lng: 35.5314, - pop: 8864, - }, - { - lat: 46.7833, - lng: 29.6167, - pop: 9000, - }, - { - lat: 35.0025, - lng: 40.5117, - pop: 9000, - }, - { - lat: 39.4737, - lng: -118.7779, - pop: 9068, - }, - { - lat: 40.1, - lng: 31.6833, - pop: 9039, - }, - { - lat: 39.8056, - lng: 40.0364, - pop: 9032, - }, - { - lat: 40.75, - lng: 33.7667, - pop: 8981, - }, - { - lat: 42.3244, - lng: 42.4222, - pop: 8987, - }, - { - lat: 41.45, - lng: 44.5333, - pop: 8967, - }, - { - lat: 41.5, - lng: 31.8667, - pop: 8678, - }, - { - lat: 32.3828, - lng: 35.6619, - pop: 8647, - }, - { - lat: 44.2333, - lng: 42.0167, - pop: 8836, - }, - { - lat: 28.3106, - lng: 54.3347, - pop: 8927, - }, - { - lat: 40.1467, - lng: 45.2642, - pop: 8553, - }, - { - lat: 35.9969, - lng: 36.7867, - pop: 8540, - }, - { - lat: 46.1353, - lng: 41.9656, - pop: 8798, - }, - { - lat: 29.4769, - lng: 54.3314, - pop: 8799, - }, - { - lat: 37.1742, - lng: -113.6809, - pop: 8786, - }, - { - lat: 41.1244, - lng: 44.2819, - pop: 8700, - }, - { - lat: 40.283, - lng: 35.267, - pop: 8696, - }, - { - lat: 35.1268, - lng: -119.4243, - pop: 8730, - }, - { - lat: 32.5756, - lng: 59.7983, - pop: 8715, - }, - { - lat: 36.5433, - lng: -119.2914, - pop: 8701, - }, - { - lat: 37.1703, - lng: 34.6083, - pop: 8679, - }, - { - lat: 42.5658, - lng: 47.5631, - pop: 8627, - }, - { - lat: 40.1639, - lng: 39.8925, - pop: 8657, - }, - { - lat: 37.0728, - lng: 40.6519, - pop: 8551, - }, - { - lat: 44.7494, - lng: 43.4386, - pop: 8544, - }, - { - lat: 37.6494, - lng: 30.5339, - pop: 8537, - }, - { - lat: 40.1575, - lng: 33.7175, - pop: 8531, - }, - { - lat: 32.2631, - lng: 51.5622, - pop: 9924, - }, - { - lat: 32.0686, - lng: 61.8058, - pop: 10000, - }, - { - lat: 33.7711, - lng: 35.6858, - pop: 10000, - }, - { - lat: 30.4667, - lng: 53.45, - pop: 9776, - }, - { - lat: 36.5833, - lng: 31.8833, - pop: 9527, - }, - { - lat: 43.2167, - lng: 46.8667, - pop: 9458, - }, - { - lat: 40.9447, - lng: 47.9411, - pop: 9507, - }, - { - lat: 44.2659, - lng: 43.7562, - pop: 9516, - }, - { - lat: 30.4344, - lng: 63.3183, - pop: 9482, - }, - { - lat: 43.0997, - lng: 44.6317, - pop: 9217, - }, - { - lat: 32.1447, - lng: 48.3925, - pop: 9177, - }, - { - lat: 44.0817, - lng: 42.9606, - pop: 9079, - }, - { - lat: 43.42, - lng: 43.92, - pop: 9010, - }, - { - lat: 40.5808, - lng: 46.8503, - pop: 8830, - }, - { - lat: 32.2539, - lng: 50.5975, - pop: 8699, - }, - { - lat: 40.9533, - lng: 45.6792, - pop: 8702, - }, - { - lat: 43.3469, - lng: 44.6975, - pop: 8590, - }, - { - lat: 46.5467, - lng: 30.6306, - pop: 8558, - }, - { - lat: 43.1486, - lng: 44.7069, - pop: 8508, - }, - { - lat: 40.9053, - lng: 45.5564, - pop: 1155, - }, -] \ No newline at end of file diff --git a/frontend/website/src/unit/databases/groundunitdatabase.ts b/frontend/website/src/unit/databases/groundunitdatabase.ts deleted file mode 100644 index a33c70e2..00000000 --- a/frontend/website/src/unit/databases/groundunitdatabase.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { getApp } from "../.."; -import { GAME_MASTER } from "../../constants/constants"; -import { UnitDatabase } from "./unitdatabase" - -export class GroundUnitDatabase extends UnitDatabase { - constructor() { - super('api/databases/units/groundunitdatabase'); - } - - getSpawnPointsByName(name: string) { - if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns) - return 0; - - const blueprint = this.getByName(name); - if (blueprint?.cost != undefined) - return blueprint?.cost; - - if (blueprint?.era == "WW2") - return 20; - else if (blueprint?.era == "Early Cold War") - return 50; - else if (blueprint?.era == "Mid Cold War") - return 100; - else if (blueprint?.era == "Late Cold War") - return 200; - else if (blueprint?.era == "Modern") - return 400; - return 0; - } - - getCategory() { - return "GroundUnit"; - } -} - -export var groundUnitDatabase = new GroundUnitDatabase(); diff --git a/frontend/website/src/unit/databases/helicopterdatabase.ts b/frontend/website/src/unit/databases/helicopterdatabase.ts deleted file mode 100644 index 2d4841ac..00000000 --- a/frontend/website/src/unit/databases/helicopterdatabase.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { getApp } from "../.."; -import { GAME_MASTER } from "../../constants/constants"; -import { UnitDatabase } from "./unitdatabase" - -export class HelicopterDatabase extends UnitDatabase { - constructor() { - super('api/databases/units/helicopterdatabase'); - } - - getSpawnPointsByName(name: string) { - if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns) - return 0; - - const blueprint = this.getByName(name); - if (blueprint?.cost != undefined) - return blueprint?.cost; - - if (blueprint?.era == "WW2") - return 20; - else if (blueprint?.era == "Early Cold War") - return 50; - else if (blueprint?.era == "Mid Cold War") - return 100; - else if (blueprint?.era == "Late Cold War") - return 200; - else if (blueprint?.era == "Modern") - return 400; - return 0; - } - - getCategory() { - return "Helicopter"; - } -} - -export var helicopterDatabase = new HelicopterDatabase(); - diff --git a/frontend/website/src/unit/databases/navyunitdatabase.ts b/frontend/website/src/unit/databases/navyunitdatabase.ts deleted file mode 100644 index 23cd17a3..00000000 --- a/frontend/website/src/unit/databases/navyunitdatabase.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { getApp } from "../.."; -import { GAME_MASTER } from "../../constants/constants"; -import { UnitDatabase } from "./unitdatabase" - -export class NavyUnitDatabase extends UnitDatabase { - constructor() { - super('api/databases/units/navyunitdatabase'); - } - - getSpawnPointsByName(name: string) { - if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns) - return 0; - - const blueprint = this.getByName(name); - if (blueprint?.cost != undefined) - return blueprint?.cost; - - if (blueprint?.era == "WW2") - return 20; - else if (blueprint?.era == "Early Cold War") - return 50; - else if (blueprint?.era == "Mid Cold War") - return 100; - else if (blueprint?.era == "Late Cold War") - return 200; - else if (blueprint?.era == "Modern") - return 400; - return 0; - } - - getCategory() { - return "NavyUnit"; - } -} - -export var navyUnitDatabase = new NavyUnitDatabase(); diff --git a/frontend/website/src/unit/databases/unitdatabase.ts b/frontend/website/src/unit/databases/unitdatabase.ts deleted file mode 100644 index f2c695ce..00000000 --- a/frontend/website/src/unit/databases/unitdatabase.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { LatLng } from "leaflet"; -import { getApp } from "../.."; -import { GAME_MASTER } from "../../constants/constants"; -import { UnitBlueprint } from "../../interfaces"; - -export abstract class UnitDatabase { - blueprints: { [key: string]: UnitBlueprint } = {}; - #url: string; - - constructor(url: string = "") { - this.#url = url; - this.load(() => {}); - } - - load(callback: CallableFunction) { - if (this.#url !== "") { - var xhr = new XMLHttpRequest(); - xhr.open('GET', this.#url, true); - xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0"); - xhr.responseType = 'json'; - xhr.onload = () => { - var status = xhr.status; - if (status === 200) { - this.blueprints = xhr.response; - callback(); - } else { - console.error(`Error retrieving database from ${this.#url}`) - } - }; - xhr.send(); - } - } - - abstract getCategory(): string; - - /* Gets a specific blueprint by name */ - getByName(name: string) { - if (name in this.blueprints) - return this.blueprints[name]; - return null; - } - - /* Gets a specific blueprint by label */ - getByLabel(label: string) { - for (let unit in this.blueprints) { - if (this.blueprints[unit].label === label) - return this.blueprints[unit]; - } - return null; - } - - getBlueprints(includeDisabled: boolean = false) { - if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns) { - var filteredBlueprints: { [key: string]: UnitBlueprint } = {}; - for (let unit in this.blueprints) { - const blueprint = this.blueprints[unit]; - if (blueprint.enabled || includeDisabled) - filteredBlueprints[unit] = blueprint; - } - return filteredBlueprints; - } - else { - var filteredBlueprints: { [key: string]: UnitBlueprint } = {}; - for (let unit in this.blueprints) { - const blueprint = this.blueprints[unit]; - if ((blueprint.enabled || includeDisabled) && this.getSpawnPointsByName(blueprint.name) <= getApp().getMissionManager().getAvailableSpawnPoints() && - getApp().getMissionManager().getCommandModeOptions().eras.includes(blueprint.era) && - (!getApp().getMissionManager().getCommandModeOptions().restrictToCoalition || blueprint.coalition === getApp().getMissionManager().getCommandedCoalition() || blueprint.coalition === undefined)) { - filteredBlueprints[unit] = blueprint; - } - } - return filteredBlueprints; - } - } - - /* Returns a list of all possible roles in a database */ - getRoles() { - var roles: string[] = []; - var filteredBlueprints = this.getBlueprints(); - for (let unit in filteredBlueprints) { - var loadouts = filteredBlueprints[unit].loadouts; - if (loadouts) { - for (let loadout of loadouts) { - for (let role of loadout.roles) { - if (role !== "" && !roles.includes(role)) - roles.push(role); - } - } - } - } - return roles; - } - - /* Returns a list of all possible types in a database */ - getTypes(unitFilter?:CallableFunction) { - var filteredBlueprints = this.getBlueprints(); - var types: string[] = []; - for (let unit in filteredBlueprints) { - if ( typeof unitFilter === "function" && !unitFilter(filteredBlueprints[unit])) - continue; - var type = filteredBlueprints[unit].type; - if (type && type !== "" && !types.includes(type)) - types.push(type); - } - return types; - } - - /* Returns a list of all possible periods in a database */ - getEras() { - var filteredBlueprints = this.getBlueprints(); - var eras: string[] = []; - for (let unit in filteredBlueprints) { - var era = filteredBlueprints[unit].era; - if (era && era !== "" && !eras.includes(era)) - eras.push(era); - } - return eras; - } - - /* Get all blueprints by range */ - getByRange(range: string) { - var filteredBlueprints = this.getBlueprints(); - var unitswithrange = []; - var minRange = 0; - var maxRange = 0; - - if (range === "Short range") { - minRange = 0; - maxRange = 10000; - } - else if (range === "Medium range") { - minRange = 10000; - maxRange = 100000; - } - else { - minRange = 100000; - maxRange = 999999; - } - - for (let unit in filteredBlueprints) { - var engagementRange = filteredBlueprints[unit].engagementRange; - if (engagementRange !== undefined) { - if (engagementRange >= minRange && engagementRange < maxRange) { - unitswithrange.push(filteredBlueprints[unit]); - } - } - } - return unitswithrange; - } - - /* Get all blueprints by type */ - getByType(type: string) { - var filteredBlueprints = this.getBlueprints(); - var units = []; - for (let unit in filteredBlueprints) { - if (filteredBlueprints[unit].type === type) { - units.push(filteredBlueprints[unit]); - } - } - return units; - } - - /* Get all blueprints by role */ - getByRole(role: string) { - var filteredBlueprints = this.getBlueprints(); - var units = []; - for (let unit in filteredBlueprints) { - var loadouts = filteredBlueprints[unit].loadouts; - if (loadouts) { - for (let loadout of loadouts) { - if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase())) { - units.push(filteredBlueprints[unit]) - break; - } - } - } - } - return units; - } - - /* Get the names of all the loadouts for a specific unit and for a specific role */ - getLoadoutNamesByRole(name: string, role: string) { - var filteredBlueprints = this.getBlueprints(); - var loadoutsByRole = []; - var loadouts = filteredBlueprints[name].loadouts; - if (loadouts) { - for (let loadout of loadouts) { - if (loadout.roles.includes(role) || loadout.roles.includes("")) { - loadoutsByRole.push(loadout.name) - } - } - } - return loadoutsByRole; - } - - /* Get the livery names for a specific unit */ - getLiveryNamesByName(name: string) { - var liveries = this.blueprints[name].liveries; - if (liveries !== undefined) - return Object.values(liveries); - else - return []; - } - - /* Get the loadout content from the unit name and loadout name */ - getLoadoutByName(name: string, loadoutName: string) { - var loadouts = this.blueprints[name].loadouts; - if (loadouts) { - for (let loadout of loadouts) { - if (loadout.name === loadoutName) - return loadout; - } - } - return null; - } - - getSpawnPointsByLabel(label: string) { - var blueprint = this.getByLabel(label); - if (blueprint) - return this.getSpawnPointsByName(blueprint.name); - else - return Infinity; - } - - getSpawnPointsByName(name: string) { - return Infinity; - } - - getUnkownUnit(name: string): UnitBlueprint { - return { - name: name, - enabled: true, - coalition: 'neutral', - era: 'N/A', - label: name, - shortLabel: '' - } - } -} \ No newline at end of file diff --git a/frontend/website/src/unit/group.ts b/frontend/website/src/unit/group.ts deleted file mode 100644 index 38d703a3..00000000 --- a/frontend/website/src/unit/group.ts +++ /dev/null @@ -1,45 +0,0 @@ -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())}) - } -} \ No newline at end of file diff --git a/frontend/website/src/unit/importexport/unitdatafile.ts b/frontend/website/src/unit/importexport/unitdatafile.ts deleted file mode 100644 index 0cffc11e..00000000 --- a/frontend/website/src/unit/importexport/unitdatafile.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Dialog } from "../../dialog/dialog"; -import { createCheckboxOption } from "../../other/utils"; - -var categoryMap = { - "Aircraft": "Aircraft", - "Helicopter": "Helicopter", - "GroundUnit": "Ground units", - "NavyUnit": "Naval units" -} - -export abstract class UnitDataFile { - - protected data: any; - protected dialog!: Dialog; - - constructor() { } - - buildCategoryCoalitionTable() { - - const categories = this.#getCategoriesFromData(); - const coalitions = ["blue", "neutral", "red"]; - - let headersHTML: string = ``; - let matrixHTML: string = ``; - - categories.forEach((category: string, index) => { - matrixHTML += `${categoryMap[category as keyof typeof categoryMap]}`; - - coalitions.forEach((coalition: string) => { - if (index === 0) - headersHTML += `${coalition[0].toUpperCase() + coalition.substring(1)}`; - - const optionIsValid = this.data[category].hasOwnProperty(coalition); - let checkboxHTML = createCheckboxOption(``, category, optionIsValid, () => { }, { - "disabled": !optionIsValid, - "name": "category-coalition-selection", - "readOnly": !optionIsValid, - "value" : `${category}:${coalition}` - }).outerHTML; - - if (optionIsValid) - checkboxHTML = checkboxHTML.replace(`"checkbox"`, `"checkbox" checked`); // inner and outerHTML screw default checked up - - matrixHTML += `${checkboxHTML}`; - - }); - matrixHTML += ""; - }); - - const table = this.dialog.getElement().querySelector("table.categories-coalitions"); - - (table.tHead).innerHTML = ` ${headersHTML}`; - (table.querySelector(`tbody`)).innerHTML = matrixHTML; - - } - - #getCategoriesFromData() { - const categories = Object.keys(this.data); - categories.sort(); - return categories; - } - - getData() { - return this.data; - } -} \ No newline at end of file diff --git a/frontend/website/src/unit/importexport/unitdatafileexport.ts b/frontend/website/src/unit/importexport/unitdatafileexport.ts deleted file mode 100644 index 2a4f7a82..00000000 --- a/frontend/website/src/unit/importexport/unitdatafileexport.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { getApp } from "../.."; -import { Dialog } from "../../dialog/dialog"; -import { zeroAppend } from "../../other/utils"; -import { Unit } from "../unit"; -import { UnitDataFile } from "./unitdatafile"; - -export class UnitDataFileExport extends UnitDataFile { - - protected data!: any; - protected dialog: Dialog; - #element!: HTMLElement; - #filename: string = "export.json"; - - constructor(elementId: string) { - super(); - this.dialog = new Dialog(elementId); - this.#element = this.dialog.getElement(); - - this.#element.querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => { - this.#doExport(); - }); - } - - /** - * Show the form to start the export journey - */ - showForm(units: Unit[]) { - this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => { - el.classList.toggle("hide", el.getAttribute("data-on-error") === "show"); - }); - - const data: any = {}; - const unitCanBeExported = (unit: Unit) => !["Aircraft", "Helicopter"].includes(unit.getCategory()); - - units.filter((unit: Unit) => unit.getAlive() && unitCanBeExported(unit)).forEach((unit: Unit) => { - const category = unit.getCategory(); - const coalition = unit.getCoalition(); - - if (!data.hasOwnProperty(category)) { - data[category] = {}; - } - - if (!data[category].hasOwnProperty(coalition)) - data[category][coalition] = []; - - data[category][coalition].push(unit); - }); - - this.data = data; - this.buildCategoryCoalitionTable(); - this.dialog.show(); - - const date = new Date(); - this.#filename = `olympus_${getApp().getMissionManager().getTheatre().replace(/[^\w]/gi, "").toLowerCase()}_${date.getFullYear()}${zeroAppend(date.getMonth() + 1, 2)}${zeroAppend(date.getDate(), 2)}_${zeroAppend(date.getHours(), 2)}${zeroAppend(date.getMinutes(), 2)}${zeroAppend(date.getSeconds(), 2)}.json`; - var input = this.#element.querySelector("#export-filename") as HTMLInputElement; - input.onchange = (ev: Event) => { - this.#filename = (ev.currentTarget as HTMLInputElement).value; - } - if (input) - input.value = this.#filename; - } - - #doExport() { - - let selectedUnits: Unit[] = []; - - this.#element.querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`).forEach((checkbox: HTMLInputElement) => { - if (checkbox instanceof HTMLInputElement) { - const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition" - selectedUnits = selectedUnits.concat(this.data[category][coalition]); - } - }); - - if (selectedUnits.length === 0) { - alert("Please select at least one option for export."); - return; - } - - var unitsToExport: { [key: string]: any } = {}; - selectedUnits.forEach((unit: Unit) => { - var data: any = unit.getData(); - if (unit.getGroupName() in unitsToExport) - unitsToExport[unit.getGroupName()].push(data); - else - unitsToExport[unit.getGroupName()] = [data]; - }); - - - const a = document.createElement("a"); - const file = new Blob([JSON.stringify(unitsToExport)], { type: 'text/plain' }); - a.href = URL.createObjectURL(file); - - var filename = this.#filename; - if (!this.#filename.toLowerCase().endsWith(".json")) - filename += ".json"; - a.download = filename; - a.click(); - this.dialog.hide(); - } - -} \ No newline at end of file diff --git a/frontend/website/src/unit/importexport/unitdatafileimport.ts b/frontend/website/src/unit/importexport/unitdatafileimport.ts deleted file mode 100644 index 271a7462..00000000 --- a/frontend/website/src/unit/importexport/unitdatafileimport.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { getApp } from "../.."; -import { Dialog } from "../../dialog/dialog"; -import { UnitData } from "../../interfaces"; -import { ImportFileJSONSchemaValidator } from "../../schemas/schema"; -import { UnitDataFile } from "./unitdatafile"; - -export class UnitDataFileImport extends UnitDataFile { - - protected data!: any; - protected dialog: Dialog; - #fileData!: { [key: string]: UnitData[] }; - - constructor(elementId: string) { - super(); - this.dialog = new Dialog(elementId); - this.dialog.getElement().querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => { - this.#doImport(); - this.dialog.hide(); - }); - } - - #doImport() { - - let selectedCategories: any = {}; - const unitsManager = getApp().getUnitsManager(); - - this.dialog.getElement().querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`).forEach((checkbox: HTMLInputElement) => { - if (checkbox instanceof HTMLInputElement) { - const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition" - selectedCategories[category] = selectedCategories[category] || {}; - selectedCategories[category][coalition] = true; - } - }); - - for (const [groupName, groupData] of Object.entries(this.#fileData)) { - if (groupName === "" || groupData.length === 0 || !this.#unitGroupDataCanBeImported(groupData)) - continue; - - let { category, coalition } = groupData[0]; - - if (!selectedCategories.hasOwnProperty(category) - || !selectedCategories[category].hasOwnProperty(coalition) - || selectedCategories[category][coalition] !== true) - continue; - - let unitsToSpawn = groupData.filter((unitData: UnitData) => this.#unitDataCanBeImported(unitData)).map((unitData: UnitData) => { - return { unitType: unitData.name, location: unitData.position, liveryID: "", skill: "High" } - }); - - unitsManager.spawnUnits(category, unitsToSpawn, coalition, false); - } - } - - selectFile() { - var input = document.createElement("input"); - input.type = "file"; - input.addEventListener("change", (e: any) => { - var file = e.target.files[0]; - if (!file) { - return; - } - var reader = new FileReader(); - reader.onload = (e: any) => { - - try { - this.#fileData = JSON.parse(e.target.result); - - const validator = new ImportFileJSONSchemaValidator(); - if (!validator.validate(this.#fileData)) { - const errors = validator.getErrors().reduce((acc:any, error:any) => { - let errorString = error.instancePath.substring(1) + ": " + error.message; - if (error.params) { - const {allowedValues} = error.params; - if (allowedValues) - errorString += ": " + allowedValues.join(', '); - } - acc.push(errorString); - return acc; - }, [] as string[]); - this.#showFileDataErrors(errors); - } else { - this.#showForm(); - } - } catch(e:any) { - this.#showFileDataErrors([e]); - } - }; - reader.readAsText(file); - }) - input.click(); - } - - #showFileDataErrors( reasons:string[]) { - - this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => { - el.classList.toggle("hide", el.getAttribute("data-on-error") === "hide"); - }); - - const reasonsList = this.dialog.getElement().querySelector(".import-error-reasons"); - if (reasonsList instanceof HTMLElement) - reasonsList.innerHTML = `
  • ${reasons.join("
  • ")}
  • `; - - this.dialog.show(); - } - - #showForm() { - this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => { - el.classList.toggle("hide", el.getAttribute("data-on-error") === "show"); - }); - - const data: any = {}; - - for (const [group, units] of Object.entries(this.#fileData)) { - if (group === "" || units.length === 0) - continue; - - if (units.some((unit: UnitData) => !this.#unitDataCanBeImported(unit))) - continue; - - const category = units[0].category; - - if (!data.hasOwnProperty(category)) { - data[category] = {}; - } - - units.forEach((unit: UnitData) => { - if (!data[category].hasOwnProperty(unit.coalition)) - data[category][unit.coalition] = []; - - data[category][unit.coalition].push(unit); - }); - - } - - this.data = data; - this.buildCategoryCoalitionTable(); - this.dialog.show(); - } - - #unitDataCanBeImported(unitData: UnitData) { - return unitData.alive && this.#unitGroupDataCanBeImported([unitData]); - } - - #unitGroupDataCanBeImported(unitGroupData: UnitData[]) { - return unitGroupData.every((unitData: UnitData) => { - return !["Aircraft", "Helicopter"].includes(unitData.category); - }) && unitGroupData.some((unitData: UnitData) => unitData.alive); - } - -} \ No newline at end of file diff --git a/frontend/website/src/unit/unit.ts b/frontend/website/src/unit/unit.ts deleted file mode 100644 index ab8f8b9a..00000000 --- a/frontend/website/src/unit/unit.ts +++ /dev/null @@ -1,1729 +0,0 @@ -import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point } from 'leaflet'; -import { getApp } from '..'; -import { enumToCoalition, enumToEmissioNCountermeasure, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM, getGroundElevation, coalitionToEnum, nmToFt, nmToM, zeroAppend } from '../other/utils'; -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, GROUPING_ZOOM_TRANSITION, MAX_SHOTS_SCATTER, SHOTS_SCATTER_DEGREES, 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'; -import { ContextActionSet } from './contextactionset'; -import * as turf from "@turf/turf"; - -var pathIcon = new Icon({ - iconUrl: '/resources/theme/images/markers/marker-icon.png', - shadowUrl: '/resources/theme/images/markers/marker-shadow.png', - iconAnchor: [13, 41] -}); - -/** - * Unit class which controls unit behaviour - */ -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; - #coalition: string = "neutral"; - #country: number = 0; - #name: string = ""; - #unitName: string = ""; - #groupName: string = ""; - #state: string = states[0]; - #task: string = "" - #hasTask: boolean = false; - #position: LatLng = new LatLng(0, 0, 0); - #speed: number = 0; - #horizontalVelocity: number = 0; - #verticalVelocity: number = 0; - #heading: number = 0; - #track: number = 0; - #isActiveTanker: boolean = false; - #isActiveAWACS: boolean = false; - #onOff: boolean = true; - #followRoads: boolean = false; - #fuel: number = 0; - #desiredSpeed: number = 0; - #desiredSpeedType: string = "CAS"; - #desiredAltitude: number = 0; - #desiredAltitudeType: string = "ASL"; - #leaderID: number = 0; - #formationOffset: Offset = { - x: 0, - y: 0, - z: 0 - }; - #targetID: number = 0; - #targetPosition: LatLng = new LatLng(0, 0); - #ROE: string = ROEs[1]; - #reactionToThreat: string = reactionsToThreat[2]; - #emissionsCountermeasures: string = emissionsCountermeasures[2]; - #TACAN: TACAN = { - isOn: false, - XY: 'X', - callsign: 'TKR', - channel: 0 - }; - #radio: Radio = { - frequency: 124000000, - callsign: 1, - callsignNumber: 1 - }; - #generalSettings: GeneralSettings = { - prohibitAA: false, - prohibitAfterburner: false, - prohibitAG: false, - prohibitAirWpn: false, - prohibitJettison: false - }; - #ammo: Ammo[] = []; - #contacts: Contact[] = []; - #activePath: LatLng[] = []; - #isLeader: boolean = false; - #operateAs: string = "blue"; - #shotsScatter: number = 2; - #shotsIntensity: number = 2; - #health: number = 100; - - /* 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; - #waitingForDoubleClick: boolean = false; - #pathMarkers: Marker[] = []; - #pathPolyline: Polyline; - #contactsPolylines: Polyline[] = []; - #engagementCircle: RangeCircle; - #acquisitionCircle: RangeCircle; - #miniMapMarker: CircleMarker | null = null; - #targetPositionMarker: TargetMarker; - #targetPositionPolyline: Polyline; - #doubleClickTimer: number = 0; - #hotgroup: number | null = null; - #detectionMethods: number[] = []; - - /* Getters for backend driven data */ - getAlive() { return this.#alive }; - getHuman() { return this.#human }; - getControlled() { return this.#controlled }; - getCoalition() { return this.#coalition }; - getCountry() { return this.#country }; - getName() { return this.#name }; - getUnitName() { return this.#unitName }; - getGroupName() { return this.#groupName }; - getState() { return this.#state }; - getTask() { return this.#task }; - getHasTask() { return this.#hasTask }; - getPosition() { return this.#position }; - getSpeed() { return this.#speed }; - getHorizontalVelocity() { return this.#horizontalVelocity }; - getVerticalVelocity() { return this.#verticalVelocity }; - getHeading() { return this.#heading }; - getTrack() { return this.#track }; - getIsActiveAWACS() { return this.#isActiveAWACS }; - getIsActiveTanker() { return this.#isActiveTanker }; - getOnOff() { return this.#onOff }; - getFollowRoads() { return this.#followRoads }; - getFuel() { return this.#fuel }; - getDesiredSpeed() { return this.#desiredSpeed }; - getDesiredSpeedType() { return this.#desiredSpeedType }; - getDesiredAltitude() { return this.#desiredAltitude }; - getDesiredAltitudeType() { return this.#desiredAltitudeType }; - getLeaderID() { return this.#leaderID }; - getFormationOffset() { return this.#formationOffset }; - getTargetID() { return this.#targetID }; - getTargetPosition() { return this.#targetPosition }; - getROE() { return this.#ROE }; - getReactionToThreat() { return this.#reactionToThreat }; - getEmissionsCountermeasures() { return this.#emissionsCountermeasures }; - getTACAN() { return this.#TACAN }; - getRadio() { return this.#radio }; - getGeneralSettings() { return this.#generalSettings }; - getAmmo() { return this.#ammo }; - getContacts() { return this.#contacts }; - getActivePath() { return this.#activePath }; - getIsLeader() { return this.#isLeader }; - getOperateAs() { return this.#operateAs }; - getShotsScatter() { return this.#shotsScatter }; - getShotsIntensity() { return this.#shotsIntensity }; - getHealth() { return this.#health }; - - static getConstructor(type: string) { - if (type === "GroundUnit") return GroundUnit; - if (type === "Aircraft") return Aircraft; - if (type === "Helicopter") return Helicopter; - if (type === "NavyUnit") return NavyUnit; - } - - constructor(ID: number) { - super(new LatLng(0, 0), { riseOnHover: true, keyboard: false }); - - this.ID = ID; - - 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 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)); - this.on('mouseover', () => { - if (this.belongsToCommandedCoalition()) { - this.setHighlighted(true); - document.dispatchEvent(new CustomEvent("unitMouseover", { detail: this })); - } - }); - this.on('mouseout', () => { - this.setHighlighted(false); - document.dispatchEvent(new CustomEvent("unitMouseout", { detail: this })); - }); - getApp().getMap().on("zoomend", (e: any) => { this.#onZoom(e); }) - - /* Deselect units if they are hidden */ - document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { - this.#updateMarker(); - this.setSelected(this.getSelected() && !this.getHidden()); - }); - - document.addEventListener("toggleMarkerVisibility", (ev: CustomEventInit) => { - this.#updateMarker(); - this.setSelected(this.getSelected() && !this.getHidden()); - }); - - /* Update the marker when the options change */ - document.addEventListener("mapOptionsChanged", (ev: CustomEventInit) => { - this.#updateMarker(); - - /* 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 (this.getSelected()) - this.drawLines(); - }); - } - - /********************** 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 - * - */ - abstract appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null): void; - - /** - * - * @returns string containing the marker category - */ - abstract getMarkerCategory(): string; - - /** - * - * @returns string containing the default marker - */ - abstract getDefaultMarker(): string; - - /** Get the category but for display use - for the user. (i.e. has spaces in it) - * - * @returns string - */ - getCategoryLabel() { - return ((GROUND_UNIT_AIR_DEFENCE_REGEX.test(this.getType())) ? "Air Defence" : this.getCategory()).replace(/([a-z])([A-Z])/g, "$1 $2"); - } - - /********************** 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(); - switch (datumIndex) { - case DataIndexes.category: dataExtractor.extractString(); break; - 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: 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; - case DataIndexes.groupName: this.#groupName = dataExtractor.extractString(); updateMarker = true; break; - case DataIndexes.state: this.#state = enumToState(dataExtractor.extractUInt8()); updateMarker = true; break; - case DataIndexes.task: this.#task = dataExtractor.extractString(); break; - case DataIndexes.hasTask: this.#hasTask = dataExtractor.extractBool(); break; - case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break; - case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break; - case DataIndexes.horizontalVelocity: this.#horizontalVelocity = dataExtractor.extractFloat64(); break; - case DataIndexes.verticalVelocity: this.#verticalVelocity = dataExtractor.extractFloat64(); break; - case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break; - case DataIndexes.track: this.#track = dataExtractor.extractFloat64(); updateMarker = true; break; - case DataIndexes.isActiveTanker: this.#isActiveTanker = dataExtractor.extractBool(); break; - case DataIndexes.isActiveAWACS: this.#isActiveAWACS = dataExtractor.extractBool(); break; - case DataIndexes.onOff: this.#onOff = dataExtractor.extractBool(); break; - case DataIndexes.followRoads: this.#followRoads = dataExtractor.extractBool(); break; - case DataIndexes.fuel: this.#fuel = dataExtractor.extractUInt16(); break; - case DataIndexes.desiredSpeed: this.#desiredSpeed = dataExtractor.extractFloat64(); break; - case DataIndexes.desiredSpeedType: this.#desiredSpeedType = dataExtractor.extractBool() ? "GS" : "CAS"; break; - case DataIndexes.desiredAltitude: this.#desiredAltitude = dataExtractor.extractFloat64(); break; - case DataIndexes.desiredAltitudeType: this.#desiredAltitudeType = dataExtractor.extractBool() ? "AGL" : "ASL"; break; - case DataIndexes.leaderID: this.#leaderID = dataExtractor.extractUInt32(); break; - case DataIndexes.formationOffset: this.#formationOffset = dataExtractor.extractOffset(); break; - case DataIndexes.targetID: this.#targetID = dataExtractor.extractUInt32(); break; - case DataIndexes.targetPosition: this.#targetPosition = dataExtractor.extractLatLng(); break; - case DataIndexes.ROE: this.#ROE = enumToROE(dataExtractor.extractUInt8()); break; - case DataIndexes.reactionToThreat: this.#reactionToThreat = enumToReactionToThreat(dataExtractor.extractUInt8()); break; - case DataIndexes.emissionsCountermeasures: this.#emissionsCountermeasures = enumToEmissioNCountermeasure(dataExtractor.extractUInt8()); break; - case DataIndexes.TACAN: this.#TACAN = dataExtractor.extractTACAN(); break; - case DataIndexes.radio: this.#radio = dataExtractor.extractRadio(); break; - case DataIndexes.generalSettings: this.#generalSettings = dataExtractor.extractGeneralSettings(); break; - 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(); 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 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().getCenteredOnUnit() === this) - document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); - } - - /** Get unit data collated into an object - * - * @returns object populated by unit information which can also be retrieved using getters - */ - getData(): UnitData { - return { - category: this.getCategory(), - categoryDisplayName: this.getCategoryLabel(), - ID: this.ID, - alive: this.#alive, - human: this.#human, - controlled: this.#controlled, - coalition: this.#coalition, - country: this.#country, - name: this.#name, - unitName: this.#unitName, - groupName: this.#groupName, - state: this.#state, - task: this.#task, - hasTask: this.#hasTask, - position: this.#position, - speed: this.#speed, - horizontalVelocity: this.#horizontalVelocity, - verticalVelocity: this.#verticalVelocity, - heading: this.#heading, - track: this.#track, - isActiveTanker: this.#isActiveTanker, - isActiveAWACS: this.#isActiveAWACS, - onOff: this.#onOff, - followRoads: this.#followRoads, - fuel: this.#fuel, - desiredSpeed: this.#desiredSpeed, - desiredSpeedType: this.#desiredSpeedType, - desiredAltitude: this.#desiredAltitude, - desiredAltitudeType: this.#desiredAltitudeType, - leaderID: this.#leaderID, - formationOffset: this.#formationOffset, - targetID: this.#targetID, - targetPosition: this.#targetPosition, - ROE: this.#ROE, - reactionToThreat: this.#reactionToThreat, - emissionsCountermeasures: this.#emissionsCountermeasures, - TACAN: this.#TACAN, - radio: this.#radio, - generalSettings: this.#generalSettings, - ammo: this.#ammo, - contacts: this.#contacts, - activePath: this.#activePath, - isLeader: this.#isLeader, - operateAs: this.#operateAs, - shotsScatter: this.#shotsScatter, - shotsIntensity: this.#shotsIntensity, - health: this.#health - } - } - - /** Get a database of information also in this unit's category - * - * @returns UnitDatabase - */ - getDatabase(): UnitDatabase | null { - return getUnitDatabaseByCategory(this.getMarkerCategory()); - } - - /** Set the unit as alive or dead - * - * @param newAlive (boolean) true = alive, false = dead - */ - setAlive(newAlive: boolean) { - if (newAlive != this.#alive) - document.dispatchEvent(new CustomEvent("unitDeath", { detail: this })); - this.#alive = newAlive; - } - - /** Set the unit as user-selected - * - * @param selected (boolean) - */ - setSelected(selected: boolean) { - /* 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; - - /* 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(); - } - else { - this.#clearContacts(); - this.#clearPath(); - this.#clearTargetPosition(); - } - - /* 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 { - this.#updateMarker(); - } - } - - /* 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 })); - } - } - } - - /** Is this unit selected? - * - * @returns boolean - */ - getSelected() { - return this.#selected; - } - - /** Set the number of the hotgroup to which the unit belongss - * - * @param hotgroup (number) - */ - setHotgroup(hotgroup: number | null) { - this.#hotgroup = hotgroup; - this.#updateMarker(); - } - - /** Get the unit's hotgroup number - * - * @returns number - */ - getHotgroup() { - return this.#hotgroup; - } - - /** Set the unit as highlighted - * - * @param highlighted (boolean) - */ - setHighlighted(highlighted: boolean) { - 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)); - } - } - - /** Get whether the unit is highlighted or not - * - * @returns boolean - */ - getHighlighted() { - return this.#highlighted; - } - - /** Get the other members of the group which this unit is in - * - * @returns Unit[] - */ - getGroupMembers() { - 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 - * - * @returns boolean - */ - belongsToCommandedCoalition() { - return (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER && getApp().getMissionManager().getCommandedCoalition() !== this.#coalition) ? false : true; - } - - getType() { - return ""; - } - - getSpawnPoints() { - return this.getDatabase()?.getSpawnPointsByName(this.getName()); - } - - getDatabaseEntry() { - return this.getDatabase()?.getByName(this.#name) ?? this.getDatabase()?.getUnkownUnit(this.getName()); - } - - 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; - } - - isControlledByDCS() { - return this.getControlled() === false && this.getHuman() === false; - } - - isControlledByOlympus() { - return this.getControlled() === true; - } - - /********************** Icon *************************/ - createIcon(): void { - /* Set the icon */ - var icon = new DivIcon({ - className: 'leaflet-unit-icon', - iconAnchor: [25, 25], - iconSize: [50, 50], - }); - this.setIcon(icon); - - /* Create the base element */ - var el = document.createElement("div"); - el.classList.add("unit"); - el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`); - el.setAttribute("data-coalition", this.#coalition); - - var iconOptions = this.getIconOptions(); - - /* Generate and append elements depending on active options */ - /* Velocity vector */ - if (iconOptions.showVvi) { - var vvi = document.createElement("div"); - vvi.classList.add("unit-vvi"); - vvi.toggleAttribute("data-rotate-to-heading"); - el.append(vvi); - } - - /* Hotgroup indicator */ - if (iconOptions.showHotgroup) { - var hotgroup = document.createElement("div"); - hotgroup.classList.add("unit-hotgroup"); - var hotgroupId = document.createElement("div"); - hotgroupId.classList.add("unit-hotgroup-id"); - hotgroup.appendChild(hotgroupId); - el.append(hotgroup); - } - - /* Main icon */ - if (iconOptions.showUnitIcon) { - var unitIcon = document.createElement("div"); - unitIcon.classList.add("unit-icon"); - var img = document.createElement("img"); - - /* 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))) - marker = this.getDatabaseEntry()?.markerFile ?? this.getDefaultMarker(); - else - 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 */ - if (iconOptions.showState) { - var state = document.createElement("div"); - state.classList.add("unit-state"); - el.appendChild(state); - } - - /* Short label */ - if (iconOptions.showShortLabel) { - var shortLabel = document.createElement("div"); - shortLabel.classList.add("unit-short-label"); - shortLabel.innerText = this.getDatabaseEntry()?.shortLabel || ""; - el.append(shortLabel); - } - - /* Fuel indicator */ - if (iconOptions.showFuel) { - var fuelIndicator = document.createElement("div"); - fuelIndicator.classList.add("unit-fuel"); - var fuelLevel = document.createElement("div"); - fuelLevel.classList.add("unit-fuel-level"); - fuelIndicator.appendChild(fuelLevel); - el.append(fuelIndicator); - } - - /* 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"); - for (let i = 0; i <= 3; i++) - ammoIndicator.appendChild(document.createElement("div")); - el.append(ammoIndicator); - } - - /* Unit summary */ - if (iconOptions.showSummary) { - var summary = document.createElement("div"); - summary.classList.add("unit-summary"); - var callsign = document.createElement("div"); - callsign.classList.add("unit-callsign"); - callsign.innerText = this.#unitName; - var altitude = document.createElement("div"); - altitude.classList.add("unit-altitude"); - var speed = document.createElement("div"); - speed.classList.add("unit-speed"); - if (iconOptions.showCallsign) summary.appendChild(callsign); - summary.appendChild(altitude); - summary.appendChild(speed); - el.appendChild(summary); - } - - this.getElement()?.appendChild(el); - } - - /********************** Visibility *************************/ - updateVisibility() { - const hiddenTypes = getApp().getMap().getHiddenTypes(); - var hidden = ( - /* Hide the unit if it is a human and humans are hidden */ - (this.getHuman() && hiddenTypes.includes("human")) || - /* Hide the unit if it is DCS-controlled and DCS controlled units are hidden */ - (this.isControlledByDCS() && hiddenTypes.includes("dcs")) || - /* Hide the unit if it is Olympus-controlled and Olympus-controlled units are hidden */ - (this.isControlledByOlympus() && hiddenTypes.includes("olympus")) || - /* 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))) || - /* 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.getSelected() && this.getCategory() == "GroundUnit" && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION && - (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))); - - /* Force dead units to be hidden */ - this.setHidden(hidden || !this.getAlive()); - } - - setHidden(hidden: boolean) { - this.#hidden = hidden; - - /* Add the marker if not present */ - if (!getApp().getMap().hasLayer(this) && !this.getHidden()) { - if (getApp().getMap().isZooming()) - this.once("zoomend", () => { this.addTo(getApp().getMap()) }) - else - this.addTo(getApp().getMap()); - } - - /* Hide the marker if necessary*/ - if (getApp().getMap().hasLayer(this) && this.getHidden()) { - 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()) - this.#drawRanges(); - else - this.once("zoomend", () => { this.#drawRanges(); }) - } else { - this.#clearRanges(); - } - } - - getHidden() { - return this.#hidden; - } - - setDetectionMethods(newDetectionMethods: number[]) { - if (!this.belongsToCommandedCoalition()) { - /* Check if the detection methods of this unit have changed */ - if (this.#detectionMethods.length !== newDetectionMethods.length || this.getDetectionMethods().some(value => !newDetectionMethods.includes(value))) { - /* Force a redraw of the unit to reflect the new status of the detection methods */ - this.setHidden(true); - this.#detectionMethods = newDetectionMethods; - this.#updateMarker(); - } - } - } - - getDetectionMethods() { - return this.#detectionMethods; - } - - getLeader() { - return getApp().getUnitsManager().getUnitByID(this.#leaderID); - } - - canFulfillRole(roles: string | string[]) { - if (typeof (roles) === "string") - roles = [roles]; - - var loadouts = this.getDatabaseEntry()?.loadouts; - if (loadouts) { - return loadouts.some((loadout: LoadoutBlueprint) => { - return (roles as string[]).some((role: string) => { return loadout.roles.includes(role) }); - }); - } else - return false; - } - - isInViewport() { - return getApp().getMap().getBounds().contains(this.getPosition()); - } - - canTargetPoint() { - return this.getDatabaseEntry()?.canTargetPoint === true; - } - - canRearm() { - return this.getDatabaseEntry()?.canRearm === true; - } - - canAAA() { - return this.getDatabaseEntry()?.canAAA === true; - } - - isIndirectFire() { - return this.getDatabaseEntry()?.indirectFire === true; - } - - isTanker() { - return this.canFulfillRole("Tanker"); - } - - isAWACS() { - return this.canFulfillRole("AWACS"); - } - - /********************** Unit commands *************************/ - addDestination(latlng: L.LatLng) { - if (!this.#human) { - var path: any = {}; - if (this.#activePath.length > 0) { - path = this.#activePath; - path[(Object.keys(path).length).toString()] = latlng; - } - else { - path = [latlng]; - } - getApp().getServerManager().addDestination(this.ID, path); - } - } - - clearDestinations() { - if (!this.#human) - this.#activePath = []; - } - - attackUnit(targetID: number) { - /* Units can't attack themselves */ - if (!this.#human) - if (this.ID != targetID) - getApp().getServerManager().attackUnit(this.ID, targetID); - } - - followUnit(targetID: number, offset: { "x": number, "y": number, "z": number }) { - /* Units can't follow themselves */ - if (!this.#human) - if (this.ID != targetID) - getApp().getServerManager().followUnit(this.ID, targetID, offset); - } - - landAt(latlng: LatLng) { - if (!this.#human) - getApp().getServerManager().landAt(this.ID, latlng); - } - - changeSpeed(speedChange: string) { - if (!this.#human) - getApp().getServerManager().changeSpeed(this.ID, speedChange); - } - - changeAltitude(altitudeChange: string) { - if (!this.#human) - getApp().getServerManager().changeAltitude(this.ID, altitudeChange); - } - - setSpeed(speed: number) { - if (!this.#human) - getApp().getServerManager().setSpeed(this.ID, speed); - } - - setSpeedType(speedType: string) { - if (!this.#human) - getApp().getServerManager().setSpeedType(this.ID, speedType); - } - - setAltitude(altitude: number) { - if (!this.#human) - getApp().getServerManager().setAltitude(this.ID, altitude); - } - - setAltitudeType(altitudeType: string) { - if (!this.#human) - getApp().getServerManager().setAltitudeType(this.ID, altitudeType); - } - - setROE(ROE: string) { - if (!this.#human) - getApp().getServerManager().setROE(this.ID, ROE); - } - - setReactionToThreat(reactionToThreat: string) { - if (!this.#human) - getApp().getServerManager().setReactionToThreat(this.ID, reactionToThreat); - } - - setEmissionsCountermeasures(emissionCountermeasure: string) { - if (!this.#human) - getApp().getServerManager().setEmissionsCountermeasures(this.ID, emissionCountermeasure); - } - - setOnOff(onOff: boolean) { - if (!this.#human) - getApp().getServerManager().setOnOff(this.ID, onOff); - } - - setFollowRoads(followRoads: boolean) { - if (!this.#human) - getApp().getServerManager().setFollowRoads(this.ID, followRoads); - } - - setOperateAs(operateAs: string) { - if (!this.#human) - getApp().getServerManager().setOperateAs(this.ID, coalitionToEnum(operateAs)); - } - - delete(explosion: boolean, explosionType: string, immediate: boolean) { - getApp().getServerManager().deleteUnit(this.ID, explosion, explosionType, immediate); - } - - refuel() { - if (!this.#human) - getApp().getServerManager().refuel(this.ID); - } - - setAdvancedOptions(isActiveTanker: boolean, isActiveAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) { - if (!this.#human) - getApp().getServerManager().setAdvacedOptions(this.ID, isActiveTanker, isActiveAWACS, TACAN, radio, generalSettings); - } - - bombPoint(latlng: LatLng) { - getApp().getServerManager().bombPoint(this.ID, latlng); - } - - carpetBomb(latlng: LatLng) { - getApp().getServerManager().carpetBomb(this.ID, latlng); - } - - bombBuilding(latlng: LatLng) { - getApp().getServerManager().bombBuilding(this.ID, latlng); - } - - fireAtArea(latlng: LatLng) { - getApp().getServerManager().fireAtArea(this.ID, latlng); - } - - simulateFireFight(latlng: LatLng, targetGroundElevation: number | null) { - getGroundElevation(this.getPosition(), (response: string) => { - var unitGroundElevation: number | null = null; - try { - unitGroundElevation = parseFloat(response); - } catch { - console.log("Simulate fire fight: could not retrieve ground elevation") - } - - /* DCS and SRTM altitude data is not exactly the same so to minimize error we use SRTM only to compute relative elevation difference */ - var altitude = this.getPosition().alt; - if (altitude !== undefined && targetGroundElevation !== null && unitGroundElevation !== null) - getApp().getServerManager().simulateFireFight(this.ID, latlng, altitude + targetGroundElevation - unitGroundElevation); - }); - } - - // TODO: Remove coalition - scenicAAA() { - var coalition = "neutral"; - if (this.getCoalition() === "red") - coalition = "blue"; - else if (this.getCoalition() == "blue") - coalition = "red"; - getApp().getServerManager().scenicAAA(this.ID, coalition); - } - - // TODO: Remove coalition - missOnPurpose() { - var coalition = "neutral"; - if (this.getCoalition() === "red") - coalition = "blue"; - else if (this.getCoalition() == "blue") - coalition = "red"; - getApp().getServerManager().missOnPurpose(this.ID, coalition); - } - - landAtPoint(latlng: LatLng) { - getApp().getServerManager().landAtPoint(this.ID, latlng); - } - - setShotsScatter(shotsScatter: number) { - if (!this.#human) - getApp().getServerManager().setShotsScatter(this.ID, shotsScatter); - } - - setShotsIntensity(shotsIntensity: number) { - if (!this.#human) - getApp().getServerManager().setShotsIntensity(this.ID, shotsIntensity); - } - - /***********************************************/ - onAdd(map: Map): this { - super.onAdd(map); - return this; - } - - onGroupChanged(member: Unit) { - this.#redrawMarker(); - } - - showFollowOptions(units: Unit[]) { - var contextActionSet = new ContextActionSet(); - - contextActionSet.addContextAction(this, 'trail', "Trail", "Follow unit in trail formation", () => this.applyFollowOptions('trail', units)); - contextActionSet.addContextAction(this, 'echelon-lh', "Echelon (LH)", "Follow unit in echelon left formation", () => this.applyFollowOptions('echelon-lh', units)); - contextActionSet.addContextAction(this, 'echelon-rh', "Echelon (RH)", "Follow unit in echelon right formation", () => this.applyFollowOptions('echelon-rh', units)); - contextActionSet.addContextAction(this, 'line-abreast-lh', "Line abreast (LH)", "Follow unit in line abreast left formation", () => this.applyFollowOptions('line-abreast-lh', units)); - contextActionSet.addContextAction(this, 'line-abreast-rh', "Line abreast (RH)", "Follow unit in line abreast right formation", () => this.applyFollowOptions('line-abreast-rh', units)); - contextActionSet.addContextAction(this, 'front', "Front", "Fly in front of unit", () => this.applyFollowOptions('front', units)); - contextActionSet.addContextAction(this, 'diamond', "Diamond", "Follow unit in diamond formation", () => this.applyFollowOptions('diamond', units)); - contextActionSet.addContextAction(this, 'custom', "Custom", "Set a custom formation position", () => this.applyFollowOptions('custom', units)); - - getApp().getMap().getUnitContextMenu().setContextActions(contextActionSet); - getApp().getMap().showUnitContextMenu(); - } - - applyFollowOptions(formation: string, units: Unit[]) { - if (formation === "custom") { - document.getElementById("custom-formation-dialog")?.classList.remove("hide"); - document.addEventListener("applyCustomFormation", () => { - var dialog = document.getElementById("custom-formation-dialog"); - if (dialog) { - dialog.classList.add("hide"); - var clock = 1; - while (clock < 8) { - if ((dialog.querySelector(`#formation-${clock}`)).checked) - break - clock++; - } - var angleDeg = 360 - (clock - 1) * 45; - var angleRad = deg2rad(angleDeg); - var distance = ftToM(parseInt((dialog.querySelector(`#distance`)?.querySelector("input")).value)); - var upDown = ftToM(parseInt((dialog.querySelector(`#up-down`)?.querySelector("input")).value)); - - // X: front-rear, positive front - // Y: top-bottom, positive top - // Z: left-right, positive right - var x = distance * Math.cos(angleRad); - var y = upDown; - var z = distance * Math.sin(angleRad); - - getApp().getUnitsManager().followUnit(this.ID, { "x": x, "y": y, "z": z }, undefined, units); - } - }); - } - else { - getApp().getUnitsManager().followUnit(this.ID, undefined, formation, units); - } - } - - /***********************************************/ - #onClick(e: any) { - /* Exit if we were waiting for a doubleclick */ - if (this.#waitingForDoubleClick) { - return; - } - - /* We'll wait for a doubleclick */ - this.#waitingForDoubleClick = true; - this.#doubleClickTimer = window.setTimeout(() => { - /* 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) - getApp().getUnitsManager().deselectAllUnits(); - - this.setSelected(!this.getSelected()); - } - } - - /* No longer waiting for a doubleclick */ - this.#waitingForDoubleClick = false; - }, 200); - } - - #onDoubleClick(e: any) { - /* Let single clicks work again */ - this.#waitingForDoubleClick = false; - clearTimeout(this.#doubleClickTimer); - - /* 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()) - unitsManager.selectUnit(unit.ID, false); - }); - } - - #onContextMenu(e: any) { - var contextActionSet = new ContextActionSet(); - - var units = getApp().getUnitsManager().getSelectedUnits(); - if (!units.includes(this)) - units.push(this); - - units.forEach((unit: Unit) => { - unit.appendContextActions(contextActionSet, this, null); - }) - - if (Object.keys(contextActionSet.getContextActions()).length > 0) { - getApp().getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng); - getApp().getMap().getUnitContextMenu().setContextActions(contextActionSet); - } - } - - #updateMarker() { - this.updateVisibility(); - - /* Draw the minimap marker */ - var drawMiniMapMarker = (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))); - if (this.#alive && drawMiniMapMarker) { - if (this.#miniMapMarker == null) { - this.#miniMapMarker = new CircleMarker(new LatLng(this.#position.lat, this.#position.lng), { radius: 0.5 }); - if (this.#coalition == "neutral") - this.#miniMapMarker.setStyle({ color: "#CFD9E8" }); - else if (this.#coalition == "red") - this.#miniMapMarker.setStyle({ color: "#ff5858" }); - else - this.#miniMapMarker.setStyle({ color: "#247be2" }); - this.#miniMapMarker.addTo(getApp().getMap().getMiniMapLayerGroup()); - this.#miniMapMarker.bringToBack(); - } - else { - if (this.#miniMapMarker.getLatLng().lat !== this.getPosition().lat || this.#miniMapMarker.getLatLng().lng !== this.getPosition().lng) { - this.#miniMapMarker.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); - this.#miniMapMarker.bringToBack(); - } - } - } - else { - if (this.#miniMapMarker != null && getApp().getMap().getMiniMapLayerGroup().hasLayer(this.#miniMapMarker)) { - getApp().getMap().getMiniMapLayerGroup().removeLayer(this.#miniMapMarker); - this.#miniMapMarker = null; - } - } - - /* Draw the marker */ - if (!this.getHidden()) { - if (this.getLatLng().lat !== this.#position.lat || this.getLatLng().lng !== this.#position.lng) { - this.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); - } - - var element = this.getElement(); - if (element != null) { - /* Draw the velocity vector */ - element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.#speed / 5}px;`); - - /* Set fuel data */ - 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); - - /* Set current unit state */ - if (this.#human) { // Unit is human - element.querySelector(".unit")?.setAttribute("data-state", "human"); - } - 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) { - element.querySelector(".unit")?.setAttribute("data-state", "no-task"); - } - else { // Unit is under Olympus control - if (this.#onOff) { - if (this.#isActiveTanker) - element.querySelector(".unit")?.setAttribute("data-state", "tanker"); - else if (this.#isActiveAWACS) - element.querySelector(".unit")?.setAttribute("data-state", "AWACS"); - else - element.querySelector(".unit")?.setAttribute("data-state", this.#state.toLowerCase()); - } - else { - element.querySelector(".unit")?.setAttribute("data-state", "off"); - } - } - - /* Set altitude and speed */ - if (element.querySelector(".unit-altitude")) - (element.querySelector(".unit-altitude")).innerText = "FL" + zeroAppend(Math.floor(mToFt(this.#position.alt as number) / 100), 3); - if (element.querySelector(".unit-speed")) - (element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.#speed))) + "GS"; - - /* Rotate elements according to heading */ - element.querySelectorAll("[data-rotate-to-heading]").forEach(el => { - const headingDeg = rad2deg(this.#track); - let currentStyle = el.getAttribute("style") || ""; - el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`); - }); - - /* Turn on ammo indicators */ - var hasFox1 = element.querySelector(".unit")?.hasAttribute("data-has-fox-1"); - var hasFox2 = element.querySelector(".unit")?.hasAttribute("data-has-fox-2"); - var hasFox3 = element.querySelector(".unit")?.hasAttribute("data-has-fox-3"); - var hasOtherAmmo = element.querySelector(".unit")?.hasAttribute("data-has-other-ammo"); - - var newHasFox1 = false; - var newHasFox2 = false; - var newHasFox3 = false; - var newHasOtherAmmo = false; - Object.values(this.#ammo).forEach((ammo: Ammo) => { - if (ammo.category == 1 && ammo.missileCategory == 1) { - if (ammo.guidance == 4 || ammo.guidance == 5) - newHasFox1 = true; - else if (ammo.guidance == 2) - newHasFox2 = true; - else if (ammo.guidance == 3) - newHasFox3 = true; - } - else - newHasOtherAmmo = true; - }); - - if (hasFox1 != newHasFox1) element.querySelector(".unit")?.toggleAttribute("data-has-fox-1", newHasFox1); - if (hasFox2 != newHasFox2) element.querySelector(".unit")?.toggleAttribute("data-has-fox-2", newHasFox2); - if (hasFox3 != newHasFox3) element.querySelector(".unit")?.toggleAttribute("data-has-fox-3", newHasFox3); - if (hasOtherAmmo != newHasOtherAmmo) element.querySelector(".unit")?.toggleAttribute("data-has-other-ammo", newHasOtherAmmo); - - /* Draw the hotgroup element */ - element.querySelector(".unit")?.toggleAttribute("data-is-in-hotgroup", this.#hotgroup != null); - if (this.#hotgroup) { - const hotgroupEl = element.querySelector(".unit-hotgroup-id") as HTMLElement; - if (hotgroupEl) - hotgroupEl.innerText = String(this.#hotgroup); - } - } - - /* Set vertical offset for altitude stacking */ - var pos = getApp().getMap().latLngToLayerPoint(this.getLatLng()).round(); - this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0)); - } - } - - #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 = []; - points.push(new LatLng(this.#position.lat, this.#position.lng)); - - /* Add markers if missing */ - while (this.#pathMarkers.length < Object.keys(this.#activePath).length) { - var marker = new Marker([0, 0], { icon: pathIcon }).addTo(getApp().getMap()); - this.#pathMarkers.push(marker); - } - - /* Remove markers if too many */ - while (this.#pathMarkers.length > Object.keys(this.#activePath).length) { - getApp().getMap().removeLayer(this.#pathMarkers[this.#pathMarkers.length - 1]); - this.#pathMarkers.splice(this.#pathMarkers.length - 1, 1) - } - - /* Update the position of the existing markers (to avoid creating markers uselessly) */ - for (let WP in this.#activePath) { - var destination = this.#activePath[WP]; - this.#pathMarkers[parseInt(WP)].setLatLng([destination.lat, destination.lng]); - points.push(new LatLng(destination.lat, destination.lng)); - this.#pathPolyline.setLatLngs(points); - } - - if (points.length == 1) - this.#clearPath(); - } - else { - this.#clearPath(); - } - } - - #clearPath() { - if (this.#pathPolyline.getLatLngs().length != 0) { - for (let WP in this.#pathMarkers) { - getApp().getMap().removeLayer(this.#pathMarkers[WP]); - } - this.#pathMarkers = []; - this.#pathPolyline.setLatLngs([]); - } - } - - #drawContacts() { - this.#clearContacts(); - if (getApp().getMap().getVisibilityOptions()[SHOW_UNIT_CONTACTS]) { - for (let index in this.#contacts) { - var contactData = this.#contacts[index]; - var contact: Unit | Weapon | null; - - if (contactData.ID in getApp().getUnitsManager().getUnits()) - contact = getApp().getUnitsManager().getUnitByID(contactData.ID); - else - contact = getApp().getWeaponsManager().getWeaponByID(contactData.ID); - - if (contact != null && contact.getAlive()) { - var startLatLng = new LatLng(this.#position.lat, this.#position.lng); - var endLatLng: LatLng; - if (contactData.detectionMethod === RWR) { - var bearingToContact = bearing(this.#position.lat, this.#position.lng, contact.getPosition().lat, contact.getPosition().lng); - var startXY = getApp().getMap().latLngToContainerPoint(startLatLng); - var endX = startXY.x + 80 * Math.sin(deg2rad(bearingToContact)); - var endY = startXY.y - 80 * Math.cos(deg2rad(bearingToContact)); - endLatLng = getApp().getMap().containerPointToLatLng(new Point(endX, endY)); - } - else - endLatLng = new LatLng(contact.getPosition().lat, contact.getPosition().lng); - - var color; - if (contactData.detectionMethod === VISUAL || contactData.detectionMethod === OPTIC) - color = "#FF00FF"; - else if (contactData.detectionMethod === RADAR || contactData.detectionMethod === IRST) - color = "#FFFF00"; - else if (contactData.detectionMethod === RWR) - color = "#00FF00"; - else - color = "#FFFFFF"; - var contactPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 1, smoothFactor: 1, dashArray: "4, 8" }); - contactPolyline.addTo(getApp().getMap()); - this.#contactsPolylines.push(contactPolyline) - } - } - } - } - - #clearContacts() { - for (let index in this.#contactsPolylines) { - getApp().getMap().removeLayer(this.#contactsPolylines[index]) - } - } - - #drawRanges() { - var engagementRange = 0; - var acquisitionRange = 0; - - /* 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; - - 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; - - if (unitEngagementRange > engagementRange) - engagementRange = unitEngagementRange; - - if (unitAcquisitionRange > acquisitionRange) - acquisitionRange = unitAcquisitionRange; - } - }) - - 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; - - /* Acquisition circles */ - var shortAcquisitionRangeCheck = (acquisitionRange > nmToM(3) || !getApp().getMap().getVisibilityOptions()[HIDE_UNITS_SHORT_RANGE_RINGS]); - - if (getApp().getMap().getVisibilityOptions()[SHOW_UNITS_ACQUISITION_RINGS] && shortAcquisitionRangeCheck && (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, IRST, RWR].includes(value)))) { - if (!getApp().getMap().hasLayer(this.#acquisitionCircle)) { - this.#acquisitionCircle.addTo(getApp().getMap()); - switch (this.getCoalition()) { - case "red": - this.#acquisitionCircle.options.color = "#D42121"; - break; - case "blue": - this.#acquisitionCircle.options.color = "#017DC1"; - break; - default: - this.#acquisitionCircle.options.color = "#111111" - break; - } - } - 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)))) { - if (!getApp().getMap().hasLayer(this.#engagementCircle)) { - this.#engagementCircle.addTo(getApp().getMap()); - switch (this.getCoalition()) { - case "red": - this.#engagementCircle.options.color = "#FF5858"; - break; - case "blue": - this.#engagementCircle.options.color = "#3BB9FF"; - break; - default: - this.#engagementCircle.options.color = "#CFD9E8" - break; - } - } - if (this.getPosition() != this.#engagementCircle.getLatLng()) - this.#engagementCircle.setLatLng(this.getPosition()); - } - else { - if (getApp().getMap().hasLayer(this.#engagementCircle)) - this.#engagementCircle.removeFrom(getApp().getMap()); - } - } - } - - #clearRanges() { - if (getApp().getMap().hasLayer(this.#acquisitionCircle)) - this.#acquisitionCircle.removeFrom(getApp().getMap()); - - if (getApp().getMap().hasLayer(this.#engagementCircle)) - this.#engagementCircle.removeFrom(getApp().getMap()); - } - - #drawTarget() { - if (this.#targetPosition.lat != 0 && this.#targetPosition.lng != 0 && getApp().getMap().getVisibilityOptions()[SHOW_UNIT_PATHS]) { - this.#drawTargetPosition(this.#targetPosition); - } - else if (this.#targetID != 0 && getApp().getMap().getVisibilityOptions()[SHOW_UNIT_TARGETS]) { - const target = getApp().getUnitsManager().getUnitByID(this.#targetID); - if (target && (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || (this.belongsToCommandedCoalition() && getApp().getUnitsManager().getUnitDetectedMethods(target).some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))))) { - this.#drawTargetPosition(target.getPosition()); - } - } - else - this.#clearTargetPosition(); - } - - #drawTargetPosition(targetPosition: LatLng) { - if (!getApp().getMap().hasLayer(this.#targetPositionMarker)) - this.#targetPositionMarker.addTo(getApp().getMap()); - if (!getApp().getMap().hasLayer(this.#targetPositionPolyline)) - this.#targetPositionPolyline.addTo(getApp().getMap()); - this.#targetPositionMarker.setLatLng(new LatLng(targetPosition.lat, targetPosition.lng)); - - if (this.getState() === 'simulate-fire-fight' && this.getShotsScatter() != MAX_SHOTS_SCATTER) { - let turfUnitPosition = turf.point([this.getPosition().lng, this.getPosition().lat]); - let turfTargetPosition = turf.point([targetPosition.lng, targetPosition.lat]); - - let bearing = turf.bearing(turfUnitPosition, turfTargetPosition); - let scatterDistance = turf.distance(turfUnitPosition, turfTargetPosition) * Math.tan((MAX_SHOTS_SCATTER - this.getShotsScatter()) * deg2rad(SHOTS_SCATTER_DEGREES)); - let destination1 = turf.destination(turfTargetPosition, scatterDistance, bearing + 90); - let destination2 = turf.destination(turfTargetPosition, scatterDistance, bearing - 90); - - this.#targetPositionPolyline.setStyle({dashArray: "4, 8"}); - this.#targetPositionPolyline.setLatLngs([new LatLng(destination1.geometry.coordinates[1], destination1.geometry.coordinates[0]), new LatLng(this.#position.lat, this.#position.lng), new LatLng(destination2.geometry.coordinates[1], destination2.geometry.coordinates[0])]) - } else { - this.#targetPositionPolyline.setStyle({dashArray: ""}); - this.#targetPositionPolyline.setLatLngs([new LatLng(this.#position.lat, this.#position.lng), new LatLng(targetPosition.lat, targetPosition.lng)]) - } - } - - #clearTargetPosition() { - if (getApp().getMap().hasLayer(this.#targetPositionMarker)) - this.#targetPositionMarker.removeFrom(getApp().getMap()); - - if (getApp().getMap().hasLayer(this.#targetPositionPolyline)) - this.#targetPositionPolyline.removeFrom(getApp().getMap()); - } - - #onZoom(e: any) { - if (this.checkZoomRedraw()) - this.#redrawMarker(); - this.#updateMarker(); - } -} - -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))), - showFuel: belongsToCommandedCoalition, - showAmmo: belongsToCommandedCoalition, - showSummary: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), - showCallsign: belongsToCommandedCoalition, - rotateToHeading: false - }; - } - - appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null) { - if (targetUnit !== null) { - if (targetUnit != this) { - contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", (units: Unit[]) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) }); - contextActionSet.addContextAction(this, "follow", "Follow unit", "Follow this unit in formation", (units: Unit[]) => { targetUnit.showFollowOptions(units); }, false); // Don't hide the context menu after the execution (to show the follow options) - } - if (targetUnit.getSelected()) { - contextActionSet.addContextAction(this, "refuel", "Refuel", "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB", (units: Unit[]) => { getApp().getUnitsManager().refuel(units) }); - } - if (getApp().getUnitsManager().getSelectedUnits().length == 1 && targetUnit === this) { - contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", () => { getApp().getMap().centerOnUnit(this.ID); }); - } - } - - if (targetPosition !== null) { - contextActionSet.addContextAction(this, "bomb", "Precision bombing", "Precision bombing of a specific point", (units: Unit[]) => { getApp().getUnitsManager().bombPoint(targetPosition, units) }); - contextActionSet.addContextAction(this, "carpet-bomb", "Carpet bombing", "Carpet bombing close to a point", (units: Unit[]) => { getApp().getUnitsManager().carpetBomb(targetPosition, units) }); - } - } -} - -export class Aircraft extends AirUnit { - constructor(ID: number) { - super(ID); - } - - getCategory() { - return "Aircraft"; - } - - appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null) { - super.appendContextActions(contextActionSet, targetUnit, targetPosition); - - if (targetPosition === null && this.getSelected()) { - contextActionSet.addContextAction(this, "refuel", "Refuel", "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB", (units: Unit[]) => { getApp().getUnitsManager().refuel(units) }); - } - } - - getMarkerCategory() { - return "aircraft"; - } - - getDefaultMarker() { - return "aircraft"; - } -} - -export class Helicopter extends AirUnit { - constructor(ID: number) { - super(ID); - } - - getCategory() { - return "Helicopter"; - } - - appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null) { - super.appendContextActions(contextActionSet, targetUnit, targetPosition); - - if (targetPosition !== null) - contextActionSet.addContextAction(this, "land-at-point", "Land here", "land at this precise location", (units: Unit[]) => { getApp().getUnitsManager().landAtPoint(targetPosition, units) }); - } - - getMarkerCategory() { - return "helicopter"; - } - - getDefaultMarker() { - return "helicopter"; - } -} - -export class GroundUnit extends Unit { - constructor(ID: number) { - super(ID); - } - - getIconOptions() { - var belongsToCommandedCoalition = this.belongsToCommandedCoalition(); - return { - showState: belongsToCommandedCoalition, - showVvi: false, - showHealth: true, - showHotgroup: belongsToCommandedCoalition, - showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), - showShortLabel: this.getDatabaseEntry()?.type === "SAM Site", - showFuel: false, - showAmmo: false, - showSummary: false, - showCallsign: belongsToCommandedCoalition, - rotateToHeading: false - }; - } - - appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null) { - contextActionSet.addContextAction(this, "group-ground", "Group ground units", "Create a group of ground units", (units: Unit[]) => { getApp().getUnitsManager().createGroup(units) }); - - if (targetUnit !== null) { - if (targetUnit != this) { - contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", (units: Unit[]) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) }); - } - - if (getApp().getUnitsManager().getSelectedUnits().length == 1 && targetUnit === this) { - contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", () => { getApp().getMap().centerOnUnit(this.ID); }); - } - } - - if (targetPosition !== null) { - if (this.canTargetPoint()) { - contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Fire at a specific area on the ground", (units: Unit[]) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) }); - contextActionSet.addContextAction(this, "simulate-fire-fight", "Simulate fire fight", "Simulate a fire fight by shooting randomly in a certain large area.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().simulateFireFight(targetPosition, units) }); - } - } - else { - if (this.canAAA()) { - contextActionSet.addContextAction(this, "scenic-aaa", "Scenic AAA", "Shoot AAA in the air without aiming at any target, when an enemy unit gets close enough.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().scenicAAA(units) }, undefined, { - "isScenic": true - }); - contextActionSet.addContextAction(this, "miss-aaa", "Dynamic accuracy AAA", "Shoot AAA towards the closest enemy unit, but don't aim precisely.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().missOnPurpose(units) }, undefined, { - "isScenic": true - }); - } - } - } - - getCategory() { - return "GroundUnit"; - } - - getType() { - 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) ?? this.getDatabase()?.getUnkownUnit(this.getName()); - else - return this.getDatabase()?.getByName(this.getName()) ?? this.getDatabase()?.getUnkownUnit(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] as boolean && - (getApp().getMap().getZoom() >= GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() < GROUPING_ZOOM_TRANSITION || - getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() >= GROUPING_ZOOM_TRANSITION)) - } - - getMarkerCategory() { - if (/\bAAA|SAM\b/.test(this.getType()) || /\bmanpad|stinger\b/i.test(this.getType())) - return "groundunit-sam"; - else - return "groundunit"; - } - - getDefaultMarker() { - return "groundunit"; - } -} - -export class NavyUnit extends Unit { - constructor(ID: number) { - super(ID); - } - - getIconOptions() { - var belongsToCommandedCoalition = this.belongsToCommandedCoalition(); - return { - showState: belongsToCommandedCoalition, - showVvi: false, - showHealth: true, - showHotgroup: true, - showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), - showShortLabel: false, - showFuel: false, - showAmmo: false, - showSummary: false, - showCallsign: belongsToCommandedCoalition, - rotateToHeading: false - }; - } - - appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null) { - contextActionSet.addContextAction(this, "group-navy", "Group navy units", "Create a group of navy units", (units: Unit[]) => { getApp().getUnitsManager().createGroup(units) }); - - if (targetUnit !== null) { - if (targetUnit != this) { - contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", (units: Unit[]) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) }); - } - if (getApp().getUnitsManager().getSelectedUnits().length == 1 && targetUnit === this) { - contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", () => { getApp().getMap().centerOnUnit(this.ID); }); - } - } - - if (targetPosition !== null) { - contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Fire at a specific area on the ground", (units: Unit[]) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) }); - } - } - - getCategory() { - return "NavyUnit"; - } - - getType() { - var blueprint = navyUnitDatabase.getByName(this.getName()); - return blueprint?.type ? blueprint.type : ""; - } - - getMarkerCategory() { - return "navyunit"; - } - - getDefaultMarker() { - return "navyunit"; - } -} diff --git a/frontend/website/src/unit/unitsmanager.ts b/frontend/website/src/unit/unitsmanager.ts deleted file mode 100644 index eebdefde..00000000 --- a/frontend/website/src/unit/unitsmanager.ts +++ /dev/null @@ -1,1544 +0,0 @@ -import { LatLng, LatLngBounds } from "leaflet"; -import { getApp } from ".."; -import { Unit } from "./unit"; -import { bearingAndDistanceToLatLng, deg2rad, getGroundElevation, getUnitDatabaseByCategory, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils"; -import { CoalitionArea } from "../map/coalitionarea/coalitionarea"; -import { groundUnitDatabase } from "./databases/groundunitdatabase"; -import { DELETE_CYCLE_TIME, DELETE_SLOW_THRESHOLD, DataIndexes, GAME_MASTER, IADSDensities, IDLE, MOVE_UNIT } from "../constants/constants"; -import { DataExtractor } from "../server/dataextractor"; -import { citiesDatabase } from "./databases/citiesdatabase"; -import { aircraftDatabase } from "./databases/aircraftdatabase"; -import { helicopterDatabase } from "./databases/helicopterdatabase"; -import { navyUnitDatabase } from "./databases/navyunitdatabase"; -import { TemporaryUnitMarker } from "../map/markers/temporaryunitmarker"; -import { Popup } from "../popups/popup"; -import { HotgroupPanel } from "../panels/hotgrouppanel"; -import { Contact, UnitBlueprint, UnitData, UnitSpawnTable } from "../interfaces"; -import { Dialog } from "../dialog/dialog"; -import { Group } from "./group"; -import { UnitDataFileExport } from "./importexport/unitdatafileexport"; -import { UnitDataFileImport } from "./importexport/unitdatafileimport"; - -/** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only - * result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows - * to avoid client/server and client/client inconsistencies. - */ -export class UnitsManager { - #copiedUnits: UnitData[]; - #deselectionEventDisabled: boolean = false; - #requestDetectionUpdate: boolean = false; - #selectionEventDisabled: boolean = false; - #slowDeleteDialog!: Dialog; - #units: { [ID: number]: Unit }; - #groups: { [groupName: string]: Group } = {}; - #unitDataExport!: UnitDataFileExport; - #unitDataImport!: UnitDataFileImport; - - constructor() { - this.#copiedUnits = []; - this.#units = {}; - - document.addEventListener('commandModeOptionsChanged', () => { Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility()) }); - document.addEventListener('contactsUpdated', (e: CustomEvent) => { this.#requestDetectionUpdate = true }); - document.addEventListener('copy', () => this.copy()); - document.addEventListener('deleteSelectedUnits', () => this.delete()); - document.addEventListener('explodeSelectedUnits', (e: any) => this.delete(true, e.detail.type)); - document.addEventListener('exportToFile', () => this.exportToFile()); - document.addEventListener('importFromFile', () => this.importFromFile()); - document.addEventListener('keyup', (event) => this.#onKeyUp(event)); - document.addEventListener('paste', () => this.paste()); - document.addEventListener('selectedUnitsChangeAltitude', (e: any) => { this.changeAltitude(e.detail.type) }); - document.addEventListener('selectedUnitsChangeSpeed', (e: any) => { this.changeSpeed(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() }); - - this.#slowDeleteDialog = new Dialog("slow-delete-dialog"); - } - - /** - * - * @returns All the existing units, both alive and dead - */ - getUnits() { - return this.#units; - } - - /** Get a specific unit by ID - * - * @param ID ID of the unit. The ID shall be the same as the unit ID in DCS. - * @returns Unit object, or null if no unit with said ID exists. - */ - getUnitByID(ID: number) { - if (ID in this.#units) - return this.#units[ID]; - else - return null; - } - - /** Returns all the units that belong to a hotgroup - * - * @param hotgroup Hotgroup number - * @returns Array of units that belong to hotgroup - */ - getUnitsByHotgroup(hotgroup: number) { - return Object.values(this.#units).filter((unit: Unit) => { return unit.getAlive() && unit.getHotgroup() == hotgroup }); - } - - /** Add a new unit to the manager - * - * @param ID ID of the new unit - * @param category Either "Aircraft", "Helicopter", "GroundUnit", or "NavyUnit". Determines what class will be used to create the new unit accordingly. - */ - addUnit(ID: number, category: string) { - if (category) { - /* Get the constructor from the unit category */ - var constructor = Unit.getConstructor(category); - if (constructor != undefined) { - this.#units[ID] = new constructor(ID); - } - } - } - - /** Sort units segregated groups based on controlling type and protection, if DCS-controlled - * - * @param units - * @returns Object - */ - segregateUnits(units: Unit[]): { [key: string]: [] } { - const data: any = { - controllable: [], - dcsProtected: [], - dcsUnprotected: [], - human: [], - olympus: [] - }; - const map = getApp().getMap(); - - units.forEach(unit => { - if (unit.getHuman()) - data.human.push(unit); - else if (unit.isControlledByOlympus()) - data.olympus.push(unit); - else if (map.getIsUnitProtected(unit)) - data.dcsProtected.push(unit); - else - data.dcsUnprotected.push(unit); - }); - data.controllable = [].concat(data.dcsUnprotected, data.human, data.olympus); - return data; - } - - /** - * - * @param numOfProtectedUnits number - */ - showProtectedUnitsPopup(numOfProtectedUnits: number) { - if (numOfProtectedUnits < 1) - return; - const messageText = (numOfProtectedUnits === 1) ? `Unit is protected` : `All selected units are protected`; - (getApp().getPopupsManager().get("infoPopup") as Popup).setText(messageText); - // Cheap way for now until we use more locks - let lock = document.querySelector("#unit-visibility-control button.lock"); - lock.classList.add("prompt"); - setTimeout(() => lock.classList.remove("prompt"), 4000); - } - - /** Update the data of all the units. The data is directly decoded from the binary buffer received from the REST Server. This is necessary for performance and bandwidth reasons. - * - * @param buffer The arraybuffer, encoded according to the ICD defined in: TODO Add reference to ICD - * @returns The decoded updateTime of the data update. - */ - update(buffer: ArrayBuffer) { - /* Extract the data from the arraybuffer. Since data is encoded dynamically (not all data is always present, but rather only the data that was actually updated since the last request). - No a prori casting can be performed. On the contrary, the array is decoded incrementally, depending on the DataIndexes of the data. The actual data decoding is performed by the Unit class directly. - Every time a piece of data is decoded the decoder seeker is incremented. */ - var dataExtractor = new DataExtractor(buffer); - - var updateTime = Number(dataExtractor.extractUInt64()); - - /* Run until all data is extracted or an error occurs */ - while (dataExtractor.getSeekPosition() < buffer.byteLength) { - /* Extract the unit ID */ - const ID = dataExtractor.extractUInt32(); - - /* If the ID of the unit does not yet exist, create the unit, if the category is known. If it isn't, some data must have been lost and we need to wait for another update */ - if (!(ID in this.#units)) { - const datumIndex = dataExtractor.extractUInt8(); - if (datumIndex == DataIndexes.category) { - const category = dataExtractor.extractString(); - this.addUnit(ID, category); - } - else { - /* Inconsistent data, we need to wait for a refresh */ - return updateTime; - } - } - /* Update the data of the unit */ - 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 - */ - if (this.#requestDetectionUpdate && getApp().getMissionManager().getCommandModeOptions().commandMode != GAME_MASTER) { - /* Create a dictionary of empty detection methods arrays */ - var detectionMethods: { [key: string]: number[] } = {}; - for (let ID in this.#units) - detectionMethods[ID] = []; - for (let ID in getApp().getWeaponsManager().getWeapons()) - detectionMethods[ID] = []; - - /* Fill the array with the detection methods */ - for (let ID in this.#units) { - const unit = this.#units[ID]; - if (unit.getAlive() && unit.belongsToCommandedCoalition()) { - const contacts = unit.getContacts(); - contacts.forEach((contact: Contact) => { - const contactID = contact.ID; - if (contactID in detectionMethods && !(detectionMethods[contactID].includes(contact.detectionMethod))) - detectionMethods[contactID]?.push(contact.detectionMethod); - }) - } - } - - /* Set the detection methods for every unit */ - for (let ID in this.#units) { - const unit = this.#units[ID]; - unit?.setDetectionMethods(detectionMethods[ID]); - } - - /* Set the detection methods for every weapon (weapons must be detected too) */ - for (let ID in getApp().getWeaponsManager().getWeapons()) { - const weapon = getApp().getWeaponsManager().getWeaponByID(parseInt(ID)); - weapon?.setDetectionMethods(detectionMethods[ID]); - } - - this.#requestDetectionUpdate = false; - } - - /* Update the detection lines of all the units. This code is handled by the UnitsManager since it must be run both when the detected OR the detecting unit is updated */ - for (let ID in this.#units) { - if (this.#units[ID].getSelected()) - this.#units[ID].drawLines(); - }; - - return updateTime; - } - - /** Set a unit as "selected", which will allow to perform operations on it, like giving it a destination, setting it to attack a target, and so on - * - * @param ID The ID of the unit to select - * @param deselectAllUnits If true, the unit will be the only selected unit - */ - selectUnit(ID: number, deselectAllUnits: boolean = true) { - if (deselectAllUnits) - this.getSelectedUnits().filter((unit: Unit) => unit.ID !== ID).forEach((unit: Unit) => unit.setSelected(false)); - this.#units[ID]?.setSelected(true); - } - - /** Select all visible units inside a bounding rectangle - * - * @param bounds Leaflet bounds object defining the selection area - */ - selectFromBounds(bounds: LatLngBounds) { - this.deselectAllUnits(); - for (let ID in this.#units) { - if (this.#units[ID].getHidden() == false) { - var latlng = new LatLng(this.#units[ID].getPosition().lat, this.#units[ID].getPosition().lng); - if (bounds.contains(latlng)) { - this.#units[ID].setSelected(true); - } - } - } - } - - /** Select units by hotgroup. A hotgroup can be created to quickly select multiple units using keyboard bindings - * - * @param hotgroup The hotgroup number - */ - selectUnitsByHotgroup(hotgroup: number, deselectAllUnits: boolean = true) { - - if (deselectAllUnits) { - this.deselectAllUnits(); - } - - this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setSelected(true)) - } - - /** Get all the currently selected units - * - * @param options Selection options - * @returns Array of selected units - */ - 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()) { - if (options) { - if (options.excludeHumans && unit.getHuman()) - continue - - if (options.excludeProtected === true && this.#unitIsProtected(unit)) { - numProtectedUnits++; - continue; - } - } - selectedUnits.push(unit); - } - } - if (options) { - if (options.showProtectionReminder === true && numProtectedUnits > selectedUnits.length && selectedUnits.length === 0) - this.showProtectedUnitsPopup(numProtectedUnits); - - if (options.onlyOnePerGroup) { - var temp: Unit[] = []; - for (let unit of selectedUnits) { - if (!temp.some((otherUnit: Unit) => unit.getGroupName() == otherUnit.getGroupName())) - temp.push(unit); - } - selectedUnits = temp; - } - } - return selectedUnits; - } - - /** Deselects all currently selected units - * - */ - deselectAllUnits() { - for (let ID in this.#units) { - this.#units[ID].setSelected(false); - } - } - - /** Deselect a specific unit - * - * @param ID ID of the unit to deselect - */ - deselectUnit(ID: number) { - this.#units[ID]?.setSelected(false); - } - - /** This function allows to quickly determine the categories (Aircraft, Helicopter, GroundUnit, NavyUnit) of an array units. This allows to enable/disable specific controls which can only be applied - * to specific categories. - * - * @param units Array of units of which to retrieve the categories - * @returns Array of categories. Each category is present only once. - */ - getUnitsCategories(units: Unit[]) { - if (units.length == 0) - return []; - return units.map((unit: Unit) => { - return unit.getCategory(); - })?.filter((value: any, index: number, array: string[]) => { - return array.indexOf(value) === index; - }); - } - - /** This function returns the value of a variable for each of the units in the input array. If all the units have the same value, returns the value, else returns undefined. This function is useful to - * present units data in the control panel, which will print a specific value only if it is the same for all the units. If the values are different, the control panel will show a "mixed values" value, or similar. - * - * @param variableGetter CallableFunction that returns the requested variable. Example: getUnitsVariable((unit: Unit) => unit.getName(), foo) will return a string value if all the units have the same name, otherwise it will return undefined. - * @param units Array of units of which to retrieve the variable - * @returns The value of the variable if all units have the same value, else undefined - */ - getUnitsVariable(variableGetter: CallableFunction, units: Unit[]) { - if (units.length == 0) - return undefined; - - var value: any = variableGetter(units[0]); - units.forEach((unit: Unit) => { - if (variableGetter(unit) !== value) - value = undefined; - }); - return value; - }; - - /** For a given unit, it returns if and how it is being detected by other units. NOTE: this function will return how a unit is being detected, i.e. how other units are detecting it. It will not return - * what the unit is detecting. - * - * @param unit The unit of which to retrieve the "detected" methods. - * @returns Array of detection methods - */ - getUnitDetectedMethods(unit: Unit) { - var detectionMethods: number[] = []; - for (let idx in this.#units) { - if (this.#units[idx].getAlive() && this.#units[idx].getIsLeader() && this.#units[idx].getCoalition() !== "neutral" && this.#units[idx].getCoalition() != unit.getCoalition()) { - this.#units[idx].getContacts().forEach((contact: Contact) => { - if (contact.ID == unit.ID && !detectionMethods.includes(contact.detectionMethod)) - detectionMethods.push(contact.detectionMethod); - }); - } - } - return detectionMethods; - } - - /*********************** Unit actions on selected units ************************/ - /** Give a new destination to the selected units - * - * @param latlng Position of the new destination - * @param mantainRelativePosition If true, the selected units will mantain their relative positions when reaching the target. This is useful to maintain a formation for groun/navy units - * @param rotation Rotation in radians by which the formation will be rigidly rotated. E.g. a ( V ) formation will look like this ( < ) if rotated pi/4 radians (90 degrees) - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - addDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - /* Compute the destination for each unit. If mantainRelativePosition is true, compute the destination so to hold the relative positions */ - var unitDestinations: { [key: number]: LatLng } = {}; - if (mantainRelativePosition) - unitDestinations = this.computeGroupDestination(latlng, rotation); - else - units.forEach((unit: Unit) => { unitDestinations[unit.ID] = latlng; }); - - units.forEach((unit: Unit) => { - /* If a unit is following another unit, and that unit is also selected, send the command to the followed ("leader") unit */ - if (unit.getState() === "follow") { - const leader = this.getUnitByID(unit.getLeaderID()) - if (leader && leader.getSelected()) - leader.addDestination(latlng); - else - unit.addDestination(latlng); - } - else { - if (unit.ID in unitDestinations) - unit.addDestination(unitDestinations[unit.ID]); - } - }); - this.#showActionMessage(units, " new destination added"); - } - - /** Clear the destinations of all the selected units - * - */ - clearDestinations(units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: false }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - for (let idx in units) { - const unit = units[idx]; - if (unit.getState() === "follow") { - const leader = this.getUnitByID(unit.getLeaderID()) - if (leader && leader.getSelected()) - leader.clearDestinations(); - else - unit.clearDestinations(); - } - else - unit.clearDestinations(); - } - } - - /** Instruct all the selected units to land at a specific location - * - * @param latlng Location where to land at - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - landAt(latlng: LatLng, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.landAt(latlng)); - - this.#showActionMessage(units, " landing"); - } - - /** Instruct all the selected units to change their speed - * - * @param speedChange Speed change, either "stop", "slow", or "fast". The specific value depends on the unit category - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - changeSpeed(speedChange: string, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.changeSpeed(speedChange)); - } - - /** Instruct all the selected units to change their altitude - * - * @param altitudeChange Altitude change, either "climb" or "descend". The specific value depends on the unit category - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - changeAltitude(altitudeChange: string, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.changeAltitude(altitudeChange)); - } - - /** Set a specific speed to all the selected units - * - * @param speed Value to set, in m/s - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setSpeed(speed: number, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.setSpeed(speed)); - this.#showActionMessage(units, `setting speed to ${msToKnots(speed)} kts`); - } - - /** Set a specific speed type to all the selected units - * - * @param speedType Value to set, either "CAS" or "GS". If "CAS" is selected, the unit will try to maintain the selected Calibrated Air Speed, but DCS will still only maintain a Ground Speed value so errors may arise depending on wind. - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setSpeedType(speedType: string, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.setSpeedType(speedType)); - this.#showActionMessage(units, `setting speed type to ${speedType}`); - } - - /** Set a specific altitude to all the selected units - * - * @param altitude Value to set, in m - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setAltitude(altitude: number, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.setAltitude(altitude)); - this.#showActionMessage(units, `setting altitude to ${mToFt(altitude)} ft`); - } - - /** Set a specific altitude type to all the selected units - * - * @param altitudeType Value to set, either "ASL" or "AGL". If "AGL" is selected, the unit will try to maintain the selected Above Ground Level altitude. Due to a DCS bug, this will only be true at the final position. - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setAltitudeType(altitudeType: string, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.setAltitudeType(altitudeType)); - this.#showActionMessage(units, `setting altitude type to ${altitudeType}`); - } - - /** Set a specific ROE to all the selected units - * - * @param ROE Value to set, see constants for acceptable values - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setROE(ROE: string, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.setROE(ROE)); - this.#showActionMessage(units, `ROE set to ${ROE}`); - } - - /** Set a specific reaction to threat to all the selected units - * - * @param reactionToThreat Value to set, see constants for acceptable values - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setReactionToThreat(reactionToThreat: string, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.setReactionToThreat(reactionToThreat)); - this.#showActionMessage(units, `reaction to threat set to ${reactionToThreat}`); - } - - /** Set a specific emissions & countermeasures to all the selected units - * - * @param emissionCountermeasure Value to set, see constants for acceptable values - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setEmissionsCountermeasures(emissionCountermeasure: string, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.setEmissionsCountermeasures(emissionCountermeasure)); - this.#showActionMessage(units, `emissions & countermeasures set to ${emissionCountermeasure}`); - } - - /** Turn selected units on or off, only works on ground and navy units - * - * @param onOff If true, the unit will be turned on - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setOnOff(onOff: boolean, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.setOnOff(onOff)); - this.#showActionMessage(units, `unit active set to ${onOff}`); - } - - /** Instruct the selected units to follow roads, only works on ground units - * - * @param followRoads If true, units will follow roads - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setFollowRoads(followRoads: boolean, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.setFollowRoads(followRoads)); - this.#showActionMessage(units, `follow roads set to ${followRoads}`); - } - - /** Instruct selected units to operate as a certain coalition - * - * @param operateAsBool If true, units will operate as blue - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setOperateAs(operateAsBool: boolean, units: Unit[] | null = null) { - var operateAs = operateAsBool ? "blue" : "red"; - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.setOperateAs(operateAs)); - this.#showActionMessage(units, `operate as set to ${operateAs}`); - } - - /** Instruct units to attack a specific unit - * - * @param ID ID of the unit to attack - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - attackUnit(ID: number, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.attackUnit(ID)); - this.#showActionMessage(units, `attacking unit ${this.getUnitByID(ID)?.getUnitName()}`); - } - - /** Instruct units to refuel at the nearest tanker, if possible. Else units will RTB - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - refuel(units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - segregatedUnits.controllable.forEach((unit: Unit) => unit.refuel()); - this.#showActionMessage(segregatedUnits.controllable, `sent to nearest tanker`); - } - - /** Instruct the selected units to follow another unit in a formation. Only works for aircrafts and helicopters. - * - * @param ID ID of the unit to follow - * @param offset Optional parameter, defines a static offset. X: front-rear, positive front, Y: top-bottom, positive top, Z: left-right, positive right - * @param formation Optional parameter, defines a predefined formation type. Values are: "trail", "echelon-lh", "echelon-rh", "line-abreast-lh", "line-abreast-rh", "front", "diamond" - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - followUnit(ID: number, offset?: { "x": number, "y": number, "z": number }, formation?: string, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - if (offset == undefined) { - /* Simple formations with fixed offsets */ - offset = { "x": 0, "y": 0, "z": 0 }; - if (formation === "trail") { offset.x = -50; offset.y = -30; offset.z = 0; } - else if (formation === "echelon-lh") { offset.x = -50; offset.y = -10; offset.z = -50; } - else if (formation === "echelon-rh") { offset.x = -50; offset.y = -10; offset.z = 50; } - else if (formation === "line-abreast-lh") { offset.x = 0; offset.y = 0; offset.z = -50; } - else if (formation === "line-abreast-rh") { offset.x = 0; offset.y = 0; offset.z = 50; } - else if (formation === "front") { offset.x = 100; offset.y = 0; offset.z = 0; } - else offset = undefined; - } - - var count = 1; - var xr = 0; var yr = 1; var zr = -1; - var layer = 1; - units.forEach((unit: Unit) => { - if (unit.ID !== ID) { - if (offset != undefined) - /* Offset is set, apply it */ - unit.followUnit(ID, { "x": offset.x * count, "y": offset.y * count, "z": offset.z * count }); - else { - /* More complex formations with variable offsets */ - if (formation === "diamond") { - var xl = xr * Math.cos(Math.PI / 4) - yr * Math.sin(Math.PI / 4); - var yl = xr * Math.sin(Math.PI / 4) + yr * Math.cos(Math.PI / 4); - unit.followUnit(ID, { "x": -yl * 50, "y": zr * 10, "z": xl * 50 }); - - if (yr == 0) { layer++; xr = 0; yr = layer; zr = -layer; } - else { - if (xr < layer) { xr++; zr--; } - else { yr--; zr++; } - } - } - } - count++; - } - }) - this.#showActionMessage(units, `following unit ${this.getUnitByID(ID)?.getUnitName()}`); - } - - /** Instruct the selected units to perform precision bombing of specific coordinates - * - * @param latlng Location to bomb - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - bombPoint(latlng: LatLng, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.bombPoint(latlng)); - this.#showActionMessage(units, `unit bombing point`); - } - - /** Instruct the selected units to perform carpet bombing of specific coordinates - * - * @param latlng Location to bomb - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - carpetBomb(latlng: LatLng, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.carpetBomb(latlng)); - this.#showActionMessage(units, `unit carpet bombing point`); - } - - /** Instruct the selected units to fire at specific coordinates - * - * @param latlng Location to fire at - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - fireAtArea(latlng: LatLng, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.fireAtArea(latlng)); - this.#showActionMessage(units, `unit firing at area`); - } - - /** Instruct the selected units to simulate a fire fight at specific coordinates - * - * @param latlng Location to fire at - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - simulateFireFight(latlng: LatLng, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - getGroundElevation(latlng, (response: string) => { - var groundElevation: number | null = null; - try { - groundElevation = parseFloat(response); - } catch { - console.warn("Simulate fire fight: could not retrieve ground elevation") - } - units?.forEach((unit: Unit) => unit.simulateFireFight(latlng, groundElevation)); - }); - this.#showActionMessage(units, `unit simulating fire fight`); - } - - /** Instruct units to enter into scenic AAA mode. Units will shoot in the air without aiming - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - scenicAAA(units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.scenicAAA()); - this.#showActionMessage(units, `unit set to perform scenic AAA`); - } - - /** Instruct units to enter into dynamic accuracy/miss on purpose mode. Units will aim to the nearest enemy unit but not precisely. - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - missOnPurpose(units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.missOnPurpose()); - this.#showActionMessage(units, `unit set to perform miss-on-purpose AAA`); - } - - /** Instruct units to land at specific point - * - * @param latlng Point where to land - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - landAtPoint(latlng: LatLng, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.landAtPoint(latlng)); - this.#showActionMessage(units, `unit landing at point`); - } - - /** Set a specific shots scatter to all the selected units - * - * @param shotsScatter Value to set - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setShotsScatter(shotsScatter: number, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.setShotsScatter(shotsScatter)); - this.#showActionMessage(units, `shots scatter set to ${shotsScatter}`); - } - - /** Set a specific shots intensity to all the selected units - * - * @param shotsScatter Value to set - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setShotsIntensity(shotsIntensity: number, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - units.forEach((unit: Unit) => unit.setShotsIntensity(shotsIntensity)); - this.#showActionMessage(units, `shots intensity set to ${shotsIntensity}`); - } - - /*********************** Control operations on selected units ************************/ - /** See getUnitsCategories for more info - * - * @returns Category array of the selected units. - */ - getSelectedUnitsCategories() { - return this.getUnitsCategories(this.getSelectedUnits()); - }; - - /** See getUnitsVariable for more info - * - * @param variableGetter CallableFunction that returns the requested variable. Example: getUnitsVariable((unit: Unit) => unit.getName(), foo) will return a string value if all the units have the same name, otherwise it will return undefined. - * @returns The value of the variable if all units have the same value, else undefined - */ - getSelectedUnitsVariable(variableGetter: CallableFunction) { - return this.getUnitsVariable(variableGetter, this.getSelectedUnits()); - }; - - /** Groups the selected units in a single (DCS) group, if all the units have the same category - * - */ - createGroup(units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: false, showProtectionReminder: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - if (this.getUnitsCategories(units).length == 1) { - var unitsData: { ID: number, location: LatLng }[] = []; - units.forEach((unit: Unit) => unitsData.push({ ID: unit.ID, location: unit.getPosition() })); - getApp().getServerManager().cloneUnits(unitsData, true, 0 /* No spawn points, we delete the original units */); - this.#showActionMessage(units, `created a group`); - } else { - (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Groups can only be created from units of the same category`); - } - } - - /** Set the hotgroup for the selected units. It will be the only hotgroup of the unit - * - * @param hotgroup Hotgroup number - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - setHotgroup(hotgroup: number, units: Unit[] | null = null) { - this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHotgroup(null)); - this.addToHotgroup(hotgroup); - } - - /** Add the selected units to a hotgroup. Units can be in multiple hotgroups at the same type - * - * @param hotgroup Hotgroup number - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - */ - addToHotgroup(hotgroup: number, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits(); - units.forEach((unit: Unit) => unit.setHotgroup(hotgroup)); - this.#showActionMessage(units, `added to hotgroup ${hotgroup}`); - (getApp().getPanelsManager().get("hotgroup") as HotgroupPanel).refreshHotgroups(); - } - - /** Delete the selected units - * - * @param explosion If true, the unit will be deleted using an explosion - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - * @returns - */ - delete(explosion: boolean = false, explosionType: string = "", units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeProtected: true, showProtectionReminder: true }); /* Can be applied to humans too */ - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return; - } - - units = segregatedUnits.controllable; - - const selectionContainsAHuman = units.some((unit: Unit) => { - return unit.getHuman() === true; - }); - - if (selectionContainsAHuman && !confirm("Your selection includes a human player. Deleting humans causes their vehicle to crash.\n\nAre you sure you want to do this?")) { - return; - } - - const doDelete = (explosion = false, explosionType = "", immediate = false) => { - units?.forEach((unit: Unit) => unit.delete(explosion, explosionType, immediate)); - this.#showActionMessage(units as Unit[], `deleted`); - } - - if (units.length >= DELETE_SLOW_THRESHOLD) - this.#showSlowDeleteDialog(units).then((action: any) => { - if (action === "delete-slow") - doDelete(explosion, explosionType, false); - else if (action === "delete-immediate") - doDelete(explosion, explosionType, true); - }) - else - doDelete(explosion, explosionType); - - } - - /** 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 - * @param rotation Rotation of the group, in radians - * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. - * @returns Array of positions for each unit, in order - */ - computeGroupDestination(latlng: LatLng, rotation: number, units: Unit[] | null = null) { - if (units === null) - units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); - - const segregatedUnits = this.segregateUnits(units); - if (segregatedUnits.controllable.length === 0) { - this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length); - return {}; - } - - units = segregatedUnits.controllable; - - if (units.length === 0) - return {}; - - /* Compute the center of the group */ - var len = units.length; - var center = { x: 0, y: 0 }; - units.forEach((unit: Unit) => { - var mercator = latLngToMercator(unit.getPosition().lat, unit.getPosition().lng); - center.x += mercator.x / len; - center.y += mercator.y / len; - }); - - /* Compute the distances from the center of the group */ - var unitDestinations: { [key: number]: LatLng } = {}; - units.forEach((unit: Unit) => { - var mercator = latLngToMercator(unit.getPosition().lat, unit.getPosition().lng); - var distancesFromCenter = { dx: mercator.x - center.x, dy: mercator.y - center.y }; - - /* Rotate the distance according to the group rotation */ - var rotatedDistancesFromCenter: { dx: number, dy: number } = { dx: 0, dy: 0 }; - rotatedDistancesFromCenter.dx = distancesFromCenter.dx * Math.cos(deg2rad(rotation)) - distancesFromCenter.dy * Math.sin(deg2rad(rotation)); - rotatedDistancesFromCenter.dy = distancesFromCenter.dx * Math.sin(deg2rad(rotation)) + distancesFromCenter.dy * Math.cos(deg2rad(rotation)); - - /* Compute the final position of the unit */ - var destMercator = latLngToMercator(latlng.lat, latlng.lng); // Convert destination point to mercator - var unitMercator = { x: destMercator.x + rotatedDistancesFromCenter.dx, y: destMercator.y + rotatedDistancesFromCenter.dy }; // Compute final position of this unit in mercator coordinates - var unitLatLng = mercatorToLatLng(unitMercator.x, unitMercator.y); - unitDestinations[unit.ID] = new LatLng(unitLatLng.lat, unitLatLng.lng); - }); - - return unitDestinations; - } - - /** Copy the selected units and store their properties in memory - * - */ - copy(units: Unit[] | null = null) { - if (!getApp().getContextManager().getCurrentContext().getAllowUnitCopying()) - return; - - if (units === null) - units = this.getSelectedUnits(); - - if (units.length === 0) - return; - - /* A JSON is used to deepcopy the units, creating a "snapshot" of their properties at the time of the copy */ - this.#copiedUnits = JSON.parse(JSON.stringify(units.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 - * - * @returns True if units were pasted successfully - */ - paste() { - if (!getApp().getContextManager().getCurrentContext().getAllowUnitPasting()) - return; - - let spawnPoints = 0; - - /* If spawns are restricted, check that the user has the necessary spawn points */ - if (getApp().getMissionManager().getCommandModeOptions().commandMode != GAME_MASTER) { - if (getApp().getMissionManager().getCommandModeOptions().restrictSpawns && getApp().getMissionManager().getRemainingSetupTime() < 0) { - (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Units can be pasted only during SETUP phase`); - return false; - } - - this.#copiedUnits.forEach((unit: UnitData) => { - let unitSpawnPoints = getUnitDatabaseByCategory(unit.category)?.getSpawnPointsByName(unit.name); - if (unitSpawnPoints !== undefined) - spawnPoints += unitSpawnPoints; - }) - - if (spawnPoints > getApp().getMissionManager().getAvailableSpawnPoints()) { - (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Not enough spawn points available!"); - return false; - } - } - - if (this.#copiedUnits.length > 0) { - /* Compute the position of the center of the copied units */ - var nUnits = this.#copiedUnits.length; - var avgLat = 0; - var avgLng = 0; - for (let idx in this.#copiedUnits) { - var unit = this.#copiedUnits[idx]; - avgLat += unit.position.lat / nUnits; - avgLng += unit.position.lng / nUnits; - } - - /* Organize the copied units in groups */ - var groups: { [key: string]: UnitData[] } = {}; - this.#copiedUnits.forEach((unit: UnitData) => { - if (!(unit.groupName in groups)) - groups[unit.groupName] = []; - groups[unit.groupName].push(unit); - }); - - /* Clone the units in groups */ - for (let groupName in groups) { - var units: { ID: number, location: LatLng }[] = []; - let markers: TemporaryUnitMarker[] = []; - groups[groupName].forEach((unit: UnitData) => { - var position = new LatLng(getApp().getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getApp().getMap().getMouseCoordinates().lng + unit.position.lng - avgLng); - markers.push(getApp().getMap().addTemporaryMarker(position, unit.name, unit.coalition)); - units.push({ ID: unit.ID, location: position }); - }); - - getApp().getServerManager().cloneUnits(units, false, spawnPoints, (res: any) => { - if (res.commandHash !== undefined) { - markers.forEach((marker: TemporaryUnitMarker) => { - marker.setCommandHash(res.commandHash); - }) - } - }); - } - (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${this.#copiedUnits.length} units pasted`); - } - else { - (getApp().getPopupsManager().get("infoPopup") as Popup).setText("No units copied!"); - } - } - - /** Automatically create an Integrated Air Defence System from a CoalitionArea object. The units will be mostly focused around big cities. The bigger the city, the larger the amount of units created next to it. - * If the CoalitionArea does not contain any city, no units will be created - * - * @param coalitionArea Boundaries of the IADS - * @param types Array of unit types to add to the IADS, e.g. AAA, SAM, flak, MANPADS - * @param eras Array of eras to which the units added to the IADS can belong - * @param ranges Array of weapon ranges the units can have - * @param density Value between 0 and 100, controls the amout of units created - * @param distribution Value between 0 and 100, controls how "scattered" the units will be - */ - createIADS(coalitionArea: CoalitionArea, types: { [key: string]: boolean }, eras: { [key: string]: boolean }, ranges: { [key: string]: boolean }, density: number, distribution: number, forceCoalition: boolean) { - const activeTypes = Object.keys(types).filter((key: string) => { return types[key]; }); - const activeEras = Object.keys(eras).filter((key: string) => { return eras[key]; }); - const activeRanges = Object.keys(ranges).filter((key: string) => { return ranges[key]; }); - - var airbases = getApp().getMissionManager().getAirbases(); - Object.keys(airbases).forEach((airbaseName: string) => { - var airbase = airbases[airbaseName]; - /* Check if the city is inside the coalition area */ - if (polyContains(new LatLng(airbase.getLatLng().lat, airbase.getLatLng().lng), coalitionArea)) { - /* Arbitrary formula to obtain a number of units */ - var pointsNumber = 2 + 10 * density / 100; - for (let i = 0; i < pointsNumber; i++) { - /* Place the unit nearby the airbase, depending on the distribution parameter */ - var bearing = Math.random() * 360; - var distance = Math.random() * distribution * 100; - const latlng = bearingAndDistanceToLatLng(airbase.getLatLng().lat, airbase.getLatLng().lng, bearing, distance); - - /* Make sure the unit is still inside the coalition area */ - if (polyContains(latlng, coalitionArea)) { - const type = activeTypes[Math.floor(Math.random() * activeTypes.length)]; - if (Math.random() < IADSDensities[type]) { - /* Get a random blueprint depending on the selected parameters and spawn the unit */ - let unitBlueprint: UnitBlueprint | null; - if (forceCoalition) - unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { type: type, eras: activeEras, ranges: activeRanges, coalition: coalitionArea.getCoalition()}); - else - unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { type: type, eras: activeEras, ranges: activeRanges }); - - if (unitBlueprint) - this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "", skill: "High" }], coalitionArea.getCoalition(), false, "", ""); - } - } - } - } - }) - - citiesDatabase.forEach((city: { lat: number, lng: number, pop: number }) => { - /* Check if the city is inside the coalition area */ - if (polyContains(new LatLng(city.lat, city.lng), coalitionArea)) { - /* Arbitrary formula to obtain a number of units depending on the city population */ - var pointsNumber = 2 + Math.pow(city.pop, 0.15) * density / 100; - for (let i = 0; i < pointsNumber; i++) { - /* Place the unit nearby the city, depending on the distribution parameter */ - var bearing = Math.random() * 360; - var distance = Math.random() * distribution * 100; - const latlng = bearingAndDistanceToLatLng(city.lat, city.lng, bearing, distance); - - /* Make sure the unit is still inside the coalition area */ - if (polyContains(latlng, coalitionArea)) { - const type = activeTypes[Math.floor(Math.random() * activeTypes.length)]; - if (Math.random() < IADSDensities[type]) { - /* Get a random blueprint depending on the selected parameters and spawn the unit */ - let unitBlueprint: UnitBlueprint | null; - if (forceCoalition) - unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { type: type, eras: activeEras, ranges: activeRanges, coalition: coalitionArea.getCoalition()}); - else - unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { type: type, eras: activeEras, ranges: activeRanges }); - - if (unitBlueprint) - this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "", skill: "High" }], coalitionArea.getCoalition(), false, "", ""); - } - } - } - } - }) - } - - /** Export all the ground and navy units to file. Does not work on Aircraft and Helicopter units. - * TODO: Extend to aircraft and helicopters - */ - exportToFile() { - if (!this.#unitDataExport) - this.#unitDataExport = new UnitDataFileExport("unit-export-dialog"); - this.#unitDataExport.showForm(Object.values(this.#units)); - } - - /** Import ground and navy units from file - * TODO: extend to support aircraft and helicopters - */ - importFromFile() { - if (!this.#unitDataImport) - this.#unitDataImport = new UnitDataFileImport("unit-import-dialog"); - this.#unitDataImport.selectFile(); - } - - /** Spawn a new group of units - * - * @param category Category of the new units - * @param units Array of unit tables - * @param coalition Coalition to which the new units will belong - * @param immediate If true the command will be performed immediately, but this may cause lag on the server - * @param airbase If true, the location of the units will be ignored and the units will spawn at the given airbase. Only works for aircrafts and helicopters - * @param country Set the country of the units. If empty string, the country will be assigned automatically - * @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 = () => { }) { - var spawnPoints = 0; - 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); - 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); - spawnFunction = () => getApp().getServerManager().spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints, callback); - - } else if (category === "GroundUnit") { - if (spawnsRestricted) { - (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Ground units can be spawned during the SETUP phase only"); - return false; - } - spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType) }, 0); - spawnFunction = () => getApp().getServerManager().spawnGroundUnits(units, coalition, country, immediate, spawnPoints, callback); - - } else if (category === "NavyUnit") { - if (spawnsRestricted) { - (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Navy units can be spawned during the SETUP phase only"); - return false; - } - spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { return points + navyUnitDatabase.getSpawnPointsByName(unit.unitType) }, 0); - spawnFunction = () => getApp().getServerManager().spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback); - } - - if (spawnPoints <= getApp().getMissionManager().getAvailableSpawnPoints()) { - getApp().getMissionManager().setSpentSpawnPoints(spawnPoints); - spawnFunction(); - document.dispatchEvent( new CustomEvent( "unitSpawned", { - "detail": { - "airbase": airbase, - "category": category, - "coalition": coalition, - "country": country, - "immediate": immediate, - "unitSpawnTable": units - } - })); - return true; - } else { - (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Not enough spawn points available!"); - return false; - } - } - - /***********************************************/ - #onKeyUp(event: KeyboardEvent) { - if (!keyEventWasInInput(event)) { - if (event.key === "Delete") - this.delete(); - else if (event.key === "a" && event.ctrlKey) - Object.values(this.getUnits()).filter((unit: Unit) => { return !unit.getHidden() }).forEach((unit: Unit) => unit.setSelected(true)); - } - } - - #onUnitSelection(unit: Unit) { - if (this.getSelectedUnits().length > 0) { - /* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */ - if (!this.#selectionEventDisabled) { - getApp().getMap().setState(MOVE_UNIT); - window.setTimeout(() => { - document.dispatchEvent(new CustomEvent("unitsSelection", { detail: this.getSelectedUnits() })); - this.#selectionEventDisabled = false; - this.#showNumberOfSelectedProtectedUnits(); - }, 100); - this.#selectionEventDisabled = true; - } - } - else { - getApp().getMap().setState(IDLE); - document.dispatchEvent(new CustomEvent("clearSelection")); - } - } - - #onUnitDeselection(unit: Unit) { - if (this.getSelectedUnits().length == 0) { - getApp().getMap().setState(IDLE); - document.dispatchEvent(new CustomEvent("clearSelection")); - } - else { - /* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */ - if (!this.#deselectionEventDisabled) { - window.setTimeout(() => { - document.dispatchEvent(new CustomEvent("unitsDeselection", { detail: this.getSelectedUnits() })); - this.#deselectionEventDisabled = false; - }, 100); - this.#deselectionEventDisabled = true; - } - } - } - - #showActionMessage(units: Unit[], message: string) { - if (units.length == 1) - (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${units[0].getUnitName()} ${message}`); - else if (units.length > 1) - (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`); - } - - #showSlowDeleteDialog(units: Unit[]) { - let button: HTMLButtonElement | null = null; - const deletionTime = Math.round(units.length * DELETE_CYCLE_TIME).toString(); - const dialog = this.#slowDeleteDialog; - const element = dialog.getElement(); - 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 = units.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) { - clearInterval(interval); - dialog.hide(); - element.removeEventListener("click", listener); - resolve(button.getAttribute("data-action")); - } - }, 250); - }); - } - - #showNumberOfSelectedProtectedUnits() { - const map = getApp().getMap(); - const units = this.getSelectedUnits(); - const numSelectedUnits = units.length; - const numProtectedUnits = units.filter((unit: Unit) => map.getIsUnitProtected(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) { - return getApp().getMap().getIsUnitProtected(unit) - } -} \ No newline at end of file diff --git a/frontend/website/src/weapon/weapon.ts b/frontend/website/src/weapon/weapon.ts deleted file mode 100644 index b157cff4..00000000 --- a/frontend/website/src/weapon/weapon.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { LatLng, DivIcon, Map } from 'leaflet'; -import { getApp } from '..'; -import { enumToCoalition, mToFt, msToKnots, rad2deg, zeroAppend } from '../other/utils'; -import { CustomMarker } from '../map/markers/custommarker'; -import { SVGInjector } from '@tanem/svg-injector'; -import { DLINK, DataIndexes, GAME_MASTER, IRST, OPTIC, RADAR, VISUAL } from '../constants/constants'; -import { DataExtractor } from '../server/dataextractor'; -import { ObjectIconOptions } from '../interfaces'; - -export class Weapon extends CustomMarker { - ID: number; - - #alive: boolean = false; - #coalition: string = "neutral"; - #name: string = ""; - #position: LatLng = new LatLng(0, 0, 0); - #speed: number = 0; - #heading: number = 0; - - #hidden: boolean = false; - #detectionMethods: number[] = []; - - getAlive() {return this.#alive}; - getCoalition() {return this.#coalition}; - getName() {return this.#name}; - getPosition() {return this.#position}; - getSpeed() {return this.#speed}; - getHeading() {return this.#heading}; - - static getConstructor(type: string) { - if (type === "Missile") return Missile; - if (type === "Bomb") return Bomb; - } - - constructor(ID: number) { - super(new LatLng(0, 0), { riseOnHover: true, keyboard: false }); - - this.ID = ID; - - /* Update the marker when the options change */ - document.addEventListener("mapOptionsChanged", (ev: CustomEventInit) => { - this.#updateMarker(); - }); - } - - getCategory() { - // Overloaded by child classes - return ""; - } - - /********************** Unit data *************************/ - setData(dataExtractor: DataExtractor) { - var updateMarker = !getApp().getMap().hasLayer(this); - - var datumIndex = 0; - while (datumIndex != DataIndexes.endOfData) { - datumIndex = dataExtractor.extractUInt8(); - switch (datumIndex) { - case DataIndexes.category: dataExtractor.extractString(); break; - case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break; - case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); break; - case DataIndexes.name: this.#name = dataExtractor.extractString(); break; - case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break; - case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break; - case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break; - } - } - - if (updateMarker) - this.#updateMarker(); - } - - getData() { - return { - category: this.getCategory(), - ID: this.ID, - alive: this.#alive, - coalition: this.#coalition, - name: this.#name, - position: this.#position, - speed: this.#speed, - heading: this.#heading - } - } - - getMarkerCategory(): string { - return ""; - } - - getIconOptions(): ObjectIconOptions { - // Default values, overloaded by child classes if needed - return { - showState: false, - showVvi: false, - showHealth: false, - showHotgroup: false, - showUnitIcon: true, - showShortLabel: false, - showFuel: false, - showAmmo: false, - showSummary: true, - showCallsign: true, - rotateToHeading: false - } - } - - setAlive(newAlive: boolean) { - this.#alive = newAlive; - } - - belongsToCommandedCoalition() { - if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER && getApp().getMissionManager().getCommandedCoalition() !== this.#coalition) - return false; - return true; - } - - getType() { - return ""; - } - - /********************** Icon *************************/ - createIcon(): void { - /* Set the icon */ - var icon = new DivIcon({ - className: 'leaflet-unit-icon', - iconAnchor: [25, 25], - iconSize: [50, 50], - }); - this.setIcon(icon); - - var el = document.createElement("div"); - el.classList.add("unit"); - el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`); - el.setAttribute("data-coalition", this.#coalition); - - // Generate and append elements depending on active options - // Velocity vector - if (this.getIconOptions().showVvi) { - var vvi = document.createElement("div"); - vvi.classList.add("unit-vvi"); - vvi.toggleAttribute("data-rotate-to-heading"); - el.append(vvi); - } - - // Main icon - if (this.getIconOptions().showUnitIcon) { - var unitIcon = document.createElement("div"); - unitIcon.classList.add("unit-icon"); - var img = document.createElement("img"); - img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`; - img.onload = () => SVGInjector(img); - unitIcon.appendChild(img); - unitIcon.toggleAttribute("data-rotate-to-heading", this.getIconOptions().rotateToHeading); - el.append(unitIcon); - } - - this.getElement()?.appendChild(el); - } - - /********************** Visibility *************************/ - updateVisibility() { - const hiddenUnits = getApp().getMap().getHiddenTypes(); - var hidden = (hiddenUnits.includes(this.getMarkerCategory())) || - (hiddenUnits.includes(this.#coalition)) || - (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0); - - this.setHidden(hidden || !this.#alive); - } - - setHidden(hidden: boolean) { - this.#hidden = hidden; - - /* Add the marker if not present */ - if (!getApp().getMap().hasLayer(this) && !this.getHidden()) { - if (getApp().getMap().isZooming()) - this.once("zoomend", () => {this.addTo(getApp().getMap())}) - else - this.addTo(getApp().getMap()); - } - - /* Hide the marker if necessary*/ - if (getApp().getMap().hasLayer(this) && this.getHidden()) { - getApp().getMap().removeLayer(this); - } - } - - getHidden() { - return this.#hidden; - } - - setDetectionMethods(newDetectionMethods: number[]) { - if (!this.belongsToCommandedCoalition()) { - /* Check if the detection methods of this unit have changed */ - if (this.#detectionMethods.length !== newDetectionMethods.length || this.getDetectionMethods().some(value => !newDetectionMethods.includes(value))) { - /* Force a redraw of the unit to reflect the new status of the detection methods */ - this.setHidden(true); - this.#detectionMethods = newDetectionMethods; - this.#updateMarker(); - } - } - } - - getDetectionMethods() { - return this.#detectionMethods; - } - - /***********************************************/ - onAdd(map: Map): this { - super.onAdd(map); - return this; - } - - #updateMarker() { - this.updateVisibility(); - - /* Draw the marker */ - if (!this.getHidden()) { - if (this.getLatLng().lat !== this.#position.lat || this.getLatLng().lng !== this.#position.lng) { - this.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); - } - - var element = this.getElement(); - if (element != null) { - /* Draw the velocity vector */ - element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.#speed / 5}px;`); - - /* Set dead/alive flag */ - element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive); - - - /* Set altitude and speed */ - if (element.querySelector(".unit-altitude")) - (element.querySelector(".unit-altitude")).innerText = "FL" + zeroAppend(Math.floor(mToFt(this.#position.alt as number) / 100), 3); - if (element.querySelector(".unit-speed")) - (element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.#speed))) + "GS"; - - /* Rotate elements according to heading */ - element.querySelectorAll("[data-rotate-to-heading]").forEach(el => { - const headingDeg = rad2deg(this.#heading); - let currentStyle = el.getAttribute("style") || ""; - el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`); - }); - } - - /* Set vertical offset for altitude stacking */ - var pos = getApp().getMap().latLngToLayerPoint(this.getLatLng()).round(); - this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y); - } - } -} - -export class Missile extends Weapon { - constructor(ID: number) { - super(ID); - } - - getCategory() { - return "Missile"; - } - - getMarkerCategory() { - if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)) - return "missile"; - else - return "aircraft"; - } - - getIconOptions() { - 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, - showFuel: false, - showAmmo: false, - showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), - showCallsign: false, - rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC) - }; - } -} - -export class Bomb extends Weapon { - constructor(ID: number) { - super(ID); - } - - getCategory() { - return "Bomb"; - } - - getMarkerCategory() { - if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)) - return "bomb"; - else - return "aircraft"; - } - - getIconOptions() { - 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, - showFuel: false, - showAmmo: false, - showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), - showCallsign: false, - rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC) - }; - } -} diff --git a/frontend/website/src/weapon/weaponsmanager.ts b/frontend/website/src/weapon/weaponsmanager.ts deleted file mode 100644 index 7f2d9228..00000000 --- a/frontend/website/src/weapon/weaponsmanager.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { getApp } from ".."; -import { Weapon } from "./weapon"; -import { DataIndexes } from "../constants/constants"; -import { DataExtractor } from "../server/dataextractor"; -import { Contact } from "../interfaces"; - -/** The WeaponsManager handles the creation and update of weapons. Data is strictly updated by the server ONLY. */ -export class WeaponsManager { - #weapons: { [ID: number]: Weapon }; - - constructor() { - this.#weapons = {}; - - document.addEventListener("commandModeOptionsChanged", () => {Object.values(this.#weapons).forEach((weapon: Weapon) => weapon.updateVisibility())}); - } - - /** - * - * @returns All the existing weapons, both active and destroyed - */ - getWeapons() { - return this.#weapons; - } - - /** Get a weapon by ID - * - * @param ID ID of the weapon - * @returns Weapon object, or null if input ID does not exist - */ - getWeaponByID(ID: number) { - if (ID in this.#weapons) - return this.#weapons[ID]; - else - return null; - } - - /** Add a new weapon to the manager - * - * @param ID ID of the new weapon - * @param category Either "Missile" or "Bomb". Determines what class will be used to create the new unit accordingly. - */ - addWeapon(ID: number, category: string) { - if (category){ - /* The name of the weapon category is exactly the same as the constructor name */ - var constructor = Weapon.getConstructor(category); - if (constructor != undefined) { - this.#weapons[ID] = new constructor(ID); - } - } - } - - /** Update the data of all the weapons. The data is directly decoded from the binary buffer received from the REST Server. This is necessary for performance and bandwidth reasons. - * - * @param buffer The arraybuffer, encoded according to the ICD defined in: TODO Add reference to ICD - * @returns The decoded updateTime of the data update. - */ - update(buffer: ArrayBuffer) { - /* Extract the data from the arraybuffer. Since data is encoded dynamically (not all data is always present, but rather only the data that was actually updated since the last request). - No a prori casting can be performed. On the contrary, the array is decoded incrementally, depending on the DataIndexes of the data. The actual data decoding is performed by the Weapon class directly. - Every time a piece of data is decoded the decoder seeker is incremented. */ - var dataExtractor = new DataExtractor(buffer); - - var updateTime = Number(dataExtractor.extractUInt64()); - - /* Run until all data is extracted or an error occurs */ - while (dataExtractor.getSeekPosition() < buffer.byteLength) { - /* Extract the weapon ID */ - const ID = dataExtractor.extractUInt32(); - - /* If the ID of the weapon does not yet exist, create the weapon, if the category is known. If it isn't, some data must have been lost and we need to wait for another update */ - if (!(ID in this.#weapons)) { - const datumIndex = dataExtractor.extractUInt8(); - if (datumIndex == DataIndexes.category) { - const category = dataExtractor.extractString(); - this.addWeapon(ID, category); - } - else { - /* Inconsistent data, we need to wait for a refresh */ - return updateTime; - } - } - /* Update the data of the weapon */ - this.#weapons[ID]?.setData(dataExtractor); - } - return updateTime; - } - - /** For a given weapon, it returns if and how it is being detected by other units. NOTE: this function will return how a weapon is being detected, i.e. how other units are detecting it. It will not return - * what the weapon is detecting (mostly because weapons can't detect units). - * - * @param weapon The unit of which to retrieve the "detected" methods. - * @returns Array of detection methods - */ - getWeaponDetectedMethods(weapon: Weapon) { - var detectionMethods: number[] = []; - var units = getApp().getUnitsManager().getUnits(); - for (let idx in units) { - if (units[idx].getAlive() && units[idx].getIsLeader() && units[idx].getCoalition() !== "neutral" && units[idx].getCoalition() != weapon.getCoalition()) - { - units[idx].getContacts().forEach((contact: Contact) => { - if (contact.ID == weapon.ID && !detectionMethods.includes(contact.detectionMethod)) - detectionMethods.push(contact.detectionMethod); - }); - } - } - return detectionMethods; - } -} \ No newline at end of file diff --git a/frontend/website/tsconfig.json b/frontend/website/tsconfig.json deleted file mode 100644 index 45b6293f..00000000 --- a/frontend/website/tsconfig.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - // "rootDirs": ["./src"], /* Specify the root folder within your source files. */ - // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - "typeRoots": [ - "./node_modules/@types" - ], /* Specify multiple folders that act like './node_modules/@types'. */ - "types": [ - "leaflet", - "geojson", - "node", - "formatcoords" - ], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - //"emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - //"outFile": "./@types/olympus/index.ts", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - }, - "include": [ - "src/**/*.ts", - "src/dom.d.ts" - ] -} \ No newline at end of file