Merge branch 'main' into 412-improve-importexport
4
.gitignore
vendored
@ -1,5 +1,8 @@
|
||||
bin
|
||||
/scripts/old
|
||||
/scripts/python/configurator/dist
|
||||
/scripts/python/configurator/build
|
||||
/scripts/python/configurator/venv
|
||||
.vs
|
||||
x64
|
||||
core.user
|
||||
@ -15,3 +18,4 @@ node_modules
|
||||
hgt
|
||||
/client/public/databases/units/old
|
||||
/client/plugins/databasemanager/index.js
|
||||
|
||||
|
||||
26
LEGAL
@ -6,9 +6,10 @@ Copyright (C) 2023 Veltro & Gang
|
||||
|
||||
DCS Olympus (the "MATERIAL" or "Software") is provided completely free
|
||||
to users subject to the it under both the terms of version 3 of the GNU
|
||||
General Public License as published by the Free Software Foundation, and
|
||||
the additional terms set out below; except where such terms conflict with this
|
||||
disclaimer, in which case, the terms of this disclaimer shall prevail.
|
||||
General Public License ("GPLv3") as published by the Free Software Foundation,
|
||||
and the additional terms set out below (the "Additional Terms"). In the event
|
||||
that the terms of GPLv3 conflict with the Additional Terms, the
|
||||
Additional Terms shall prevail.
|
||||
|
||||
The authors and/or copyright holders of the Software have not received any
|
||||
financial benefit in connection with the Software. In any event, the
|
||||
@ -17,15 +18,14 @@ implied, including but not limited to the warranties of merchantability,
|
||||
fitness for a particular purpose and non-infringement. In no event shall
|
||||
the authors and/or copyright holders be liable for any claim, damages or
|
||||
other liability, whether in an action of contract, tort or otherwise,
|
||||
arising from, out of or in connection with the Software or the use or o
|
||||
ther dealings in the Software.
|
||||
|
||||
Any party making use of the Software in any manner agrees to be
|
||||
bound by the terms set out in this disclaimer, version 3 of the GNU
|
||||
General Public Licence, and the Additional Terms below.
|
||||
arising from, out of or in connection with the Software or the use or
|
||||
other dealings in the Software.
|
||||
|
||||
THIS MATERIAL IS NOT MADE OR SUPPORTED BY EAGLE DYNAMICS SA.
|
||||
|
||||
Any party making use of the Software in any manner agrees to be bound by
|
||||
the entirety of this document.
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
@ -657,4 +657,12 @@ copy of the Program in return for a fee.
|
||||
GNU General Public Licence Version 3 above shall be governed by and
|
||||
interpreted in accordance with English Law and the parties submit to the
|
||||
exclusive jurisdiction of the English Courts.
|
||||
|
||||
2. Entire Agreement
|
||||
|
||||
The text of this document contains the entire understanding between
|
||||
you, the licensee, and the authors and/or copyright holders of the
|
||||
Software with respect to the subject matter to which it pertains.
|
||||
It supersedes all prior agreements and understandings (if applicable),
|
||||
oral or written, with respect to such matters.
|
||||
|
||||
|
||||
@ -1,17 +1,28 @@
|
||||
cd src
|
||||
msbuild olympus.sln /t:Rebuild /p:Configuration=Release
|
||||
cd ..
|
||||
|
||||
cd scripts/python/configurator
|
||||
call build_configurator.bat
|
||||
cd ../../..
|
||||
|
||||
cd client
|
||||
call npm install
|
||||
rmdir /s /q "hgt"
|
||||
call npm install
|
||||
call npm run emit-declarations
|
||||
call npm run build
|
||||
call npm run build-release
|
||||
|
||||
cd "plugins\controltips"
|
||||
call npm run build
|
||||
call npm install
|
||||
call npm run build-release
|
||||
cd "..\.."
|
||||
|
||||
cd "plugins\databasemanager"
|
||||
call npm run build
|
||||
call npm install
|
||||
call npm run build-release
|
||||
cd "..\.."
|
||||
|
||||
call npm prune --production
|
||||
cd ..
|
||||
|
||||
call "C:\Program Files (x86)\Inno Setup 6\iscc.exe" "installer\olympus.iss"
|
||||
|
||||
617
client/@types/olympus/index.d.ts
vendored
@ -13,11 +13,11 @@ declare module "contextmenus/contextmenu" {
|
||||
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
|
||||
* @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, y: number, latlng: LatLng): void;
|
||||
show(x?: number | undefined, y?: number | undefined, latlng?: LatLng | undefined): void;
|
||||
/** Hide the contextmenu
|
||||
*
|
||||
*/
|
||||
@ -83,7 +83,7 @@ declare module "controls/switch" {
|
||||
}
|
||||
declare module "constants/constants" {
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
import { MapMarkerControl } from "map/map";
|
||||
import { MapMarkerVisibilityControl } from "map/map";
|
||||
export const UNITS_URI = "units";
|
||||
export const WEAPONS_URI = "weapons";
|
||||
export const LOGS_URI = "logs";
|
||||
@ -150,6 +150,10 @@ declare module "constants/constants" {
|
||||
bounds: LatLngBounds;
|
||||
zoom: number;
|
||||
};
|
||||
Falklands: {
|
||||
bounds: LatLngBounds;
|
||||
zoom: number;
|
||||
};
|
||||
};
|
||||
export const mapLayers: {
|
||||
"ArcGIS Satellite": {
|
||||
@ -195,7 +199,7 @@ declare module "constants/constants" {
|
||||
export const visibilityControls: string[];
|
||||
export const visibilityControlsTypes: string[][];
|
||||
export const visibilityControlsTooltips: string[];
|
||||
export const MAP_MARKER_CONTROLS: MapMarkerControl[];
|
||||
export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[];
|
||||
export const IADSTypes: string[];
|
||||
export const IADSDensities: {
|
||||
[key: string]: number;
|
||||
@ -264,6 +268,10 @@ declare module "constants/constants" {
|
||||
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";
|
||||
@ -314,15 +322,40 @@ declare module "controls/dropdown" {
|
||||
#private;
|
||||
constructor(ID: string | null, callback: CallableFunction, options?: string[] | null, defaultText?: string);
|
||||
getContainer(): HTMLElement;
|
||||
setOptions(optionsList: string[], sort?: "" | "string" | "number" | "string+number"): void;
|
||||
/** 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. "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?: "" | "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;
|
||||
selectText(text: string): 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;
|
||||
getValue(): string;
|
||||
/** 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;
|
||||
@ -651,14 +684,14 @@ declare module "interfaces" {
|
||||
declare module "unit/databases/unitdatabase" {
|
||||
import { LatLng } from "leaflet";
|
||||
import { UnitBlueprint } from "interfaces";
|
||||
export class UnitDatabase {
|
||||
export abstract class UnitDatabase {
|
||||
#private;
|
||||
blueprints: {
|
||||
[key: string]: UnitBlueprint;
|
||||
};
|
||||
constructor(url?: string);
|
||||
load(callback: CallableFunction): void;
|
||||
getCategory(): string;
|
||||
abstract getCategory(): string;
|
||||
getByName(name: string): UnitBlueprint | null;
|
||||
getByLabel(label: string): UnitBlueprint | null;
|
||||
getBlueprints(includeDisabled?: boolean): {
|
||||
@ -679,6 +712,7 @@ declare module "unit/databases/unitdatabase" {
|
||||
generateTestGrid(initialPosition: LatLng): void;
|
||||
getSpawnPointsByLabel(label: string): number;
|
||||
getSpawnPointsByName(name: string): number;
|
||||
getUnkownUnit(name: string): UnitBlueprint;
|
||||
}
|
||||
}
|
||||
declare module "unit/databases/aircraftdatabase" {
|
||||
@ -774,7 +808,7 @@ declare module "other/utils" {
|
||||
ranges?: string[];
|
||||
eras?: string[];
|
||||
}): UnitBlueprint | null;
|
||||
export function getMarkerCategoryByName(name: string): "aircraft" | "helicopter" | "groundunit-other" | "navyunit" | "groundunit";
|
||||
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 base64ToBytes(base64: string): ArrayBufferLike;
|
||||
export function enumToState(state: number): string;
|
||||
@ -918,28 +952,6 @@ declare module "contextmenus/mapcontextmenu" {
|
||||
setCoalitionArea(coalitionArea: CoalitionArea): void;
|
||||
}
|
||||
}
|
||||
declare module "contextmenus/unitcontextmenu" {
|
||||
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
|
||||
*/
|
||||
setOptions(options: {
|
||||
[key: string]: {
|
||||
text: string;
|
||||
tooltip: string;
|
||||
};
|
||||
}, callback: CallableFunction): void;
|
||||
}
|
||||
}
|
||||
declare module "map/markers/targetmarker" {
|
||||
import { LatLngExpression, MarkerOptions } from "leaflet";
|
||||
import { CustomMarker } from "map/markers/custommarker";
|
||||
@ -1069,18 +1081,30 @@ declare module "map/rangecircle" {
|
||||
_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
|
||||
*
|
||||
* Just about everything is a unit - even missiles!
|
||||
*/
|
||||
export class Unit extends CustomMarker {
|
||||
export abstract class Unit extends CustomMarker {
|
||||
#private;
|
||||
ID: number;
|
||||
getAlive(): boolean;
|
||||
@ -1126,33 +1150,50 @@ declare module "unit/unit" {
|
||||
getShotsScatter(): number;
|
||||
getShotsIntensity(): number;
|
||||
getHealth(): number;
|
||||
static getConstructor(type: string): typeof GroundUnit | undefined;
|
||||
static getConstructor(type: string): typeof Aircraft | undefined;
|
||||
constructor(ID: number);
|
||||
getCategory(): string;
|
||||
/********************** Unit data *************************/
|
||||
setData(dataExtractor: DataExtractor): void;
|
||||
drawLines(): void;
|
||||
/** Get unit data collated into an object
|
||||
/********************** Abstract methods *************************/
|
||||
/** Get the unit category string
|
||||
*
|
||||
* @returns object populated by unit information which can also be retrieved using getters
|
||||
* @returns string The unit category
|
||||
*/
|
||||
getData(): UnitData;
|
||||
/**
|
||||
*
|
||||
* @returns string containing the marker category
|
||||
*/
|
||||
getMarkerCategory(): string;
|
||||
/** Get a database of information also in this unit's category
|
||||
*
|
||||
* @returns UnitDatabase
|
||||
*/
|
||||
getDatabase(): UnitDatabase | null;
|
||||
abstract getCategory(): string;
|
||||
/** Get the icon options
|
||||
* Used to configure how the marker appears on the map
|
||||
*
|
||||
* @returns ObjectIconOptions
|
||||
*/
|
||||
getIconOptions(): 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;
|
||||
/********************** 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
|
||||
@ -1168,17 +1209,7 @@ declare module "unit/unit" {
|
||||
* @returns boolean
|
||||
*/
|
||||
getSelected(): boolean;
|
||||
/** Set whether this unit is selectable
|
||||
*
|
||||
* @param selectable (boolean)
|
||||
*/
|
||||
setSelectable(selectable: boolean): void;
|
||||
/** Get whether this unit is selectable
|
||||
*
|
||||
* @returns boolean
|
||||
*/
|
||||
getSelectable(): boolean;
|
||||
/** Set the number of the hotgroup to which the unit belongs
|
||||
/** Set the number of the hotgroup to which the unit belongss
|
||||
*
|
||||
* @param hotgroup (number)
|
||||
*/
|
||||
@ -1203,6 +1234,11 @@ declare module "unit/unit" {
|
||||
* @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
|
||||
@ -1210,6 +1246,11 @@ declare module "unit/unit" {
|
||||
belongsToCommandedCoalition(): boolean;
|
||||
getType(): string;
|
||||
getSpawnPoints(): number | undefined;
|
||||
getDatabaseEntry(): import("interfaces").UnitBlueprint | undefined;
|
||||
getGroup(): Group | null;
|
||||
setGroup(group: Group | null): void;
|
||||
drawLines(): void;
|
||||
checkZoomRedraw(): boolean;
|
||||
/********************** Icon *************************/
|
||||
createIcon(): void;
|
||||
/********************** Visibility *************************/
|
||||
@ -1223,9 +1264,8 @@ declare module "unit/unit" {
|
||||
isInViewport(): boolean;
|
||||
canTargetPoint(): boolean;
|
||||
canRearm(): boolean;
|
||||
canLandAtPoint(): boolean;
|
||||
canAAA(): boolean;
|
||||
indirectFire(): boolean;
|
||||
isIndirectFire(): boolean;
|
||||
isTanker(): boolean;
|
||||
isAWACS(): boolean;
|
||||
/********************** Unit commands *************************/
|
||||
@ -1264,19 +1304,12 @@ declare module "unit/unit" {
|
||||
setShotsScatter(shotsScatter: number): void;
|
||||
setShotsIntensity(shotsIntensity: number): void;
|
||||
/***********************************************/
|
||||
getActions(): {
|
||||
[key: string]: {
|
||||
text: string;
|
||||
tooltip: string;
|
||||
type: string;
|
||||
};
|
||||
};
|
||||
executeAction(e: any, action: string): void;
|
||||
/***********************************************/
|
||||
onAdd(map: Map): this;
|
||||
getActionOptions(): {};
|
||||
onGroupChanged(member: Unit): void;
|
||||
showFollowOptions(units: Unit[]): void;
|
||||
applyFollowOptions(formation: string, units: Unit[]): void;
|
||||
}
|
||||
export class AirUnit extends Unit {
|
||||
export abstract class AirUnit extends Unit {
|
||||
getIconOptions(): {
|
||||
showState: boolean;
|
||||
showVvi: boolean;
|
||||
@ -1290,21 +1323,21 @@ declare module "unit/unit" {
|
||||
showCallsign: boolean;
|
||||
rotateToHeading: boolean;
|
||||
};
|
||||
getActions(): {
|
||||
[key: string]: {
|
||||
text: string;
|
||||
tooltip: string;
|
||||
type: string;
|
||||
};
|
||||
};
|
||||
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);
|
||||
@ -1321,15 +1354,13 @@ declare module "unit/unit" {
|
||||
showCallsign: boolean;
|
||||
rotateToHeading: boolean;
|
||||
};
|
||||
getActions(): {
|
||||
[key: string]: {
|
||||
text: string;
|
||||
tooltip: string;
|
||||
type: string;
|
||||
};
|
||||
};
|
||||
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);
|
||||
@ -1346,16 +1377,59 @@ declare module "unit/unit" {
|
||||
showCallsign: boolean;
|
||||
rotateToHeading: boolean;
|
||||
};
|
||||
getActions(): {
|
||||
[key: string]: {
|
||||
text: string;
|
||||
tooltip: string;
|
||||
type: string;
|
||||
};
|
||||
};
|
||||
getMarkerCategory(): string;
|
||||
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" {
|
||||
@ -1457,7 +1531,7 @@ declare module "contextmenus/airbasespawnmenu" {
|
||||
* @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, y: number): void;
|
||||
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.
|
||||
@ -1465,8 +1539,100 @@ declare module "contextmenus/airbasespawnmenu" {
|
||||
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 = {
|
||||
"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): 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<any>;
|
||||
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]: boolean;
|
||||
};
|
||||
isZooming(): boolean;
|
||||
getPreviousZoom(): number;
|
||||
getIsUnitProtected(unit: Unit): boolean;
|
||||
getMapMarkerVisibilityControls(): MapMarkerVisibilityControl[];
|
||||
}
|
||||
}
|
||||
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;
|
||||
@ -1474,6 +1640,8 @@ declare module "context/context" {
|
||||
export class Context {
|
||||
#private;
|
||||
constructor(config: ContextInterface);
|
||||
getAllowUnitCopying(): boolean;
|
||||
getAllowUnitPasting(): boolean;
|
||||
getUseSpawnMenu(): boolean;
|
||||
getUseUnitControlPanel(): boolean;
|
||||
getUseUnitInfoPanel(): boolean;
|
||||
@ -1532,96 +1700,6 @@ declare module "popups/popup" {
|
||||
setText(text: string): 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 MapMarkerControl = {
|
||||
"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): 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, y: number, latlng: L.LatLng): void;
|
||||
getUnitContextMenu(): UnitContextMenu;
|
||||
hideUnitContextMenu(): void;
|
||||
showAirbaseContextMenu(x: number, y: number, latlng: L.LatLng, airbase: Airbase): void;
|
||||
getAirbaseContextMenu(): AirbaseContextMenu;
|
||||
hideAirbaseContextMenu(): void;
|
||||
showAirbaseSpawnMenu(x: number, y: number, latlng: L.LatLng, airbase: Airbase): void;
|
||||
getAirbaseSpawnMenu(): AirbaseSpawnContextMenu;
|
||||
hideAirbaseSpawnMenu(): void;
|
||||
showCoalitionAreaContextMenu(x: number, y: number, latlng: L.LatLng, coalitionArea: CoalitionArea): void;
|
||||
getCoalitionAreaContextMenu(): CoalitionAreaContextMenu;
|
||||
hideCoalitionAreaContextMenu(): void;
|
||||
isZooming(): boolean;
|
||||
getMousePosition(): L.Point;
|
||||
getMouseCoordinates(): L.LatLng;
|
||||
spawnFromAirbase(e: any): void;
|
||||
centerOnUnit(ID: number | null): void;
|
||||
getCenterUnit(): Unit | null;
|
||||
setTheatre(theatre: string): void;
|
||||
getMiniMapLayerGroup(): L.LayerGroup<any>;
|
||||
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]: boolean;
|
||||
};
|
||||
unitIsProtected(unit: Unit): boolean;
|
||||
getMapMarkerControls(): MapMarkerControl[];
|
||||
}
|
||||
}
|
||||
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 "mission/missionmanager" {
|
||||
import { Airbase } from "mission/airbase";
|
||||
import { Bullseye } from "mission/bullseye";
|
||||
@ -1708,30 +1786,6 @@ declare module "panels/serverstatuspanel" {
|
||||
update(frameRate: number, load: number): void;
|
||||
}
|
||||
}
|
||||
declare module "toolbars/toolbar" {
|
||||
export class Toolbar {
|
||||
#private;
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string);
|
||||
show(): void;
|
||||
hide(): void;
|
||||
toggle(): void;
|
||||
getElement(): HTMLElement;
|
||||
getVisible(): boolean;
|
||||
}
|
||||
}
|
||||
declare module "toolbars/primarytoolbar" {
|
||||
import { Dropdown } from "controls/dropdown";
|
||||
import { Toolbar } from "toolbars/toolbar";
|
||||
export class PrimaryToolbar extends Toolbar {
|
||||
#private;
|
||||
constructor(ID: string);
|
||||
getMainDropdown(): Dropdown;
|
||||
}
|
||||
}
|
||||
declare module "panels/unitcontrolpanel" {
|
||||
import { Panel } from "panels/panel";
|
||||
export class UnitControlPanel extends Panel {
|
||||
@ -1794,12 +1848,36 @@ declare module "shortcut/shortcutmanager" {
|
||||
onKeyUp(callback: CallableFunction): void;
|
||||
}
|
||||
}
|
||||
declare module "toolbars/toolbar" {
|
||||
export class Toolbar {
|
||||
#private;
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string);
|
||||
show(): void;
|
||||
hide(): void;
|
||||
toggle(): void;
|
||||
getElement(): HTMLElement;
|
||||
getVisible(): boolean;
|
||||
}
|
||||
}
|
||||
declare module "toolbars/commandmodetoolbar" {
|
||||
import { Toolbar } from "toolbars/toolbar";
|
||||
export class CommandModeToolbar extends Toolbar {
|
||||
}
|
||||
}
|
||||
declare module "unit/citiesDatabase" {
|
||||
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;
|
||||
@ -1921,140 +1999,163 @@ declare module "unit/unitsmanager" {
|
||||
* @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.
|
||||
*/
|
||||
selectedUnitsAddDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number): void;
|
||||
addDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number, units?: Unit[] | null): void;
|
||||
/** Clear the destinations of all the selected units
|
||||
*
|
||||
*/
|
||||
selectedUnitsClearDestinations(): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsLandAt(latlng: LatLng): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsChangeSpeed(speedChange: string): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsChangeAltitude(altitudeChange: string): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetSpeed(speed: number): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetSpeedType(speedType: string): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetAltitude(altitude: number): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetAltitudeType(altitudeType: string): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetROE(ROE: string): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetReactionToThreat(reactionToThreat: string): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetEmissionsCountermeasures(emissionCountermeasure: string): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetOnOff(onOff: boolean): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetFollowRoads(followRoads: boolean): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetOperateAs(operateAsBool: boolean): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsAttackUnit(ID: number): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsRefuel(): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsFollowUnit(ID: number, offset?: {
|
||||
followUnit(ID: number, offset?: {
|
||||
"x": number;
|
||||
"y": number;
|
||||
"z": number;
|
||||
}, formation?: string): void;
|
||||
}, 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.
|
||||
*/
|
||||
selectedUnitsBombPoint(latlng: LatLng): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsCarpetBomb(latlng: LatLng): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsFireAtArea(latlng: LatLng): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSimulateFireFight(latlng: LatLng): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsScenicAAA(): void;
|
||||
scenicAAA(units?: Unit[] | null): void;
|
||||
/** Instruct units to enter into 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.
|
||||
*/
|
||||
selectedUnitsMissOnPurpose(): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsLandAtPoint(latlng: LatLng): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetShotsScatter(shotsScatter: number): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetShotsIntensity(shotsIntensity: number): void;
|
||||
setShotsIntensity(shotsIntensity: number, units?: Unit[] | null): void;
|
||||
/*********************** Control operations on selected units ************************/
|
||||
/** See getUnitsCategories for more info
|
||||
*
|
||||
@ -2070,42 +2171,46 @@ declare module "unit/unitsmanager" {
|
||||
/** Groups the selected units in a single (DCS) group, if all the units have the same category
|
||||
*
|
||||
*/
|
||||
selectedUnitsCreateGroup(): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetHotgroup(hotgroup: number): void;
|
||||
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.
|
||||
*/
|
||||
selectedUnitsAddToHotgroup(hotgroup: number): void;
|
||||
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
|
||||
*/
|
||||
selectedUnitsDelete(explosion?: boolean, explosionType?: string): void;
|
||||
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
|
||||
*/
|
||||
selectedUnitsComputeGroupDestination(latlng: LatLng, rotation: number): {
|
||||
computeGroupDestination(latlng: LatLng, rotation: number, units?: Unit[] | null): {
|
||||
[key: number]: LatLng;
|
||||
};
|
||||
/** Copy the selected units and store their properties in memory
|
||||
*
|
||||
*/
|
||||
selectedUnitsCopy(): void;
|
||||
copy(units?: Unit[] | null): void;
|
||||
/*********************** Unit manipulation functions ************************/
|
||||
/** Paste the copied units
|
||||
*
|
||||
* @returns True if units were pasted successfully
|
||||
*/
|
||||
pasteUnits(): false | undefined;
|
||||
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
|
||||
*
|
||||
|
||||
@ -43,7 +43,7 @@ if (config["server"] != undefined)
|
||||
module.exports = app;
|
||||
|
||||
const DemoDataGenerator = require('./demo.js');
|
||||
var demoDataGenerator = new DemoDataGenerator(app);
|
||||
var demoDataGenerator = new DemoDataGenerator(app, config);
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* _____ _____ _____ ____ _ *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* | __ \\ / ____|/ ____| / __ \\| | *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* | | | | | | (___ | | | | |_ _ _ __ ___ _ __ _ _ ___ *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* | | | | | \\___ \\ | | | | | | | | '_ ` _ \\| '_ \\| | | / __| *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* | |__| | |____ ____) | | |__| | | |_| | | | | | | |_) | |_| \\__ \\ *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* |_____/ \\_____|_____/ \\____/|_|\\__, |_| |_| |_| .__/ \\__,_|___/ *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* __/ | | | *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "* |___/ |_| *");
|
||||
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
|
||||
console.log('\x1b[36m%s\x1b[0m', "");
|
||||
console.log("Please wait while DCS Olympus Server starts up...");
|
||||
|
||||
var fs = require('fs');
|
||||
let rawdata = fs.readFileSync('../olympus.json');
|
||||
let config = JSON.parse(rawdata);
|
||||
@ -98,3 +111,6 @@ function onListening() {
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
||||
|
||||
console.log("DCS Olympus server v0.4.8 started correctly!")
|
||||
console.log("Waiting for connections...")
|
||||
|
||||
@ -14,7 +14,7 @@ const DEMO_WEAPONS_DATA = {
|
||||
}
|
||||
|
||||
class DemoDataGenerator {
|
||||
constructor(app)
|
||||
constructor(app, config)
|
||||
{
|
||||
app.get('/demo/units', (req, res) => this.units(req, res));
|
||||
app.get('/demo/weapons', (req, res) => this.weapons(req, res));
|
||||
@ -27,14 +27,14 @@ class DemoDataGenerator {
|
||||
|
||||
app.use('/demo', basicAuth({
|
||||
users: {
|
||||
'admin': 'password',
|
||||
'blue': 'bluepassword',
|
||||
'red': 'redpassword'
|
||||
'admin': config["authentication"]["gameMasterPassword"],
|
||||
'blue': config["authentication"]["blueCommanderPassword"],
|
||||
'red': config["authentication"]["redCommanderPassword"]
|
||||
},
|
||||
}))
|
||||
|
||||
|
||||
let baseData = { alive: true, human: false, controlled: true, coalition: 2, country: 0, unitName: "Cool guy", groupName: "Cool group 1", state: 1, task: "Being cool!",
|
||||
let baseData = { alive: true, human: false, controlled: true, coalition: 2, country: 0, unitName: "Cool guy", groupName: "Cool group 1", state: 13, task: "Being cool!",
|
||||
hasTask: true, position: { lat: 37, lng: -116, alt: 1000 }, speed: 200, horizontalVelocity: 200, verticalVelicity: 0, heading: 45, isActiveTanker: false, isActiveAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
@ -52,9 +52,9 @@ class DemoDataGenerator {
|
||||
isLeader: true
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
UNCOMMENT TO TEST ALL UNITS
|
||||
// UNCOMMENT TO TEST ALL UNITS ****************
|
||||
|
||||
var databases = Object.assign({}, aircraftDatabase, helicopterDatabase, groundUnitDatabase, navyUnitDatabase);
|
||||
var t = Object.keys(databases).length;
|
||||
@ -91,6 +91,7 @@ class DemoDataGenerator {
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
let idx = 1;
|
||||
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
|
||||
DEMO_UNIT_DATA[idx].name = "S_75M_Volhov";
|
||||
@ -114,6 +115,44 @@ class DemoDataGenerator {
|
||||
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
|
||||
DEMO_UNIT_DATA[idx].category = "GroundUnit";
|
||||
DEMO_UNIT_DATA[idx].isLeader = false;
|
||||
|
||||
|
||||
idx += 1;
|
||||
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
|
||||
DEMO_UNIT_DATA[idx].name = "F-14B";
|
||||
DEMO_UNIT_DATA[idx].groupName = `Group-1`;
|
||||
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
|
||||
DEMO_UNIT_DATA[idx].category = "Aircraft";
|
||||
DEMO_UNIT_DATA[idx].isLeader = false;
|
||||
|
||||
idx += 1;
|
||||
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
|
||||
DEMO_UNIT_DATA[idx].name = "Infantry AK";
|
||||
DEMO_UNIT_DATA[idx].groupName = `Group-2`;
|
||||
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
|
||||
DEMO_UNIT_DATA[idx].category = "GroundUnit";
|
||||
DEMO_UNIT_DATA[idx].isLeader = true;
|
||||
DEMO_UNIT_DATA[idx].coalition = 0;
|
||||
DEMO_UNIT_DATA[idx].operateAs = 2;
|
||||
|
||||
idx += 1;
|
||||
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
|
||||
DEMO_UNIT_DATA[idx].name = "Infantry AK";
|
||||
DEMO_UNIT_DATA[idx].groupName = `Group-3`;
|
||||
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
|
||||
DEMO_UNIT_DATA[idx].category = "GroundUnit";
|
||||
DEMO_UNIT_DATA[idx].isLeader = true;
|
||||
DEMO_UNIT_DATA[idx].coalition = 0;
|
||||
DEMO_UNIT_DATA[idx].operateAs = 1;
|
||||
|
||||
idx += 1;
|
||||
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
|
||||
DEMO_UNIT_DATA[idx].name = "KC-135";
|
||||
DEMO_UNIT_DATA[idx].groupName = `Group-4`;
|
||||
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
|
||||
DEMO_UNIT_DATA[idx].category = "Aircraft";
|
||||
DEMO_UNIT_DATA[idx].isLeader = true;
|
||||
|
||||
|
||||
this.startTime = Date.now();
|
||||
}
|
||||
|
||||
1256
client/package-lock.json
generated
@ -2,10 +2,11 @@
|
||||
"name": "DCSOlympus",
|
||||
"node-main": "./bin/www",
|
||||
"main": "http://localhost:3000",
|
||||
"version": "v0.4.6-alpha",
|
||||
"version": "v0.4.8-alpha",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "browserify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] && copy.bat",
|
||||
"build": "browserify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] && copy.bat",
|
||||
"build-release": "browserify .\\src\\index.ts -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] && copy.bat",
|
||||
"emit-declarations": "tsc --project tsconfig.json --declaration --emitDeclarationOnly --outfile ./@types/olympus/index.d.ts",
|
||||
"copy": "copy.bat",
|
||||
"start": "node ./bin/www",
|
||||
@ -20,6 +21,7 @@
|
||||
"ejs": "^3.1.8",
|
||||
"express": "~4.16.1",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"js-sha256": "^0.10.1",
|
||||
"leaflet-gesture-handling": "^1.2.2",
|
||||
"morgan": "~1.9.1",
|
||||
"save": "^2.9.0",
|
||||
@ -48,10 +50,9 @@
|
||||
"nodemon": "^2.0.20",
|
||||
"requirejs": "^2.3.6",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tinyify": "^4.0.0",
|
||||
"tsify": "^5.0.4",
|
||||
"tslib": "latest",
|
||||
"typedoc": "^0.24.8",
|
||||
"typedoc-umlclass": "^0.7.1",
|
||||
"typescript": "^4.9.4",
|
||||
"usng.js": "^0.4.5",
|
||||
"watchify": "^4.0.0"
|
||||
|
||||
@ -1,384 +0,0 @@
|
||||
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
||||
if (kind === "m") throw new TypeError("Private method is not writable");
|
||||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
||||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
||||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
||||
};
|
||||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
||||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
||||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
||||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
||||
};
|
||||
var _ControlTipsPlugin_instances, _ControlTipsPlugin_element, _ControlTipsPlugin_app, _ControlTipsPlugin_shortcutManager, _ControlTipsPlugin_cursorIsHoveringOverUnit, _ControlTipsPlugin_cursorIsHoveringOverAirbase, _ControlTipsPlugin_mouseoverElement, _ControlTipsPlugin_updateTips;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ControlTipsPlugin = void 0;
|
||||
const SHOW_CONTROL_TIPS = "Show control tips";
|
||||
class ControlTipsPlugin {
|
||||
constructor() {
|
||||
_ControlTipsPlugin_instances.add(this);
|
||||
_ControlTipsPlugin_element.set(this, void 0);
|
||||
_ControlTipsPlugin_app.set(this, void 0);
|
||||
_ControlTipsPlugin_shortcutManager.set(this, void 0);
|
||||
_ControlTipsPlugin_cursorIsHoveringOverUnit.set(this, false);
|
||||
_ControlTipsPlugin_cursorIsHoveringOverAirbase.set(this, false);
|
||||
_ControlTipsPlugin_mouseoverElement.set(this, void 0);
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_element, document.createElement("div"), "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_element, "f").id = "control-tips-panel";
|
||||
document.body.appendChild(__classPrivateFieldGet(this, _ControlTipsPlugin_element, "f"));
|
||||
}
|
||||
getName() {
|
||||
return "Control Tips Plugin";
|
||||
}
|
||||
initialize(app) {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_app, app, "f");
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_shortcutManager, __classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getShortcutManager(), "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_shortcutManager, "f").onKeyDown(() => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_shortcutManager, "f").onKeyUp(() => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("airbaseMouseover", (ev) => {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverAirbase, true, "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("airbaseMouseout", (ev) => {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverAirbase, false, "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("unitDeselection", (ev) => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("unitMouseover", (ev) => {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverUnit, true, "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("unitMouseout", (ev) => {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverUnit, false, "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("unitsSelection", (ev) => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("mapVisibilityOptionsChanged", () => {
|
||||
this.toggle(!__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getMap().getVisibilityOptions()[SHOW_CONTROL_TIPS]);
|
||||
});
|
||||
document.addEventListener("mouseover", (ev) => {
|
||||
if (ev.target instanceof HTMLElement) {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_mouseoverElement, ev.target, "f");
|
||||
}
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("mouseup", (ev) => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getMap().addVisibilityOption(SHOW_CONTROL_TIPS, true);
|
||||
return true;
|
||||
}
|
||||
getElement() {
|
||||
return __classPrivateFieldGet(this, _ControlTipsPlugin_element, "f");
|
||||
}
|
||||
toggle(bool) {
|
||||
this.getElement().classList.toggle("hide", bool);
|
||||
}
|
||||
}
|
||||
exports.ControlTipsPlugin = ControlTipsPlugin;
|
||||
_ControlTipsPlugin_element = new WeakMap(), _ControlTipsPlugin_app = new WeakMap(), _ControlTipsPlugin_shortcutManager = new WeakMap(), _ControlTipsPlugin_cursorIsHoveringOverUnit = new WeakMap(), _ControlTipsPlugin_cursorIsHoveringOverAirbase = new WeakMap(), _ControlTipsPlugin_mouseoverElement = new WeakMap(), _ControlTipsPlugin_instances = new WeakSet(), _ControlTipsPlugin_updateTips = function _ControlTipsPlugin_updateTips() {
|
||||
const combos = [
|
||||
{
|
||||
"keys": [],
|
||||
"tips": [
|
||||
{
|
||||
"key": `SHIFT`,
|
||||
"action": `Box select`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"showIfUnitSelected": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": `Deselect`,
|
||||
"showIfUnitSelected": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse1+drag`,
|
||||
"action": `Move map`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"showIfUnitSelected": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Spawn menu`,
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Quick options`,
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Airbase menu`,
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Set first waypoint`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2 (hold)`,
|
||||
"action": `Interact (ground)`,
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Shift`,
|
||||
"action": "<em> in formation...</em>",
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 2
|
||||
},
|
||||
{
|
||||
"key": "CTRL",
|
||||
"action": "<em> ... more</em>",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL",
|
||||
"action": " Pin tool",
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL+Mouse2",
|
||||
"action": " Airbase menu",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle Blue/Red",
|
||||
"mouseoverSelector": "#coalition-switch .ol-switch-fill"
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": "Set Neutral",
|
||||
"mouseoverSelector": "#coalition-switch .ol-switch-fill"
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle time display",
|
||||
"mouseoverSelector": "#connection-status-panel[data-is-connected] #connection-status-message abbr"
|
||||
},
|
||||
{
|
||||
"key": `Mouse1 or Z`,
|
||||
"action": "Change location system",
|
||||
"mouseoverSelector": "#coordinates-tool, #coordinates-tool *"
|
||||
},
|
||||
{
|
||||
"key": `Comma`,
|
||||
"action": "Decrease precision",
|
||||
"mouseoverSelector": `#coordinates-tool[data-location-system="MGRS"], #coordinates-tool[data-location-system="MGRS"] *`
|
||||
},
|
||||
{
|
||||
"key": `Period`,
|
||||
"action": "Increase precision",
|
||||
"mouseoverSelector": `#coordinates-tool[data-location-system="MGRS"], #coordinates-tool[data-location-system="MGRS"] *`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ControlLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle pin",
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle selection",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Add waypoint`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Interact (airbase)`,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Interact (unit)`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": true,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Shift`,
|
||||
"action": "<em> in formation...</em>",
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 2
|
||||
},
|
||||
{
|
||||
"key": `[Num 1-9]`,
|
||||
"action": "Set hotgroup",
|
||||
"showIfUnitSelected": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ShiftLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `Mouse1+drag`,
|
||||
"action": "Box select",
|
||||
"showIfUnitSelected": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": "Set first formation waypoint",
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 2
|
||||
},
|
||||
{
|
||||
"key": `[Num 1-9]`,
|
||||
"action": "Add to hotgroup",
|
||||
"showIfUnitSelected": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL",
|
||||
"action": "<em> ... more</em>",
|
||||
"minSelectedUnits": 2,
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"unitsMustBeControlled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ControlLeft", "ShiftLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": "Add formation waypoint",
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 2,
|
||||
"unitsMustBeControlled": true
|
||||
}, {
|
||||
"key": `[Num 1-9]`,
|
||||
"action": "Add hotgroup to selection",
|
||||
"callback": (tip) => {
|
||||
return (Object.values(__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getUnitsManager().getUnits()).some((unit) => {
|
||||
return unit.getHotgroup();
|
||||
}));
|
||||
},
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
const currentCombo = combos.find((combo) => __classPrivateFieldGet(this, _ControlTipsPlugin_shortcutManager, "f").keyComboMatches(combo.keys)) || combos[0];
|
||||
const element = this.getElement();
|
||||
element.innerHTML = "";
|
||||
let numSelectedUnits = 0;
|
||||
let numSelectedControlledUnits = 0;
|
||||
let unitSelectionContainsControlled = false;
|
||||
if (__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getUnitsManager()) {
|
||||
let selectedUnits = Object.values(__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getUnitsManager().getSelectedUnits());
|
||||
numSelectedUnits = selectedUnits.length;
|
||||
numSelectedControlledUnits = selectedUnits.filter((unit) => unit.getControlled()).length;
|
||||
unitSelectionContainsControlled = numSelectedControlledUnits > 0;
|
||||
}
|
||||
const tipsIncludesActiveMouseover = (currentCombo.tips.some((tip) => {
|
||||
if (!tip.mouseoverSelector) {
|
||||
return false;
|
||||
}
|
||||
if (__classPrivateFieldGet(this, _ControlTipsPlugin_mouseoverElement, "f") instanceof HTMLElement === false) {
|
||||
return false;
|
||||
}
|
||||
if (!__classPrivateFieldGet(this, _ControlTipsPlugin_mouseoverElement, "f").matches(tip.mouseoverSelector)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
currentCombo.tips.filter((tip) => {
|
||||
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 !== __classPrivateFieldGet(this, _ControlTipsPlugin_cursorIsHoveringOverAirbase, "f")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (typeof tip.showIfHoveringOverUnit === "boolean") {
|
||||
if (tip.showIfHoveringOverUnit !== __classPrivateFieldGet(this, _ControlTipsPlugin_cursorIsHoveringOverUnit, "f")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (tipsIncludesActiveMouseover && (typeof tip.mouseoverSelector !== "string" || !__classPrivateFieldGet(this, _ControlTipsPlugin_mouseoverElement, "f").matches(tip.mouseoverSelector))) {
|
||||
return false;
|
||||
}
|
||||
if (!tipsIncludesActiveMouseover && typeof tip.mouseoverSelector === "string") {
|
||||
return false;
|
||||
}
|
||||
if (typeof tip.callback === "function" && !tip.callback(tip)) {
|
||||
return false;
|
||||
}
|
||||
element.innerHTML += `<div><span class="key">${tip.key}</span><span class="action">${tip.action}</span></div>`;
|
||||
});
|
||||
};
|
||||
|
||||
},{}],2:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const controltipsplugin_1 = require("./controltipsplugin");
|
||||
globalThis.getOlympusPlugin = () => {
|
||||
return new controltipsplugin_1.ControlTipsPlugin();
|
||||
};
|
||||
|
||||
},{"./controltipsplugin":1}]},{},[2]);
|
||||
6251
client/plugins/controltips/package-lock.json
generated
@ -3,8 +3,27 @@
|
||||
"version": "v0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat"
|
||||
"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": {}
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ export class ControlTipsPlugin implements OlympusPlugin {
|
||||
this.#updateTips();
|
||||
});
|
||||
|
||||
document.addEventListener("mapVisibilityOptionsChanged", () => {
|
||||
document.addEventListener("mapOptionsChanged", () => {
|
||||
this.toggle( !this.#app.getMap().getVisibilityOptions()[SHOW_CONTROL_TIPS] );
|
||||
});
|
||||
|
||||
|
||||
6203
client/plugins/databasemanager/package-lock.json
generated
@ -4,10 +4,29 @@
|
||||
"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'] -p [ tsify --noImplicitAny ]"
|
||||
"watch": "watchify ./src/index.ts --debug -o ../../public/plugins/databasemanager/index.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js']"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {}
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ import { NavyUnitEditor } from "./navyuniteditor";
|
||||
*/
|
||||
|
||||
export class DatabaseManagerPlugin implements OlympusPlugin {
|
||||
#app: OlympusApp | null = null;
|
||||
#app!: OlympusApp;
|
||||
|
||||
#element: HTMLElement;
|
||||
#mainContentContainer: HTMLElement;
|
||||
@ -157,6 +157,15 @@ export class DatabaseManagerPlugin implements OlympusPlugin {
|
||||
*/
|
||||
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();
|
||||
@ -169,7 +178,7 @@ export class DatabaseManagerPlugin implements OlympusPlugin {
|
||||
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 - 1, 0, mainButtonDiv);
|
||||
arr.splice(arr.length - 3, 0, mainButtonDiv);
|
||||
toolbar.getMainDropdown().setOptionsElements(arr);
|
||||
mainButton.onclick = () => {
|
||||
toolbar.getMainDropdown().close();
|
||||
@ -197,6 +206,9 @@ export class DatabaseManagerPlugin implements OlympusPlugin {
|
||||
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
|
||||
|
||||
@ -1294,7 +1294,7 @@
|
||||
"ALBATROS": {
|
||||
"name": "albatros",
|
||||
"coalition": "red",
|
||||
"type": "Frigade",
|
||||
"type": "Frigate",
|
||||
"era": "Early Cold War",
|
||||
"label": "Albatros (Grisha-5)",
|
||||
"shortLabel": "Albatros",
|
||||
@ -1610,7 +1610,7 @@
|
||||
"era": "",
|
||||
"label": "LS Samuel Chase",
|
||||
"shortLabel": "LS Samuel Chase",
|
||||
"type": "Landing SHip",
|
||||
"type": "Landing Ship",
|
||||
"enabled": true,
|
||||
"liveries": {},
|
||||
"acquisitionRange": 0,
|
||||
|
||||
@ -64,7 +64,9 @@
|
||||
"aimTime": 5,
|
||||
"shotsToFire": 100,
|
||||
"markerFile": "groundunit-artillery",
|
||||
"tags": "120mm"
|
||||
"tags": "120mm",
|
||||
"indirectFire": true,
|
||||
"shotsBaseInterval": 300
|
||||
},
|
||||
"2S6 Tunguska": {
|
||||
"name": "2S6 Tunguska",
|
||||
@ -1120,7 +1122,8 @@
|
||||
"cost": 15000000,
|
||||
"tags": "Radar, CA",
|
||||
"markerFile": "groundunit-aaa",
|
||||
"canAAA": true
|
||||
"canAAA": true,
|
||||
"shotsBaseScatter": null
|
||||
},
|
||||
"Grad-URAL": {
|
||||
"name": "Grad-URAL",
|
||||
@ -2387,7 +2390,8 @@
|
||||
"shotsToFire": 100,
|
||||
"tags": "Russian type 1",
|
||||
"markerFile": "groundunit-infantry",
|
||||
"canAAA": true
|
||||
"canAAA": true,
|
||||
"aimMethodRange": 3600
|
||||
},
|
||||
"KAMAZ Truck": {
|
||||
"name": "KAMAZ Truck",
|
||||
@ -7221,7 +7225,7 @@
|
||||
"name": "Infantry AK Ins",
|
||||
"coalition": "red",
|
||||
"era": "Early Cold War",
|
||||
"label": "AK-74",
|
||||
"label": "Insurgent AK-74",
|
||||
"shortLabel": "AK-74 (Ins)",
|
||||
"type": "Infantry",
|
||||
"enabled": true,
|
||||
@ -7238,7 +7242,8 @@
|
||||
"shotsToFire": 100,
|
||||
"tags": "Insurgent",
|
||||
"markerFile": "groundunit-infantry",
|
||||
"canAAA": true
|
||||
"canAAA": true,
|
||||
"aimMethodRange": 3600
|
||||
},
|
||||
"MLRS FDDM": {
|
||||
"name": "MLRS FDDM",
|
||||
@ -7283,7 +7288,8 @@
|
||||
"shotsToFire": 100,
|
||||
"tags": "Russian type 2",
|
||||
"markerFile": "groundunit-infantry",
|
||||
"canAAA": true
|
||||
"canAAA": true,
|
||||
"aimMethodRange": 3600
|
||||
},
|
||||
"Infantry AK ver3": {
|
||||
"name": "Infantry AK ver3",
|
||||
@ -7306,7 +7312,8 @@
|
||||
"shotsToFire": 100,
|
||||
"tags": "Russian type 3",
|
||||
"markerFile": "groundunit-infantry",
|
||||
"canAAA": true
|
||||
"canAAA": true,
|
||||
"aimMethodRange": 3600
|
||||
},
|
||||
"Smerch_HE": {
|
||||
"name": "Smerch_HE",
|
||||
@ -8039,7 +8046,7 @@
|
||||
"canRearm": false,
|
||||
"muzzleVelocity": 1200,
|
||||
"barrelHeight": 3,
|
||||
"aimTime": 11,
|
||||
"aimTime": 20,
|
||||
"shotsToFire": 100,
|
||||
"cost": 750000,
|
||||
"tags": "CA",
|
||||
|
||||
@ -1294,7 +1294,7 @@
|
||||
"ALBATROS": {
|
||||
"name": "albatros",
|
||||
"coalition": "red",
|
||||
"type": "Frigade",
|
||||
"type": "Frigate",
|
||||
"era": "Early Cold War",
|
||||
"label": "Albatros (Grisha-5)",
|
||||
"shortLabel": "Albatros",
|
||||
@ -1610,7 +1610,7 @@
|
||||
"era": "",
|
||||
"label": "LS Samuel Chase",
|
||||
"shortLabel": "LS Samuel Chase",
|
||||
"type": "Landing SHip",
|
||||
"type": "Landing Ship",
|
||||
"enabled": true,
|
||||
"liveries": {},
|
||||
"acquisitionRange": 0,
|
||||
|
||||
BIN
client/public/images/favicons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
client/public/images/favicons/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
client/public/images/favicons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
client/public/images/favicons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 779 B |
BIN
client/public/images/favicons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
client/public/images/favicons/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
16
client/public/images/favicons/site.webmanifest
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "DCS Olympus",
|
||||
"short_name": "DCS Olympus",
|
||||
"icons": [{
|
||||
"src": "/images/favicons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "/images/favicons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
@ -329,6 +329,19 @@
|
||||
background-image: url("/resources/theme/images/states/awacs.svg");
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-state="miss-on-purpose"] .unit-state {
|
||||
background-image: url("/resources/theme/images/states/miss-on-purpose.svg");
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-state="scenic-aaa"] .unit-state {
|
||||
background-image: url("/resources/theme/images/states/scenic-aaa.svg");
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-state="simulate-fire-fight"] .unit-state {
|
||||
background-image: url("/resources/theme/images/states/simulate-fire-fight.svg");
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit"] .unit-health::before {
|
||||
background-image: url("/resources/theme/images/icons/health.svg");
|
||||
background-repeat: no-repeat;
|
||||
|
||||
@ -247,6 +247,10 @@
|
||||
column-gap: 5px;
|
||||
}
|
||||
|
||||
.unit-label-count-container>*:first-child {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.unit-label-count-container button {
|
||||
display: flex !important;
|
||||
flex-direction: row;
|
||||
|
||||
@ -2,6 +2,69 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
|
||||
#roe-buttons-container button,
|
||||
#reaction-to-threat-buttons-container button,
|
||||
#emissions-countermeasures-buttons-container button,
|
||||
#shots-scatter-buttons-container button
|
||||
#shots-intensity-buttons-container button {
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--accent-light-blue);
|
||||
display: flex;
|
||||
height: 30px;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
#reaction-to-threat-buttons-container button:not(:first-child) svg {
|
||||
width: 150%;
|
||||
margin: -5px;
|
||||
}
|
||||
|
||||
#unit-control-panel .ol-option-button button.selected {
|
||||
background-color: white;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
#unit-control-panel .ol-option-button button.selected svg * {
|
||||
fill: var(--background-steel);
|
||||
stroke: var(--background-steel);
|
||||
}
|
||||
|
||||
#rapid-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 5px;
|
||||
height: fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
#rapid-controls button {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
#rapid-controls button.pulse {
|
||||
animation: pulse 1.5s linear infinite;
|
||||
}
|
||||
|
||||
#rapid-controls svg {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
fill: white;
|
||||
stroke: white;
|
||||
}
|
||||
|
||||
#rapid-controls button:before {
|
||||
display: inline-block;
|
||||
filter: invert(100%);
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#unit-control-panel {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -55,10 +118,6 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
}
|
||||
}
|
||||
|
||||
#unit-control-panel h3 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#unit-control-panel #selected-units-container {
|
||||
align-items: left;
|
||||
border-radius: var(--border-radius-md);
|
||||
@ -72,9 +131,9 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
|
||||
#unit-control-panel #selected-units-container button {
|
||||
align-items: center;
|
||||
border-radius: var(--border-radius-md);
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
font-size: 11px;
|
||||
font-size: 13px;
|
||||
height: 32px;
|
||||
justify-content: space-between;
|
||||
margin-right: 5px;
|
||||
@ -88,6 +147,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
content: attr(data-label);
|
||||
font-size: 10px;
|
||||
padding: 4px 6px;
|
||||
padding-right: 7px;
|
||||
white-space: nowrap;
|
||||
width: fit-content;
|
||||
}
|
||||
@ -103,7 +163,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
padding: 4px;
|
||||
padding-left: 0;
|
||||
padding-left: 7px;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@ -204,27 +264,33 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
content: "GS";
|
||||
}
|
||||
|
||||
#unit-control-panel .ol-slider-value {
|
||||
.switch-control .ol-switch {
|
||||
height: 23px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
|
||||
.ol-slider-value {
|
||||
color: var(--accent-light-blue);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#unit-control-panel .switch-control {
|
||||
.switch-control {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#unit-control-panel .switch-control h4 {
|
||||
.switch-control h4 {
|
||||
margin: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#unit-control-panel .switch-control h4 img {
|
||||
.switch-control h4 img {
|
||||
height: 15px;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
@ -232,56 +298,60 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
opacity: 80%;
|
||||
}
|
||||
|
||||
#unit-control-panel .switch-control .ol-switch {
|
||||
height: 25px;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
#unit-control-panel .switch-control .ol-switch-fill {
|
||||
background-color: var(--accent-light-blue);
|
||||
}
|
||||
|
||||
#unit-control-panel .switch-control .ol-switch-fill::after {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#unit-control-panel .switch-control .ol-switch[data-value="true"]>.ol-switch-fill::before {
|
||||
content: "YES";
|
||||
}
|
||||
|
||||
#unit-control-panel .switch-control .ol-switch[data-value="false"]>.ol-switch-fill::before {
|
||||
content: "NO";
|
||||
}
|
||||
|
||||
#operate-as-switch[data-value="true"] .ol-switch-fill {
|
||||
background-color: var(--accent-light-blue);
|
||||
}
|
||||
|
||||
#operate-as-switch[data-value="false"] .ol-switch-fill {
|
||||
background-color: var(--primary-red);
|
||||
}
|
||||
|
||||
#operate-as-switch[data-value="true"]>.ol-switch-fill::before {
|
||||
content: "BLUE" !important;
|
||||
}
|
||||
|
||||
#operate-as-switch[data-value="false"]>.ol-switch-fill::before {
|
||||
content: "RED" !important;
|
||||
}
|
||||
|
||||
#advanced-settings-div {
|
||||
position: relative;
|
||||
column-gap: 5px;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
display: flex;
|
||||
height: fit-content;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#advanced-settings-div>*:nth-child(2) {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#advanced-settings-div>button {
|
||||
#advanced-settings-div > button {
|
||||
background-color: var(--background-grey);
|
||||
box-shadow: 0px 2px 5px #000A;
|
||||
font-size:13px;
|
||||
height: 40px;
|
||||
padding:0 20px;
|
||||
}
|
||||
|
||||
#delete-options {
|
||||
font-size:13px;
|
||||
}
|
||||
|
||||
#delete-options.ol-select > .ol-select-value:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
#delete-options.ol-select > .ol-select-value svg {
|
||||
background-color: transparent;
|
||||
position: absolute;
|
||||
right:2px;
|
||||
translate:0 1px;
|
||||
}
|
||||
|
||||
#delete-options.ol-select > .ol-select-value svg * {
|
||||
fill: var(--primary-red);
|
||||
}
|
||||
|
||||
#delete-options * {
|
||||
background-color: var(--background-steel);
|
||||
}
|
||||
|
||||
#delete-options.ol-select > .ol-select-value:hover,
|
||||
#delete-options .ol-select-options > div:not(.hr):hover,
|
||||
#delete-options .ol-select-options > div:not(.hr):hover button,
|
||||
#delete-options .ol-select-options > div hr {
|
||||
background-color: var(--background-grey);
|
||||
}
|
||||
|
||||
#delete-options .ol-select-options > div:first-of-type {
|
||||
margin-top:12px;
|
||||
padding-top:0;
|
||||
}
|
||||
|
||||
#delete-options .ol-select-options > div:last-of-type {
|
||||
margin-bottom:12px;
|
||||
padding-bottom:0;
|
||||
}
|
||||
|
||||
#delete-options button {
|
||||
@ -291,11 +361,16 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
}
|
||||
|
||||
#delete-options button svg {
|
||||
background-color: transparent;
|
||||
margin-right: 10px;
|
||||
width: 18px;
|
||||
max-height: 18px;
|
||||
}
|
||||
|
||||
#delete-options button svg * {
|
||||
stroke: red;
|
||||
}
|
||||
|
||||
/* Element visibility control */
|
||||
#unit-control-panel:not([data-show-categories-tooltip]) #categories-tooltip,
|
||||
#unit-control-panel:not([data-show-speed-slider]) #speed-slider,
|
||||
|
||||
@ -23,7 +23,7 @@ body {
|
||||
}
|
||||
|
||||
.hidden-cursor {
|
||||
cursor: none !important;
|
||||
/*cursor: none !important;*/
|
||||
}
|
||||
|
||||
.hidden-cursor * {
|
||||
@ -232,7 +232,6 @@ form {
|
||||
|
||||
.ol-select>.ol-select-options>div {
|
||||
background-color: var(--background-grey);
|
||||
box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25);
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
padding: 2px 15px;
|
||||
@ -687,6 +686,19 @@ nav.ol-panel> :last-child {
|
||||
width:10px;
|
||||
}
|
||||
|
||||
@keyframes lock-prompt {
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ol-navbar-buttons-group > .protectable > button[data-protected].lock.prompt svg {
|
||||
animation: lock-prompt .25s alternate infinite;
|
||||
}
|
||||
|
||||
.ol-navbar-buttons-group > .protectable > button.lock svg.locked * {
|
||||
fill:white !important;
|
||||
}
|
||||
@ -863,6 +875,43 @@ nav.ol-panel> :last-child {
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
#loading-screen {
|
||||
display: flex;
|
||||
background-image: linear-gradient(var(--background-steel), var(--background-grey));
|
||||
height: 100%;
|
||||
left: 0px;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
z-index: 999999;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
row-gap: 20px;
|
||||
}
|
||||
|
||||
#loading-screen img {
|
||||
height: 300px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
#loading-screen div {
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
animation: blinker 3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes blinker {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
opacity: 0%;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
#authentication-form {
|
||||
align-items: end;
|
||||
column-gap: 10px;
|
||||
@ -1426,20 +1475,54 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
transform: translateX(calc((var(--width) - var(--height)) * 0.5));
|
||||
}
|
||||
|
||||
.ol-contexmenu-panel {
|
||||
padding: 20px;
|
||||
.switch-control.yes-no .ol-switch[data-value="true"] .ol-switch-fill {
|
||||
background-color: var(--accent-light-blue);
|
||||
}
|
||||
|
||||
.ol-coalition-switch[data-value="false"]>.ol-switch-fill {
|
||||
background-color: var(--primary-blue);
|
||||
.switch-control.yes-no .ol-switch[data-value="false"] .ol-switch-fill {
|
||||
background-color: var(--ol-switch-off);
|
||||
}
|
||||
|
||||
.ol-coalition-switch[data-value="true"]>.ol-switch-fill {
|
||||
background-color: var(--primary-red);
|
||||
.switch-control.yes-no .ol-switch[data-value="undefined"] .ol-switch-fill {
|
||||
background-color: var(--ol-switch-undefined);
|
||||
}
|
||||
|
||||
.ol-coalition-switch[data-value="undefined"]>.ol-switch-fill {
|
||||
background-color: var(--primary-neutral);
|
||||
.switch-control.coalition .ol-switch>.ol-switch-fill::before,
|
||||
.switch-control.yes-no .ol-switch>.ol-switch-fill::before {
|
||||
translate:-100% 0;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.switch-control.yes-no .ol-switch[data-value="true"]>.ol-switch-fill::before {
|
||||
content: "YES";
|
||||
}
|
||||
|
||||
.switch-control.yes-no .ol-switch[data-value="false"]>.ol-switch-fill::before {
|
||||
content: "NO";
|
||||
}
|
||||
|
||||
.switch-control.coalition [data-value="true"] .ol-switch-fill {
|
||||
background-color: var(--primary-blue);
|
||||
}
|
||||
|
||||
.switch-control.coalition [data-value="false"] .ol-switch-fill {
|
||||
background-color: var(--primary-red);
|
||||
}
|
||||
|
||||
.switch-control.coalition [data-value="undefined"] .ol-switch-fill {
|
||||
background-color: var(--primary-neutral);
|
||||
}
|
||||
|
||||
.switch-control.coalition [data-value="true"] .ol-switch-fill::before {
|
||||
content: "BLUE";
|
||||
}
|
||||
|
||||
.switch-control.coalition [data-value="false"] .ol-switch-fill::before {
|
||||
content: "RED";
|
||||
}
|
||||
|
||||
.switch-control.no-label [data-value] .ol-switch-fill::before {
|
||||
content:"";
|
||||
}
|
||||
|
||||
.ol-context-menu>ul {
|
||||
|
||||
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="19"
|
||||
height="15"
|
||||
viewBox="0 0 19 15"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="miss-on-purpose.svg"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs10" /><sodipodi:namedview
|
||||
id="namedview8"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="39.220856"
|
||||
inkscape:cx="17.37596"
|
||||
inkscape:cy="10.759582"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1377"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:deskcolor="#d1d1d1" /><g
|
||||
id="g139-7"
|
||||
transform="matrix(0.02244338,0,0,0.02244338,3.6322523,1.8724426)"
|
||||
style="fill:none;fill-opacity:1;stroke:#262626;stroke-width:180.346;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"><path
|
||||
d="m 376.91839,231.41639 c -1.31238,-3.09721 -4.35709,-5.09202 -7.71677,-5.09202 -3.35968,0 -6.40439,1.99481 -7.71677,5.09202 l -23.1503,54.01737 c -1.78483,4.1996 -2.72974,8.66167 -2.72974,13.22874 v 40.42116 l -75.59281,44.0958 v -9.86906 c 0,-6.98183 -5.61697,-12.5988 -12.5988,-12.5988 -6.98184,0 -12.59881,5.61697 -12.59881,12.5988 v 29.39721 16.7984 12.5988 c 0,6.98184 5.61697,12.59881 12.59881,12.59881 6.98183,0 12.5988,-5.61697 12.5988,-12.59881 v -4.1996 h 75.59281 v 17.16587 l -30.70958,26.92994 c -1.83732,1.57485 -2.88722,3.88463 -2.88722,6.2994 v 8.3992 c 0,4.61956 3.77964,8.39921 8.3992,8.39921 h 50.39521 v -33.59681 c 0,-4.61956 3.77964,-8.3992 8.3992,-8.3992 4.61956,0 8.3992,3.77964 8.3992,8.3992 v 33.59681 h 50.39521 c 4.61956,0 8.3992,-3.77965 8.3992,-8.39921 v -8.3992 c 0,-2.41477 -1.0499,-4.72455 -2.88722,-6.2994 l -30.70958,-26.92994 v -17.16587 h 75.59281 v 4.1996 c 0,6.98184 5.61697,12.59881 12.5988,12.59881 6.98184,0 12.59881,-5.61697 12.59881,-12.59881 v -12.5988 -16.7984 -29.39721 c 0,-6.98183 -5.61697,-12.5988 -12.59881,-12.5988 -6.98183,0 -12.5988,5.61697 -12.5988,12.5988 v 9.86906 l -75.59281,-44.0958 V 298.6625 c 0,-4.56707 -0.94491,-9.02914 -2.72974,-13.22874 z"
|
||||
id="path1154-3"
|
||||
style="fill:none;fill-opacity:1;stroke:#262626;stroke-width:180.346;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /><path
|
||||
d="m 184.66044,8.5185895 c 12.17907,0 22.01865,9.8395825 22.01865,22.0186445 v 7.15606 C 271.15243,47.257643 322.07055,98.244571 331.6349,162.6491 h 7.15606 c 12.17906,0 22.01864,9.83958 22.01864,22.01864 0,12.17907 -9.83958,22.01865 -22.01864,22.01865 h -7.15606 c -9.56435,64.47334 -60.55128,115.39146 -124.95581,124.95581 v 7.15606 c 0,12.17906 -9.83958,22.01864 -22.01865,22.01864 -12.17906,0 -22.01864,-9.83958 -22.01864,-22.01864 V 331.6422 C 98.168455,322.07785 47.250339,271.15973 37.68599,206.68639 h -7.156059 c -12.179063,0 -22.0186444,-9.83958 -22.0186444,-22.01865 0,-12.17906 9.8395814,-22.01864 22.0186444,-22.01864 H 37.68599 C 47.250339,98.175761 98.168455,47.257643 162.6418,37.693294 v -7.15606 c 0,-12.179062 9.83958,-22.0186445 22.01864,-22.0186445 z M 82.411363,206.68639 c 8.601033,40.11522 40.184027,71.6294 80.230437,80.23043 v -14.1745 c 0,-12.17906 9.83958,-22.01864 22.01864,-22.01864 12.17907,0 22.01865,9.83958 22.01865,22.01864 v 14.1745 c 40.11522,-8.60103 71.6294,-40.18402 80.23044,-80.23043 h -14.17451 c -12.17906,0 -22.01864,-9.83958 -22.01864,-22.01865 0,-12.17906 9.83958,-22.01864 22.01864,-22.01864 h 14.17451 C 278.30849,122.53388 246.79431,91.019701 206.67909,82.418665 v 14.174506 c 0,12.179059 -9.83958,22.018639 -22.01865,22.018639 -12.17906,0 -22.01864,-9.83958 -22.01864,-22.018639 V 82.418665 C 122.52658,91.019701 91.012396,122.53388 82.411363,162.6491 h 14.174502 c 12.179065,0 22.018645,9.83958 22.018645,22.01864 0,12.17907 -9.83958,22.01865 -22.018645,22.01865 z M 184.66044,162.6491 a 22.018645,22.018645 0 1 1 0,44.03729 22.018645,22.018645 0 1 1 0,-44.03729 z"
|
||||
id="path2-1"
|
||||
style="fill:none;fill-opacity:1;stroke:#262626;stroke-width:180.346;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /></g><g
|
||||
id="g139"
|
||||
transform="matrix(0.02244338,0,0,0.02244338,3.6322523,1.8724426)"
|
||||
style="fill:#ffffff;fill-opacity:1"><path
|
||||
d="m 376.91839,231.41639 c -1.31238,-3.09721 -4.35709,-5.09202 -7.71677,-5.09202 -3.35968,0 -6.40439,1.99481 -7.71677,5.09202 l -23.1503,54.01737 c -1.78483,4.1996 -2.72974,8.66167 -2.72974,13.22874 v 40.42116 l -75.59281,44.0958 v -9.86906 c 0,-6.98183 -5.61697,-12.5988 -12.5988,-12.5988 -6.98184,0 -12.59881,5.61697 -12.59881,12.5988 v 29.39721 16.7984 12.5988 c 0,6.98184 5.61697,12.59881 12.59881,12.59881 6.98183,0 12.5988,-5.61697 12.5988,-12.59881 v -4.1996 h 75.59281 v 17.16587 l -30.70958,26.92994 c -1.83732,1.57485 -2.88722,3.88463 -2.88722,6.2994 v 8.3992 c 0,4.61956 3.77964,8.39921 8.3992,8.39921 h 50.39521 v -33.59681 c 0,-4.61956 3.77964,-8.3992 8.3992,-8.3992 4.61956,0 8.3992,3.77964 8.3992,8.3992 v 33.59681 h 50.39521 c 4.61956,0 8.3992,-3.77965 8.3992,-8.39921 v -8.3992 c 0,-2.41477 -1.0499,-4.72455 -2.88722,-6.2994 l -30.70958,-26.92994 v -17.16587 h 75.59281 v 4.1996 c 0,6.98184 5.61697,12.59881 12.5988,12.59881 6.98184,0 12.59881,-5.61697 12.59881,-12.59881 v -12.5988 -16.7984 -29.39721 c 0,-6.98183 -5.61697,-12.5988 -12.59881,-12.5988 -6.98183,0 -12.5988,5.61697 -12.5988,12.5988 v 9.86906 l -75.59281,-44.0958 V 298.6625 c 0,-4.56707 -0.94491,-9.02914 -2.72974,-13.22874 z"
|
||||
id="path1154"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.52495;stroke-opacity:1" /><path
|
||||
d="m 184.66044,8.5185895 c 12.17907,0 22.01865,9.8395825 22.01865,22.0186445 v 7.15606 C 271.15243,47.257643 322.07055,98.244571 331.6349,162.6491 h 7.15606 c 12.17906,0 22.01864,9.83958 22.01864,22.01864 0,12.17907 -9.83958,22.01865 -22.01864,22.01865 h -7.15606 c -9.56435,64.47334 -60.55128,115.39146 -124.95581,124.95581 v 7.15606 c 0,12.17906 -9.83958,22.01864 -22.01865,22.01864 -12.17906,0 -22.01864,-9.83958 -22.01864,-22.01864 V 331.6422 C 98.168455,322.07785 47.250339,271.15973 37.68599,206.68639 h -7.156059 c -12.179063,0 -22.0186444,-9.83958 -22.0186444,-22.01865 0,-12.17906 9.8395814,-22.01864 22.0186444,-22.01864 H 37.68599 C 47.250339,98.175761 98.168455,47.257643 162.6418,37.693294 v -7.15606 c 0,-12.179062 9.83958,-22.0186445 22.01864,-22.0186445 z M 82.411363,206.68639 c 8.601033,40.11522 40.184027,71.6294 80.230437,80.23043 v -14.1745 c 0,-12.17906 9.83958,-22.01864 22.01864,-22.01864 12.17907,0 22.01865,9.83958 22.01865,22.01864 v 14.1745 c 40.11522,-8.60103 71.6294,-40.18402 80.23044,-80.23043 h -14.17451 c -12.17906,0 -22.01864,-9.83958 -22.01864,-22.01865 0,-12.17906 9.83958,-22.01864 22.01864,-22.01864 h 14.17451 C 278.30849,122.53388 246.79431,91.019701 206.67909,82.418665 v 14.174506 c 0,12.179059 -9.83958,22.018639 -22.01865,22.018639 -12.17906,0 -22.01864,-9.83958 -22.01864,-22.018639 V 82.418665 C 122.52658,91.019701 91.012396,122.53388 82.411363,162.6491 h 14.174502 c 12.179065,0 22.018645,9.83958 22.018645,22.01864 0,12.17907 -9.83958,22.01865 -22.018645,22.01865 z M 184.66044,162.6491 a 22.018645,22.018645 0 1 1 0,44.03729 22.018645,22.018645 0 1 1 0,-44.03729 z"
|
||||
id="path2"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0.688083" /></g></svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
77
client/public/themes/olympus/images/states/scenic-aaa.svg
Normal file
@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="19"
|
||||
height="15"
|
||||
viewBox="0 0 19 15"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="scenic-aaa.svg"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs10" /><sodipodi:namedview
|
||||
id="namedview8"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="55.466666"
|
||||
inkscape:cx="7.7253607"
|
||||
inkscape:cy="7.3737982"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1377"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:deskcolor="#d1d1d1" /><g
|
||||
id="g6370-3"
|
||||
transform="matrix(0.02459887,0,0,0.02459887,3.3277353,0.85717408)"
|
||||
style="stroke:#262626;stroke-width:139.23403799;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"><path
|
||||
d="m 376.91839,231.41639 c -1.31238,-3.09721 -4.35709,-5.09202 -7.71677,-5.09202 -3.35968,0 -6.40439,1.99481 -7.71677,5.09202 l -23.1503,54.01737 c -1.78483,4.1996 -2.72974,8.66167 -2.72974,13.22874 v 40.42116 l -75.59281,44.0958 v -9.86906 c 0,-6.98183 -5.61697,-12.5988 -12.5988,-12.5988 -6.98184,0 -12.59881,5.61697 -12.59881,12.5988 v 29.39721 16.7984 12.5988 c 0,6.98184 5.61697,12.59881 12.59881,12.59881 6.98183,0 12.5988,-5.61697 12.5988,-12.59881 v -4.1996 h 75.59281 v 17.16587 l -30.70958,26.92994 c -1.83732,1.57485 -2.88722,3.88463 -2.88722,6.2994 v 8.3992 c 0,4.61956 3.77964,8.39921 8.3992,8.39921 h 50.39521 v -33.59681 c 0,-4.61956 3.77964,-8.3992 8.3992,-8.3992 4.61956,0 8.3992,3.77964 8.3992,8.3992 v 33.59681 h 50.39521 c 4.61956,0 8.3992,-3.77965 8.3992,-8.39921 v -8.3992 c 0,-2.41477 -1.0499,-4.72455 -2.88722,-6.2994 l -30.70958,-26.92994 v -17.16587 h 75.59281 v 4.1996 c 0,6.98184 5.61697,12.59881 12.5988,12.59881 6.98184,0 12.59881,-5.61697 12.59881,-12.59881 v -12.5988 -16.7984 -29.39721 c 0,-6.98183 -5.61697,-12.5988 -12.59881,-12.5988 -6.98183,0 -12.5988,5.61697 -12.5988,12.5988 v 9.86906 l -75.59281,-44.0958 V 298.6625 c 0,-4.56707 -0.94491,-9.02914 -2.72974,-13.22874 z"
|
||||
id="path1154-7-0"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#262626;stroke-width:139.23403799;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /><path
|
||||
style="fill:none;stroke:#262626;stroke-width:139.23403799;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 90.329008,471.87874 294.83388,138.74536"
|
||||
id="path9928-4"
|
||||
sodipodi:nodetypes="cc" /><path
|
||||
style="fill:none;stroke:#262626;stroke-width:139.23403799;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 37.215553,461.40058 33.602384,51.668198"
|
||||
id="path9928-3-3"
|
||||
sodipodi:nodetypes="cc" /><path
|
||||
style="fill:none;stroke:#262626;stroke-width:139.23403799;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 61.785043,466.45901 134.77088,205.58883"
|
||||
id="path9928-31-1"
|
||||
sodipodi:nodetypes="cc" /><path
|
||||
d="m 97.776883,46.496287 c -2.69052,-1.821707 -6.27788,-1.485392 -8.576032,0.784735 -2.298159,2.270126 -2.634474,5.885513 -0.784735,8.548007 l 31.389404,45.766871 -27.914144,9.08052 c -2.774607,0.89684 -4.652369,3.47525 -4.652369,6.38998 0,2.91473 1.877762,5.49315 4.652369,6.38999 l 28.895064,9.36076 -14.82589,28.19441 c -1.37329,2.60645 -0.89684,5.80143 1.20513,7.87538 2.10197,2.07394 5.26893,2.57842 7.87538,1.20513 l 28.19442,-14.82589 9.36076,28.89507 c 0.89684,2.7746 3.47525,4.65236 6.38998,4.65236 2.91474,0 5.49316,-1.87776 6.39,-4.65236 l 9.36076,-28.89507 28.19441,14.82589 c 2.60645,1.37329 5.80144,0.89684 7.87539,-1.20513 2.07393,-2.10197 2.57841,-5.26893 1.20512,-7.87538 l -14.82589,-28.19441 28.89507,-9.36076 c 2.7746,-0.89684 4.65236,-3.47526 4.65236,-6.38999 0,-2.91473 -1.87776,-5.49314 -4.65236,-6.38998 l -29.84797,-9.66907 7.20276,-19.730481 c 0.89684,-2.466311 0.28026,-5.212883 -1.56947,-7.062617 -1.84974,-1.849732 -4.59631,-2.46631 -7.06262,-1.56947 l -19.73049,7.202748 -9.69707,-29.875988 c -0.89684,-2.7746 -3.47526,-4.652359 -6.39,-4.652359 -2.91473,0 -5.49314,1.877759 -6.38998,4.652359 l -9.05248,27.91415 z"
|
||||
id="path10424-8"
|
||||
style="stroke:#262626;stroke-width:139.23403799;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /></g><g
|
||||
id="g6370"
|
||||
transform="matrix(0.02459887,0,0,0.02459887,3.3277353,0.85717408)"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-opacity:1"><path
|
||||
d="m 376.91839,231.41639 c -1.31238,-3.09721 -4.35709,-5.09202 -7.71677,-5.09202 -3.35968,0 -6.40439,1.99481 -7.71677,5.09202 l -23.1503,54.01737 c -1.78483,4.1996 -2.72974,8.66167 -2.72974,13.22874 v 40.42116 l -75.59281,44.0958 v -9.86906 c 0,-6.98183 -5.61697,-12.5988 -12.5988,-12.5988 -6.98184,0 -12.59881,5.61697 -12.59881,12.5988 v 29.39721 16.7984 12.5988 c 0,6.98184 5.61697,12.59881 12.59881,12.59881 6.98183,0 12.5988,-5.61697 12.5988,-12.59881 v -4.1996 h 75.59281 v 17.16587 l -30.70958,26.92994 c -1.83732,1.57485 -2.88722,3.88463 -2.88722,6.2994 v 8.3992 c 0,4.61956 3.77964,8.39921 8.3992,8.39921 h 50.39521 v -33.59681 c 0,-4.61956 3.77964,-8.3992 8.3992,-8.3992 4.61956,0 8.3992,3.77964 8.3992,8.3992 v 33.59681 h 50.39521 c 4.61956,0 8.3992,-3.77965 8.3992,-8.39921 v -8.3992 c 0,-2.41477 -1.0499,-4.72455 -2.88722,-6.2994 l -30.70958,-26.92994 v -17.16587 h 75.59281 v 4.1996 c 0,6.98184 5.61697,12.59881 12.5988,12.59881 6.98184,0 12.59881,-5.61697 12.59881,-12.59881 v -12.5988 -16.7984 -29.39721 c 0,-6.98183 -5.61697,-12.5988 -12.59881,-12.5988 -6.98183,0 -12.5988,5.61697 -12.5988,12.5988 v 9.86906 l -75.59281,-44.0958 V 298.6625 c 0,-4.56707 -0.94491,-9.02914 -2.72974,-13.22874 z"
|
||||
id="path1154-7"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.52495;stroke-opacity:1" /><path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 90.329008,471.87874 294.83388,138.74536"
|
||||
id="path9928"
|
||||
sodipodi:nodetypes="cc" /><path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 37.215553,461.40058 33.602384,51.668198"
|
||||
id="path9928-3"
|
||||
sodipodi:nodetypes="cc" /><path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 61.785043,466.45901 134.77088,205.58883"
|
||||
id="path9928-31"
|
||||
sodipodi:nodetypes="cc" /><path
|
||||
d="m 97.776883,46.496287 c -2.69052,-1.821707 -6.27788,-1.485392 -8.576032,0.784735 -2.298159,2.270126 -2.634474,5.885513 -0.784735,8.548007 l 31.389404,45.766871 -27.914144,9.08052 c -2.774607,0.89684 -4.652369,3.47525 -4.652369,6.38998 0,2.91473 1.877762,5.49315 4.652369,6.38999 l 28.895064,9.36076 -14.82589,28.19441 c -1.37329,2.60645 -0.89684,5.80143 1.20513,7.87538 2.10197,2.07394 5.26893,2.57842 7.87538,1.20513 l 28.19442,-14.82589 9.36076,28.89507 c 0.89684,2.7746 3.47525,4.65236 6.38998,4.65236 2.91474,0 5.49316,-1.87776 6.39,-4.65236 l 9.36076,-28.89507 28.19441,14.82589 c 2.60645,1.37329 5.80144,0.89684 7.87539,-1.20513 2.07393,-2.10197 2.57841,-5.26893 1.20512,-7.87538 l -14.82589,-28.19441 28.89507,-9.36076 c 2.7746,-0.89684 4.65236,-3.47526 4.65236,-6.38999 0,-2.91473 -1.87776,-5.49314 -4.65236,-6.38998 l -29.84797,-9.66907 7.20276,-19.730481 c 0.89684,-2.466311 0.28026,-5.212883 -1.56947,-7.062617 -1.84974,-1.849732 -4.59631,-2.46631 -7.06262,-1.56947 l -19.73049,7.202748 -9.69707,-29.875988 c -0.89684,-2.7746 -3.47526,-4.652359 -6.39,-4.652359 -2.91473,0 -5.49314,1.877759 -6.38998,4.652359 l -9.05248,27.91415 z"
|
||||
id="path10424"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.280262;stroke-opacity:1" /></g></svg>
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="19"
|
||||
height="15"
|
||||
viewBox="0 0 19 15"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="simulate-fire-fight.svg"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs10" /><sodipodi:namedview
|
||||
id="namedview8"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="39.220856"
|
||||
inkscape:cx="4.2961837"
|
||||
inkscape:cy="7.419522"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1377"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:deskcolor="#d1d1d1" /><path
|
||||
d="m 16.282245,9.6013771 c 0,0.1740419 -0.142397,0.3164399 -0.316438,0.3164399 h -2.300125 c -0.108773,0.189864 -0.314463,0.316441 -0.547837,0.316441 h -2.161682 l 0.104821,0.316439 h 1.10754 c 0.174043,0 0.316441,0.142398 0.316441,0.316441 v 0.316439 c 0,0.174043 -0.142398,0.316442 -0.316441,0.316442 h -1.671199 c -0.136465,0 -0.257107,-0.08702 -0.300618,-0.215576 L 9.8486235,10.234258 H 9.3205639 v 0.949319 c 0,0.174043 -0.142398,0.316442 -0.3164399,0.316442 H 8.6876841 c -0.1740424,0 -0.3164404,-0.142399 -0.3164404,-0.316442 v -0.87021 L 6.5497356,10.76825 C 6.349983,10.817694 6.1561633,10.667385 6.1561633,10.461699 V 9.2849368 c 0,-0.174042 0.1423981,-0.31644 0.3164399,-0.31644 H 8.3712437 V 8.6520569 c 0,-0.350062 0.2828182,-0.6328802 0.6328803,-0.6328802 h 2.847961 c 0.350061,0 0.63288,0.2828182 0.63288,0.6328802 h 0.63288 c 0.233374,0 0.43906,0.1265758 0.547837,0.3164399 h 1.983684 c 0,-0.1740418 0.142398,-0.3164399 0.316441,-0.3164399 0.174041,0 0.316438,0.1423981 0.316438,0.3164399 v 0.31644 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#262626;stroke-width:3.88754;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path14144" /><path
|
||||
d="m 7.2231639,6.192428 c 0.5023486,0 0.9849197,0.1404203 1.4002473,0.3935723 V 12.521231 H 4.8261305 V 9.0740104 L 3.7660562,10.865854 C 3.5445481,11.241626 3.0580216,11.366224 2.6822489,11.144715 2.3064765,10.923208 2.181878,10.436681 2.4033861,10.060908 L 3.9104319,7.5135653 C 4.394981,6.6947765 5.2750798,6.192428 6.2263778,6.192428 Z M 5.1425706,3.9773475 c -3.695e-4,-2.1099689 3.1640311,-2.1099689 3.1644005,0 3.697e-4,2.109969 -3.164031,2.109969 -3.1644005,0 z"
|
||||
id="path10805"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#262626;stroke-width:3.88754;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
sodipodi:nodetypes="scccccsccsssss" /><path
|
||||
d="m 16.290263,9.5997326 c 0,0.174042 -0.142396,0.31644 -0.316438,0.31644 h -2.300124 c -0.108774,0.1898644 -0.314463,0.3164404 -0.547838,0.3164404 h -2.161681 l 0.104821,0.316439 h 1.10754 c 0.174042,0 0.31644,0.142399 0.31644,0.316443 v 0.316439 c 0,0.174043 -0.142398,0.316442 -0.31644,0.316442 h -1.671198 c -0.136466,0 -0.257108,-0.08702 -0.300618,-0.215576 L 9.8566424,10.232613 H 9.3285828 v 0.949321 c 0,0.174043 -0.1423981,0.316442 -0.31644,0.316442 H 8.6957029 c -0.1740423,0 -0.3164404,-0.142399 -0.3164404,-0.316442 v -0.870211 l -1.821508,0.454883 C 6.3580019,10.816049 6.1641822,10.66574 6.1641822,10.460054 V 9.2832924 c 0,-0.174042 0.142398,-0.31644 0.3164398,-0.31644 H 8.3792625 V 8.6504125 c 0,-0.350062 0.2828182,-0.6328802 0.6328803,-0.6328802 h 2.8479602 c 0.350062,0 0.63288,0.2828182 0.63288,0.6328802 h 0.63288 c 0.233375,0 0.439061,0.1265757 0.547838,0.3164399 h 1.983684 c 0,-0.1740419 0.142398,-0.3164399 0.31644,-0.3164399 0.174042,0 0.316438,0.142398 0.316438,0.3164399 v 0.31644 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#262626;stroke-width:1.31528;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="path14144-2" /></svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@ -45,6 +45,8 @@
|
||||
--nav-text: #ECECEC;
|
||||
|
||||
--ol-select-secondary: #545F6C;
|
||||
--ol-switch-off:#686868;
|
||||
--ol-switch-undefined:#383838;
|
||||
|
||||
/*** General border radii **/
|
||||
--border-radius-xs: 2px;
|
||||
@ -88,3 +90,7 @@
|
||||
--unit-fuel-y: 22px;
|
||||
--unit-vvi-width: 4px;
|
||||
}
|
||||
|
||||
* {
|
||||
font-weight:600;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
import { MapMarkerControl } from "../map/map";
|
||||
import { MapMarkerVisibilityControl } from "../map/map";
|
||||
|
||||
export const UNITS_URI = "units";
|
||||
export const WEAPONS_URI = "weapons";
|
||||
@ -15,11 +15,11 @@ 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 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"];
|
||||
@ -35,16 +35,16 @@ export const ROEDescriptions: string[] = [
|
||||
];
|
||||
|
||||
export const reactionsToThreatDescriptions: string[] = [
|
||||
"None (No reaction)",
|
||||
"Manoeuvre (no countermeasures)",
|
||||
"Passive (Countermeasures only, no manoeuvre)",
|
||||
"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)",
|
||||
"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)"
|
||||
];
|
||||
|
||||
@ -102,6 +102,13 @@ export const minimapBoundaries = [
|
||||
new LatLng(10.7725, 149.3918333),
|
||||
new LatLng(22.5127778, 149.5427778),
|
||||
new LatLng(22.09, 135.0572222)
|
||||
],
|
||||
[ // 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)
|
||||
]
|
||||
];
|
||||
|
||||
@ -111,32 +118,32 @@ export const mapBounds = {
|
||||
"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: 5 },
|
||||
"Caucasus": { bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]), zoom: 4 },
|
||||
// TODO "Falklands", "Sinai", "Normandy 2"
|
||||
"Falklands": { bounds: new LatLngBounds([-49.097217, -79.418267], [-56.874517, -43.316433]), zoom: 3 },
|
||||
}
|
||||
|
||||
export const mapLayers = {
|
||||
"ArcGIS Satellite": {
|
||||
urlTemplate: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
||||
minZoom: 1,
|
||||
maxZoom: 18,
|
||||
maxZoom: 19,
|
||||
attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, GetApp().getMap()ping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
|
||||
},
|
||||
"USGS Topo": {
|
||||
urlTemplate: 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}',
|
||||
minZoom: 1,
|
||||
maxZoom: 18,
|
||||
maxZoom: 14,
|
||||
attribution: 'Tiles courtesy of the <a href="https://usgs.gov/">U.S. Geological Survey</a>'
|
||||
},
|
||||
"OpenStreetMap Mapnik": {
|
||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
minZoom: 1,
|
||||
maxZoom: 18,
|
||||
maxZoom: 20,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
},
|
||||
"OPENVKarte": {
|
||||
urlTemplate: 'https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png',
|
||||
minZoom: 1,
|
||||
maxZoom: 18,
|
||||
maxZoom: 20,
|
||||
attribution: 'Map <a href="https://memomaps.de/">memomaps.de</a> <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
},
|
||||
"Esri.DeLorme": {
|
||||
@ -148,7 +155,7 @@ export const mapLayers = {
|
||||
"CyclOSM": {
|
||||
urlTemplate: 'https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png',
|
||||
minZoom: 1,
|
||||
maxZoom: 18,
|
||||
maxZoom: 20,
|
||||
attribution: '<a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> | Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}
|
||||
}
|
||||
@ -157,62 +164,66 @@ export const mapLayers = {
|
||||
export const IDLE = "Idle";
|
||||
export const MOVE_UNIT = "Move unit";
|
||||
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
|
||||
export const MAP_MARKER_CONTROLS:MapMarkerControl[] = [{
|
||||
"name":"Human",
|
||||
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" ],
|
||||
"toggles": ["human"],
|
||||
"tooltip": "Toggle human players' visibility"
|
||||
}, {
|
||||
"image": "visibility/dcs.svg",
|
||||
"isProtected": true,
|
||||
"name":"DCS",
|
||||
"name": "DCS",
|
||||
"protectable": true,
|
||||
"toggles": [ "dcs" ],
|
||||
"toggles": ["dcs"],
|
||||
"tooltip": "Toggle DCS-controlled units' visibility"
|
||||
}, {
|
||||
"image": "visibility/aircraft.svg",
|
||||
"name":"Aircraft",
|
||||
"toggles": [ "aircraft" ],
|
||||
"name": "Aircraft",
|
||||
"toggles": ["aircraft"],
|
||||
"tooltip": "Toggle aircraft's visibility"
|
||||
}, {
|
||||
"image": "visibility/helicopter.svg",
|
||||
"name":"Helicopter",
|
||||
"toggles": [ "helicopter" ],
|
||||
"name": "Helicopter",
|
||||
"toggles": ["helicopter"],
|
||||
"tooltip": "Toggle helicopters' visibility"
|
||||
}, {
|
||||
"image": "visibility/groundunit-sam.svg",
|
||||
"name":"Air defence",
|
||||
"toggles": [ "groundunit-sam" ],
|
||||
"name": "Air defence",
|
||||
"toggles": ["groundunit-sam"],
|
||||
"tooltip": "Toggle air defence units' visibility"
|
||||
}, {
|
||||
"image": "visibility/groundunit-other.svg",
|
||||
"name":"Ground units",
|
||||
"toggles": [ "groundunit-other" ],
|
||||
"image": "visibility/groundunit.svg",
|
||||
"name": "Ground units",
|
||||
"toggles": ["groundunit"],
|
||||
"tooltip": "Toggle ground units' visibility"
|
||||
}, {
|
||||
"image": "visibility/navyunit.svg",
|
||||
"name":"Naval",
|
||||
"toggles": [ "navyunit" ],
|
||||
"name": "Naval",
|
||||
"toggles": ["navyunit"],
|
||||
"tooltip": "Toggle naval units' visibility"
|
||||
}, {
|
||||
"image": "visibility/airbase.svg",
|
||||
"name":"Airbase",
|
||||
"toggles": [ "airbase" ],
|
||||
"name": "Airbase",
|
||||
"toggles": ["airbase"],
|
||||
"tooltip": "Toggle airbase' visibility"
|
||||
}];
|
||||
|
||||
export const IADSTypes = ["AAA", "MANPADS", "SAM Site", "Radar"];
|
||||
export const IADSDensities: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Site": 0.1, "Radar": 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 IADSDensities: { [key: string]: number } = { "AAA": 0.8, "MANPADS": 0.3, "SAM Site": 0.1, "Radar": 0.05 };
|
||||
|
||||
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 enum DataIndexes {
|
||||
startOfData = 0,
|
||||
@ -264,12 +275,16 @@ export enum DataIndexes {
|
||||
};
|
||||
|
||||
export const MGRS_PRECISION_10KM = 2;
|
||||
export const MGRS_PRECISION_1KM = 3;
|
||||
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 MGRS_PRECISION_10M = 5;
|
||||
export const MGRS_PRECISION_1M = 6;
|
||||
|
||||
export const DELETE_CYCLE_TIME = 0.05;
|
||||
export const DELETE_CYCLE_TIME = 0.05;
|
||||
export const DELETE_SLOW_THRESHOLD = 50;
|
||||
|
||||
export const GROUPING_ZOOM_TRANSITION = 13;
|
||||
export const GROUPING_ZOOM_TRANSITION = 13;
|
||||
|
||||
export const MAX_SHOTS_SCATTER = 3;
|
||||
export const MAX_SHOTS_INTENSITY = 3;
|
||||
export const SHOTS_SCATTER_DEGREES = 10;
|
||||
@ -1,4 +1,6 @@
|
||||
export interface ContextInterface {
|
||||
allowUnitCopying?: boolean;
|
||||
allowUnitPasting?: boolean;
|
||||
useSpawnMenu?: boolean;
|
||||
useUnitControlPanel?: boolean;
|
||||
useUnitInfoPanel?: boolean;
|
||||
@ -6,16 +8,28 @@ export interface ContextInterface {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ export class AirbaseContextMenu extends ContextMenu {
|
||||
|
||||
document.addEventListener("contextMenuLandAirbase", (e: any) => {
|
||||
if (this.#airbase)
|
||||
getApp().getUnitsManager().selectedUnitsLandAt(this.#airbase.getLatLng());
|
||||
getApp().getUnitsManager().landAt(this.#airbase.getLatLng());
|
||||
this.hide();
|
||||
})
|
||||
}
|
||||
@ -111,7 +111,7 @@ export class AirbaseContextMenu extends ContextMenu {
|
||||
#showSpawnMenu() {
|
||||
if (this.#airbase != null) {
|
||||
getApp().setActiveCoalition(this.#airbase.getCoalition());
|
||||
getApp().getMap().showAirbaseSpawnMenu(this.getX(), this.getY(), this.getLatLng(), this.#airbase);
|
||||
getApp().getMap().showAirbaseSpawnMenu(this.#airbase, this.getX(), this.getY(), this.getLatLng());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ export class AirbaseSpawnContextMenu extends ContextMenu {
|
||||
* @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, y: number) {
|
||||
show(x: number | undefined, y: number | undefined) {
|
||||
super.show(x, y, new LatLng(0, 0));
|
||||
|
||||
this.#aircraftSpawnMenu.setAirbase(undefined);
|
||||
|
||||
@ -19,15 +19,15 @@ export class ContextMenu {
|
||||
|
||||
/** 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
|
||||
* @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, y: number, latlng: LatLng) {
|
||||
this.#latlng = latlng;
|
||||
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.#y = y;
|
||||
this.#x = x ?? this.#x;
|
||||
this.#y = y ?? this.#y;
|
||||
this.clip();
|
||||
this.getContainer()?.dispatchEvent(new Event("show"));
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export class MapContextMenu extends ContextMenu {
|
||||
|
||||
/* Create the coalition switch */
|
||||
this.#coalitionSwitch = new Switch("coalition-switch", (value: boolean) => this.#onSwitchClick(value));
|
||||
this.#coalitionSwitch.setValue(false);
|
||||
this.#coalitionSwitch.setValue(true);
|
||||
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick());
|
||||
|
||||
/* Create the spawn menus for the different unit types */
|
||||
@ -128,9 +128,9 @@ export class MapContextMenu extends ContextMenu {
|
||||
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) });
|
||||
if (getApp().getActiveCoalition() == "blue")
|
||||
this.#coalitionSwitch.setValue(false);
|
||||
else if (getApp().getActiveCoalition() == "red")
|
||||
this.#coalitionSwitch.setValue(true);
|
||||
else if (getApp().getActiveCoalition() == "red")
|
||||
this.#coalitionSwitch.setValue(false);
|
||||
else
|
||||
this.#coalitionSwitch.setValue(undefined);
|
||||
|
||||
@ -232,10 +232,10 @@ export class MapContextMenu extends ContextMenu {
|
||||
|
||||
/** Callback called when the user left clicks on the coalition switch
|
||||
*
|
||||
* @param value Switch position (false: "blue", true: "red")
|
||||
* @param value Switch position (true: "blue", false: "red")
|
||||
*/
|
||||
#onSwitchClick(value: boolean) {
|
||||
value ? getApp().setActiveCoalition("red") : getApp().setActiveCoalition("blue");
|
||||
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();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { deg2rad, ftToM } from "../other/utils";
|
||||
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. */
|
||||
@ -16,15 +16,19 @@ export class UnitContextMenu extends 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
|
||||
*/
|
||||
setOptions(options: { [key: string]: {text: string, tooltip: string }}, callback: CallableFunction) {
|
||||
this.getContainer()?.replaceChildren(...Object.keys(options).map((key: string, idx: number) => {
|
||||
const option = options[key];
|
||||
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 = option.tooltip;
|
||||
el.innerText = option.text;
|
||||
el.title = contextAction.getDescription();
|
||||
el.innerText = contextAction.getLabel();
|
||||
el.id = key;
|
||||
button.addEventListener("click", () => callback(key));
|
||||
button.addEventListener("click", () => {
|
||||
contextAction.executeCallback();
|
||||
if (contextAction.getHideContextAfterExecution())
|
||||
this.hide();
|
||||
});
|
||||
button.appendChild(el);
|
||||
return (button);
|
||||
}));
|
||||
|
||||
@ -5,8 +5,10 @@ export class Dropdown {
|
||||
#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)
|
||||
@ -15,7 +17,10 @@ export class Dropdown {
|
||||
this.#container = document.getElementById(ID) as HTMLElement;
|
||||
|
||||
this.#options = this.#container.querySelector(".ol-select-options") as HTMLElement;
|
||||
this.#value = this.#container.querySelector(".ol-select-value") 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;
|
||||
|
||||
@ -36,48 +41,37 @@ export class Dropdown {
|
||||
return this.#container;
|
||||
}
|
||||
|
||||
setOptions(optionsList: string[], sort: "" | "string" | "number" | "string+number" = "string") {
|
||||
if (sort === "number") {
|
||||
this.#optionsList = optionsList.sort((optionA: string, optionB: string) => {
|
||||
const a = parseInt(optionA);
|
||||
const b = parseInt(optionB);
|
||||
if (a > b)
|
||||
return 1;
|
||||
else
|
||||
return (b > a) ? -1 : 0;
|
||||
});
|
||||
} else if (sort === "string+number") {
|
||||
this.#optionsList = optionsList.sort((optionA: string, optionB: string) => {
|
||||
var regex = /\d+/g;
|
||||
var matchesA = optionA.match(regex);
|
||||
var matchesB = optionB.match(regex);
|
||||
if ((matchesA != null && matchesA?.length > 0) && (matchesB != null && matchesB?.length > 0) && optionA[0] == optionB[0]) {
|
||||
const a = parseInt(matchesA[0] ?? 0);
|
||||
const b = parseInt(matchesB[0] ?? 0);
|
||||
if (a > b)
|
||||
return 1;
|
||||
else
|
||||
return (b > a) ? -1 : 0;
|
||||
} else {
|
||||
if (optionA > optionB)
|
||||
return 1;
|
||||
else
|
||||
return (optionB > optionA) ? -1 : 0;
|
||||
}
|
||||
|
||||
});
|
||||
} else if (sort === "string") {
|
||||
this.#optionsList = optionsList.sort();
|
||||
}
|
||||
/** 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. "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: "" | "string" | "number" | "string+number" = "string", labelsList: string[] | undefined = undefined) {
|
||||
/* 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);
|
||||
|
||||
/* If no options are provided, return */
|
||||
if (this.#optionsList.length == 0) {
|
||||
optionsList = ["No options available"]
|
||||
this.#value.innerText = "No options available";
|
||||
return;
|
||||
}
|
||||
this.#options.replaceChildren(...optionsList.map((option: string, idx: number) => {
|
||||
|
||||
/* 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");
|
||||
button.textContent = option;
|
||||
|
||||
/* 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)
|
||||
@ -91,8 +85,21 @@ export class Dropdown {
|
||||
}));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -104,19 +111,22 @@ export class Dropdown {
|
||||
this.#options.appendChild(optionElement);
|
||||
}
|
||||
|
||||
selectText(text: string) {
|
||||
const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text);
|
||||
if (index > -1) {
|
||||
this.selectValue(index);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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");
|
||||
el.innerText = option;
|
||||
|
||||
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;
|
||||
@ -133,16 +143,24 @@ export class Dropdown {
|
||||
this.#value.innerText = this.#defaultValue;
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.#value.innerText;
|
||||
}
|
||||
|
||||
/** 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");
|
||||
@ -205,4 +223,110 @@ export class Dropdown {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,7 @@ 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 { UnitSpawnOptions, UnitSpawnTable } from "../interfaces";
|
||||
import { UnitBlueprint, UnitSpawnOptions, UnitSpawnTable } from "../interfaces";
|
||||
|
||||
export class UnitSpawnMenu {
|
||||
protected showRangeCircles: boolean = false;
|
||||
@ -61,7 +61,7 @@ export class UnitSpawnMenu {
|
||||
|
||||
/* Create the dropdowns and the altitude slider */
|
||||
this.#unitRoleTypeDropdown = new Dropdown(null, (roleType: string) => this.#setUnitRoleType(roleType), undefined, "Unit type");
|
||||
this.#unitLabelDropdown = new Dropdown(null, (label: string) => this.#setUnitLabel(label), undefined, "Unit label");
|
||||
this.#unitLabelDropdown = new Dropdown(null, (name: string) => this.#setUnitName(name), undefined, "Unit label");
|
||||
this.#unitLoadoutDropdown = new Dropdown(null, (loadout: string) => this.#setUnitLoadout(loadout), undefined, "Unit loadout");
|
||||
this.#unitCountDropdown = new Dropdown(null, (count: string) => this.#setUnitCount(count), undefined, "Unit count");
|
||||
this.#unitCountryDropdown = new Dropdown(null, () => { /* Custom button implementation */ }, undefined, "Unit country");
|
||||
@ -153,16 +153,28 @@ export class UnitSpawnMenu {
|
||||
this.#unitImageEl.classList.toggle("hide", true);
|
||||
this.#unitLiveryDropdown.reset();
|
||||
|
||||
var blueprints: UnitBlueprint[] = [];
|
||||
if (this.#orderByRole)
|
||||
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByRole(this.spawnOptions.roleType).map((blueprint) => { return blueprint.label }), "string+number");
|
||||
blueprints = this.#unitDatabase.getByRole(this.spawnOptions.roleType);
|
||||
else
|
||||
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByType(this.spawnOptions.roleType).map((blueprint) => { return blueprint.label }), "string+number");
|
||||
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.getByLabel(element.textContent ?? "");
|
||||
let entry = this.#unitDatabase.getByName(name);
|
||||
if (entry) {
|
||||
element.querySelectorAll("button")[0]?.append(...(entry.tags?.split(",").map((tag: string) => {
|
||||
tag = tag.trim();
|
||||
@ -418,8 +430,7 @@ export class UnitSpawnMenu {
|
||||
this.#container.dispatchEvent(new Event("unitRoleTypeChanged"));
|
||||
}
|
||||
|
||||
#setUnitLabel(label: string) {
|
||||
var name = this.#unitDatabase.getByLabel(label)?.name || null;
|
||||
#setUnitName(name: string) {
|
||||
if (name != null)
|
||||
this.spawnOptions.name = name;
|
||||
this.#container.dispatchEvent(new Event("unitLabelChanged"));
|
||||
|
||||
2
client/src/dom.d.ts
vendored
@ -18,7 +18,7 @@ interface CustomEventMap {
|
||||
"groupDeletion": CustomEvent<Unit[]>,
|
||||
"mapStateChanged": CustomEvent<string>,
|
||||
"mapContextMenu": CustomEvent<>,
|
||||
"mapVisibilityOptionsChanged": CustomEvent<>,
|
||||
"mapOptionsChanged": CustomEvent<>,
|
||||
"commandModeOptionsChanged": CustomEvent<>,
|
||||
"contactsUpdated": CustomEvent<Unit>,
|
||||
"activeCoalitionChanged": CustomEvent<>
|
||||
|
||||
@ -13,23 +13,22 @@ import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
|
||||
import { ClickableMiniMap } from "./clickableminimap";
|
||||
import { SVGInjector } from '@tanem/svg-injector'
|
||||
import { mapLayers, 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 } from "../constants/constants";
|
||||
import { TargetMarker } from "./markers/targetmarker";
|
||||
import { CoalitionArea } from "./coalitionarea/coalitionarea";
|
||||
import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
|
||||
import { DrawingCursor } from "./coalitionarea/drawingcursor";
|
||||
import { AirbaseSpawnContextMenu } from "../contextmenus/airbasespawnmenu";
|
||||
import { Popup } from "../popups/popup";
|
||||
import { GestureHandling } from "leaflet-gesture-handling";
|
||||
import { TouchBoxSelect } from "./touchboxselect";
|
||||
import { DestinationPreviewHandle } from "./markers/destinationpreviewHandle";
|
||||
import { ContextActionSet } from "../unit/contextactionset";
|
||||
|
||||
var hasTouchScreen = false;
|
||||
//if ("maxTouchPoints" in navigator)
|
||||
// hasTouchScreen = navigator.maxTouchPoints > 0;
|
||||
|
||||
if (hasTouchScreen)
|
||||
if (hasTouchScreen)
|
||||
L.Map.addInitHook('addHandler', 'boxSelect', TouchBoxSelect);
|
||||
else
|
||||
else
|
||||
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
|
||||
|
||||
L.Map.addInitHook("addHandler", "gestureHandling", GestureHandling);
|
||||
@ -38,10 +37,10 @@ L.Map.addInitHook("addHandler", "gestureHandling", GestureHandling);
|
||||
require("../../public/javascripts/leaflet.nauticscale.js")
|
||||
require("../../public/javascripts/L.Path.Drag.js")
|
||||
|
||||
export type MapMarkerControl = {
|
||||
export type MapMarkerVisibilityControl = {
|
||||
"image": string;
|
||||
"isProtected"?: boolean,
|
||||
"name":string,
|
||||
"name": string,
|
||||
"protectable"?: boolean,
|
||||
"toggles": string[],
|
||||
"tooltip": string
|
||||
@ -75,7 +74,6 @@ export class Map extends L.Map {
|
||||
#destinationRotationCenter: L.LatLng | null = null;
|
||||
#coalitionAreas: CoalitionArea[] = [];
|
||||
|
||||
#targetCursor: TargetMarker = new TargetMarker(new L.LatLng(0, 0), { interactive: false });
|
||||
#destinationPreviewCursors: DestinationPreviewMarker[] = [];
|
||||
#drawingCursor: DrawingCursor = new DrawingCursor();
|
||||
#destinationPreviewHandle: DestinationPreviewHandle = new DestinationPreviewHandle(new L.LatLng(0, 0));
|
||||
@ -90,7 +88,7 @@ export class Map extends L.Map {
|
||||
#coalitionAreaContextMenu: CoalitionAreaContextMenu = new CoalitionAreaContextMenu("coalition-area-contextmenu");
|
||||
|
||||
#mapSourceDropdown: Dropdown;
|
||||
#mapMarkerControls:MapMarkerControl[] = MAP_MARKER_CONTROLS;
|
||||
#mapMarkerVisibilityControls: MapMarkerVisibilityControl[] = MAP_MARKER_CONTROLS;
|
||||
#mapVisibilityOptionsDropdown: Dropdown;
|
||||
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
|
||||
#visibilityOptions: { [key: string]: boolean } = {}
|
||||
@ -100,21 +98,21 @@ export class Map extends L.Map {
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
constructor(ID: string) {
|
||||
/* Init the leaflet map */
|
||||
super(ID, {
|
||||
preferCanvas: true,
|
||||
doubleClickZoom: false,
|
||||
zoomControl: false,
|
||||
boxZoom: false,
|
||||
super(ID, {
|
||||
preferCanvas: true,
|
||||
doubleClickZoom: false,
|
||||
zoomControl: false,
|
||||
boxZoom: false,
|
||||
//@ts-ignore Needed because the boxSelect option is non-standard
|
||||
boxSelect: true,
|
||||
zoomAnimation: true,
|
||||
boxSelect: true,
|
||||
zoomAnimation: true,
|
||||
maxBoundsViscosity: 1.0,
|
||||
minZoom: 7,
|
||||
minZoom: 7,
|
||||
keyboard: true,
|
||||
keyboardPanDelta: 0,
|
||||
gestureHandling: hasTouchScreen
|
||||
gestureHandling: hasTouchScreen
|
||||
});
|
||||
this.setView([37.23, -115.8], 10);
|
||||
|
||||
@ -198,15 +196,15 @@ export class Map extends L.Map {
|
||||
this.#panToUnit(this.#centerUnit);
|
||||
});
|
||||
|
||||
document.addEventListener("mapVisibilityOptionsChanged", () => {
|
||||
document.addEventListener("mapOptionsChanged", () => {
|
||||
this.getContainer().toggleAttribute("data-hide-labels", !this.getVisibilityOptions()[SHOW_UNIT_LABELS]);
|
||||
});
|
||||
|
||||
/* 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.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.#deafultPanDelta));
|
||||
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);
|
||||
|
||||
/* Option buttons */
|
||||
@ -257,7 +255,7 @@ export class Map extends L.Map {
|
||||
|
||||
/* Operations to perform if you are NOT in a state */
|
||||
if (this.#state !== COALITIONAREA_DRAW_POLYGON) {
|
||||
this.#deselectCoalitionAreas();
|
||||
this.#deselectSelectedCoalitionArea();
|
||||
}
|
||||
|
||||
/* Operations to perform if you ARE in a state */
|
||||
@ -291,7 +289,6 @@ export class Map extends L.Map {
|
||||
else {
|
||||
this.#hiddenTypes.push(key);
|
||||
}
|
||||
Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
}
|
||||
|
||||
getHiddenTypes() {
|
||||
@ -322,7 +319,7 @@ export class Map extends L.Map {
|
||||
return this.#mapContextMenu;
|
||||
}
|
||||
|
||||
showUnitContextMenu(x: number, y: number, latlng: L.LatLng) {
|
||||
showUnitContextMenu(x: number | undefined = undefined, y: number | undefined = undefined, latlng: L.LatLng | undefined = undefined) {
|
||||
this.hideAllContextMenus();
|
||||
this.#unitContextMenu.show(x, y, latlng);
|
||||
}
|
||||
@ -335,7 +332,7 @@ export class Map extends L.Map {
|
||||
this.#unitContextMenu.hide();
|
||||
}
|
||||
|
||||
showAirbaseContextMenu(x: number, y: number, latlng: L.LatLng, airbase: Airbase) {
|
||||
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);
|
||||
@ -349,7 +346,7 @@ export class Map extends L.Map {
|
||||
this.#airbaseContextMenu.hide();
|
||||
}
|
||||
|
||||
showAirbaseSpawnMenu(x: number, y: number, latlng: L.LatLng, airbase: Airbase) {
|
||||
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);
|
||||
@ -377,11 +374,6 @@ export class Map extends L.Map {
|
||||
this.#coalitionAreaContextMenu.hide();
|
||||
}
|
||||
|
||||
isZooming() {
|
||||
return this.#isZooming;
|
||||
}
|
||||
|
||||
/* Mouse coordinates */
|
||||
getMousePosition() {
|
||||
return this.#lastMousePosition;
|
||||
}
|
||||
@ -390,11 +382,6 @@ export class Map extends L.Map {
|
||||
return this.containerPointToLatLng(this.#lastMousePosition);
|
||||
}
|
||||
|
||||
/* Spawn from air base */
|
||||
spawnFromAirbase(e: any) {
|
||||
//this.#aircraftSpawnMenu(e);
|
||||
}
|
||||
|
||||
centerOnUnit(ID: number | null) {
|
||||
if (ID != null) {
|
||||
this.options.scrollWheelZoom = 'center';
|
||||
@ -407,7 +394,7 @@ export class Map extends L.Map {
|
||||
this.#updateCursor();
|
||||
}
|
||||
|
||||
getCenterUnit() {
|
||||
getCenteredOnUnit() {
|
||||
return this.#centerUnit;
|
||||
}
|
||||
|
||||
@ -420,7 +407,6 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
this.setView(bounds.getCenter(), 8);
|
||||
//this.setMaxBounds(bounds);
|
||||
|
||||
if (this.#miniMap)
|
||||
this.#miniMap.remove();
|
||||
@ -502,10 +488,35 @@ export class Map extends L.Map {
|
||||
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;
|
||||
}
|
||||
|
||||
/* Event handlers */
|
||||
#onClick(e: any) {
|
||||
if (!this.#preventLeftClick) {
|
||||
@ -560,19 +571,20 @@ export class Map extends L.Map {
|
||||
}
|
||||
}
|
||||
else if (this.#state === MOVE_UNIT) {
|
||||
if (!e.originalEvent.ctrlKey) {
|
||||
getApp().getUnitsManager().selectedUnitsClearDestinations();
|
||||
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;
|
||||
}
|
||||
getApp().getUnitsManager().selectedUnitsAddDestination(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) {
|
||||
@ -611,65 +623,31 @@ export class Map extends L.Map {
|
||||
if (e.originalEvent.button != 2 || e.originalEvent.ctrlKey || e.originalEvent.shiftKey)
|
||||
return;
|
||||
|
||||
var options: { [key: string]: { text: string, tooltip: string } } = {};
|
||||
const selectedUnits = getApp().getUnitsManager().getSelectedUnits();
|
||||
const selectedUnitTypes = getApp().getUnitsManager().getSelectedUnitsCategories();
|
||||
var contextActionSet = new ContextActionSet();
|
||||
var units = getApp().getUnitsManager().getSelectedUnits();
|
||||
units.forEach((unit: Unit) => {
|
||||
unit.appendContextActions(contextActionSet, null, e.latlng);
|
||||
})
|
||||
|
||||
if (selectedUnitTypes.length === 1 && ["Aircraft", "Helicopter"].includes(selectedUnitTypes[0])) {
|
||||
if (selectedUnits.every((unit: Unit) => { return unit.canLandAtPoint()}))
|
||||
options["land-at-point"] = { text: "Land here", tooltip: "Land at this precise location" };
|
||||
|
||||
if (selectedUnits.every((unit: Unit) => { return unit.canTargetPoint()})) {
|
||||
options["bomb"] = { text: "Precision bombing", tooltip: "Precision bombing of a specific point" };
|
||||
options["carpet-bomb"] = { text: "Carpet bombing", tooltip: "Carpet bombing close to a point" };
|
||||
}
|
||||
|
||||
if (Object.keys(options).length === 0)
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Selected units can not perform point actions.`);
|
||||
}
|
||||
else if (selectedUnitTypes.length === 1 && ["GroundUnit", "NavyUnit"].includes(selectedUnitTypes[0])) {
|
||||
if (selectedUnits.every((unit: Unit) => { return unit.canTargetPoint() })) {
|
||||
options["fire-at-area"] = { text: "Fire at area", tooltip: "Fire at a large area" };
|
||||
options["simulate-fire-fight"] = { text: "Simulate fire fight", tooltip: "Simulate a fire fight by shooting randomly in a certain large area" };
|
||||
}
|
||||
else
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Selected units can not perform point actions.`);
|
||||
}
|
||||
else if(selectedUnitTypes.length > 1) {
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Multiple unit types selected, no common actions available.`);
|
||||
}
|
||||
|
||||
if (Object.keys(options).length > 0) {
|
||||
this.showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
|
||||
this.getUnitContextMenu().setOptions(options, (option: string) => {
|
||||
this.hideUnitContextMenu();
|
||||
if (option === "bomb") {
|
||||
getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getApp().getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
|
||||
}
|
||||
else if (option === "carpet-bomb") {
|
||||
getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getApp().getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
|
||||
}
|
||||
else if (option === "fire-at-area") {
|
||||
getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getApp().getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
|
||||
}
|
||||
else if (option === "simulate-fire-fight") {
|
||||
getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getApp().getUnitsManager().selectedUnitsSimulateFireFight(this.getMouseCoordinates());
|
||||
}
|
||||
else if (option === "land-at-point") {
|
||||
getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getApp().getUnitsManager().selectedUnitsLandAtPoint(this.getMouseCoordinates());
|
||||
}
|
||||
});
|
||||
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) {
|
||||
@ -721,6 +699,7 @@ export class Map extends L.Map {
|
||||
this.#isZooming = false;
|
||||
}
|
||||
|
||||
/* */
|
||||
#panToUnit(unit: Unit) {
|
||||
var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng);
|
||||
this.setView(unitPosition, this.getZoom(), { animate: false });
|
||||
@ -735,13 +714,15 @@ export class Map extends L.Map {
|
||||
|
||||
#createUnitMarkerControlButtons() {
|
||||
const unitVisibilityControls = <HTMLElement>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";
|
||||
const makeTitle = (isProtected: boolean) => {
|
||||
return (isProtected) ? "Unit type is protected and will ignore orders" : "Unit is NOT protected and will respond to orders";
|
||||
}
|
||||
this.getMapMarkerControls().forEach( (control:MapMarkerControl) => {
|
||||
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 = `
|
||||
<button data-on-click="toggleMarkerVisibility" title="${control.tooltip}" data-on-click-params='{"types":${toggles}}'>
|
||||
<img src="/resources/theme/images/buttons/${control.image}" />
|
||||
@ -749,7 +730,7 @@ export class Map extends L.Map {
|
||||
`;
|
||||
unitVisibilityControls.appendChild(div);
|
||||
|
||||
if ( control.protectable ) {
|
||||
if (control.protectable) {
|
||||
div.innerHTML += `
|
||||
<button class="lock" ${control.isProtected ? "data-protected" : ""} title="${makeTitle(control.isProtected || false)}">
|
||||
<img src="/resources/theme/images/buttons/other/lock-solid.svg" class="locked" />
|
||||
@ -757,10 +738,10 @@ export class Map extends L.Map {
|
||||
</button>`;
|
||||
|
||||
const btn = <HTMLButtonElement>div.querySelector("button.lock");
|
||||
btn.addEventListener("click", (ev:MouseEventInit) => {
|
||||
btn.addEventListener("click", (ev: MouseEventInit) => {
|
||||
control.isProtected = !control.isProtected;
|
||||
btn.toggleAttribute("data-protected", control.isProtected);
|
||||
btn.title = makeTitle( control.isProtected );
|
||||
btn.title = makeTitle(control.isProtected);
|
||||
document.dispatchEvent(new CustomEvent("toggleMarkerProtection", {
|
||||
detail: {
|
||||
"_element": btn,
|
||||
@ -774,28 +755,32 @@ export class Map extends L.Map {
|
||||
unitVisibilityControls.querySelectorAll(`img[src$=".svg"]`).forEach(img => SVGInjector(img));
|
||||
}
|
||||
|
||||
unitIsProtected(unit:Unit) {
|
||||
const toggles = this.#mapMarkerControls.reduce((list, control:MapMarkerControl) => {
|
||||
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());
|
||||
});
|
||||
}
|
||||
|
||||
#deselectCoalitionAreas() {
|
||||
#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");
|
||||
}
|
||||
@ -808,48 +793,47 @@ export class Map extends L.Map {
|
||||
const singleCursor = !this.#shiftKey;
|
||||
const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length;
|
||||
if (singleCursor) {
|
||||
if ( this.#destinationPreviewCursors.length != 1) {
|
||||
this.#hideDestinationCursors();
|
||||
var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false });
|
||||
marker.addTo(this);
|
||||
this.#destinationPreviewCursors = [marker];
|
||||
}
|
||||
|
||||
this.#destinationPreviewHandleLine.removeFrom(this);
|
||||
this.#destinationPreviewHandle.removeFrom(this);
|
||||
this.#hideDestinationCursors();
|
||||
}
|
||||
else if (!singleCursor) {
|
||||
while (this.#destinationPreviewCursors.length > selectedUnitsCount) {
|
||||
this.removeLayer(this.#destinationPreviewCursors[0]);
|
||||
this.#destinationPreviewCursors.splice(0, 1);
|
||||
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();
|
||||
}
|
||||
|
||||
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 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().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
|
||||
if (idx < this.#destinationPreviewCursors.length)
|
||||
this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey ? latlng : this.getMouseCoordinates());
|
||||
})
|
||||
};
|
||||
const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: 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());
|
||||
this.#destinationPreviewHandleLine.setLatLngs([groupLatLng, this.getMouseCoordinates()]);
|
||||
this.#destinationPreviewHandle.setLatLng(this.getMouseCoordinates());
|
||||
} else {
|
||||
this.#hideDestinationCursors();
|
||||
}
|
||||
}
|
||||
|
||||
#hideDestinationCursors() {
|
||||
@ -880,34 +864,9 @@ export class Map extends L.Map {
|
||||
this.#drawingCursor.removeFrom(this);
|
||||
}
|
||||
|
||||
#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();
|
||||
}
|
||||
}
|
||||
|
||||
#setVisibilityOption(option: string, ev: any) {
|
||||
this.#visibilityOptions[option] = ev.currentTarget.checked;
|
||||
document.dispatchEvent(new CustomEvent("mapVisibilityOptionsChanged"));
|
||||
}
|
||||
|
||||
getMapMarkerControls() {
|
||||
return this.#mapMarkerControls;
|
||||
document.dispatchEvent(new CustomEvent("mapOptionsChanged"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -287,7 +287,7 @@ export class MissionManager {
|
||||
}
|
||||
|
||||
#onAirbaseClick(e: any) {
|
||||
getApp().getMap().showAirbaseContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng, e.sourceTarget);
|
||||
getApp().getMap().showAirbaseContextMenu(e.sourceTarget, e.originalEvent.x, e.originalEvent.y, e.latlng);
|
||||
}
|
||||
|
||||
#loadAirbaseChartData(callsign: string) {
|
||||
|
||||
@ -17,6 +17,7 @@ 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";
|
||||
@ -231,11 +232,21 @@ export class OlympusApp {
|
||||
this.#setupEvents();
|
||||
|
||||
/* Set the splash background image to a random image */
|
||||
var splashScreen = document.getElementById("splash-screen");
|
||||
if (splashScreen) {
|
||||
let i = Math.round(Math.random() * 7 + 1);
|
||||
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);
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
#setupEvents() {
|
||||
@ -380,9 +391,9 @@ export class OlympusApp {
|
||||
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().selectedUnitsSetHotgroup(parseInt(ev.code.substring(5))); // "These selected units are hotgroup X (forget any previous membership)"
|
||||
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().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5))); // "Add (append) these units to hotgroup X (in addition to any existing members)"
|
||||
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."
|
||||
},
|
||||
@ -409,8 +420,9 @@ export class OlympusApp {
|
||||
loginForm.addEventListener("submit", (ev:SubmitEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var hash = sha256.create();
|
||||
const username = (loginForm.querySelector("#username") as HTMLInputElement).value;
|
||||
const password = (loginForm.querySelector("#password") as HTMLInputElement).value;
|
||||
const password = hash.update((loginForm.querySelector("#password") as HTMLInputElement).value).hex();
|
||||
|
||||
// Update the user credentials
|
||||
this.getServerManager().setCredentials(username, password);
|
||||
|
||||
@ -348,7 +348,7 @@ export function getMarkerCategoryByName(name: string) {
|
||||
else if (navyUnitDatabase.getByName(name) != null)
|
||||
return "navyunit";
|
||||
else
|
||||
return "groundunit-other"; // TODO add other unit types
|
||||
return "aircraft"; // TODO add other unit types
|
||||
}
|
||||
|
||||
export function getUnitDatabaseByCategory(category: string) {
|
||||
|
||||
@ -2,14 +2,13 @@ import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getApp } from "..";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Slider } from "../controls/slider";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
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 { PrimaryToolbar } from "../toolbars/primarytoolbar";
|
||||
import { ContextActionSet } from "../unit/contextactionset";
|
||||
|
||||
export class UnitControlPanel extends Panel {
|
||||
#altitudeSlider: Slider;
|
||||
@ -34,36 +33,36 @@ export class UnitControlPanel extends Panel {
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
constructor(ID: string) {
|
||||
super(ID);
|
||||
|
||||
/* Unit control sliders */
|
||||
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getApp().getUnitsManager().selectedUnitsSetAltitude(ftToM(value)); });
|
||||
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getApp().getUnitsManager().selectedUnitsSetAltitudeType(value? "ASL": "AGL"); });
|
||||
this.#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().selectedUnitsSetSpeed(knotsToMs(value)); });
|
||||
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getApp().getUnitsManager().selectedUnitsSetSpeedType(value? "CAS": "GS"); });
|
||||
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().selectedUnitsSetROE(option); });
|
||||
}).filter((button: HTMLButtonElement, index: number) => {return ROEs[index] !== "";});
|
||||
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().selectedUnitsSetReactionToThreat(option); });
|
||||
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().selectedUnitsSetEmissionsCountermeasures(option); });
|
||||
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().selectedUnitsSetShotsScatter(option); });
|
||||
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().selectedUnitsSetShotsIntensity(option); });
|
||||
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"]);
|
||||
@ -92,51 +91,71 @@ export class UnitControlPanel extends Panel {
|
||||
|
||||
/* On off switch */
|
||||
this.#onOffSwitch = new Switch("on-off-switch", (value: boolean) => {
|
||||
getApp().getUnitsManager().selectedUnitsSetOnOff(value);
|
||||
getApp().getUnitsManager().setOnOff(value);
|
||||
});
|
||||
|
||||
/* Follow roads switch */
|
||||
this.#followRoadsSwitch = new Switch("follow-roads-switch", (value: boolean) => {
|
||||
getApp().getUnitsManager().selectedUnitsSetFollowRoads(value);
|
||||
getApp().getUnitsManager().setFollowRoads(value);
|
||||
});
|
||||
|
||||
/* Operate as */
|
||||
this.#operateAsSwitch = new Switch("operate-as-switch", (value: boolean) => {
|
||||
getApp().getUnitsManager().selectedUnitsSetOperateAs(value);
|
||||
getApp().getUnitsManager().setOperateAs(value);
|
||||
});
|
||||
|
||||
/* Mouseover of (?) highlights activation buttons */
|
||||
const operateAsQuestionMark = <HTMLElement>this.getElement().querySelector("#operate-as h4 img");
|
||||
operateAsQuestionMark.addEventListener("mouseover", () => {
|
||||
document.querySelectorAll(`#rapid-controls button.scenic-action`).forEach((btn: Element) => {
|
||||
btn.classList.add(`pulse`);
|
||||
});
|
||||
});
|
||||
operateAsQuestionMark.addEventListener("mouseout", () => {
|
||||
document.querySelectorAll(`#rapid-controls button.scenic-action.pulse`).forEach((btn: Element) => {
|
||||
btn.classList.remove(`pulse`);
|
||||
});
|
||||
});
|
||||
|
||||
/* Advanced settings dialog */
|
||||
this.#advancedSettingsDialog = <HTMLElement> document.querySelector("#advanced-settings-dialog");
|
||||
this.#advancedSettingsDialog = <HTMLElement>document.querySelector("#advanced-settings-dialog");
|
||||
|
||||
/* Advanced settings dropdowns */
|
||||
this.#TACANXYDropdown = new Dropdown("TACAN-XY", () => {});
|
||||
this.#TACANXYDropdown = new Dropdown("TACAN-XY", () => { });
|
||||
this.#TACANXYDropdown.setOptions(["X", "Y"]);
|
||||
this.#radioDecimalsDropdown = new Dropdown("radio-decimals", () => {});
|
||||
this.#radioDecimalsDropdown = new Dropdown("radio-decimals", () => { });
|
||||
this.#radioDecimalsDropdown.setOptions([".000", ".250", ".500", ".750"]);
|
||||
this.#radioCallsignDropdown = new Dropdown("radio-callsign", () => {});
|
||||
this.#radioCallsignDropdown = new Dropdown("radio-callsign", () => { });
|
||||
this.#deleteDropdown = new Dropdown("delete-options", () => { });
|
||||
|
||||
/* Events and timer */
|
||||
window.setInterval(() => {this.update();}, 25);
|
||||
window.setInterval(() => { this.update(); }, 25);
|
||||
|
||||
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => {
|
||||
this.show();
|
||||
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => {
|
||||
this.show();
|
||||
this.addButtons();
|
||||
this.#updateRapidControls();
|
||||
this.#updateRapidControls();
|
||||
});
|
||||
document.addEventListener("clearSelection", () => {
|
||||
document.addEventListener("clearSelection", () => {
|
||||
this.hide();
|
||||
this.#updateRapidControls();
|
||||
this.#updateRapidControls();
|
||||
});
|
||||
document.addEventListener("applyAdvancedSettings", () => {this.#applyAdvancedSettings();})
|
||||
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 ) => {
|
||||
this.getElement().querySelector( `button[data-unit-id="${ev.detail.ID}"]` )?.remove();
|
||||
this.#updateRapidControls();
|
||||
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());
|
||||
@ -144,16 +163,16 @@ export class UnitControlPanel extends Panel {
|
||||
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() )
|
||||
if (!context.getUseUnitControlPanel())
|
||||
return;
|
||||
|
||||
|
||||
super.show();
|
||||
this.#speedTypeSwitch.resetExpectedValue();
|
||||
this.#altitudeTypeSwitch.resetExpectedValue();
|
||||
@ -161,6 +180,7 @@ export class UnitControlPanel extends Panel {
|
||||
this.#AWACSSwitch.resetExpectedValue();
|
||||
this.#onOffSwitch.resetExpectedValue();
|
||||
this.#followRoadsSwitch.resetExpectedValue();
|
||||
this.#operateAsSwitch.resetExpectedValue();
|
||||
this.#altitudeSlider.resetExpectedValue();
|
||||
this.#speedSlider.resetExpectedValue();
|
||||
this.#calculateMaxHeight();
|
||||
@ -169,26 +189,26 @@ export class UnitControlPanel extends Panel {
|
||||
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-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 ) => {
|
||||
button.addEventListener("click", (ev: MouseEventInit) => {
|
||||
// Ctrl-click deselection
|
||||
if ( ev.ctrlKey === true && ev.shiftKey === false && ev.altKey === false ) {
|
||||
getApp().getUnitsManager().deselectUnit( unit.ID );
|
||||
if (ev.ctrlKey === true && ev.shiftKey === false && ev.altKey === false) {
|
||||
getApp().getUnitsManager().deselectUnit(unit.ID);
|
||||
button.remove();
|
||||
// Deselect all
|
||||
// Deselect all
|
||||
} else {
|
||||
getApp().getUnitsManager().deselectAllUnits();
|
||||
getApp().getUnitsManager().selectUnit(unit.ID, true);
|
||||
@ -204,14 +224,14 @@ export class UnitControlPanel extends Panel {
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.getVisible()){
|
||||
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()});
|
||||
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);
|
||||
@ -219,37 +239,37 @@ export class UnitControlPanel extends Panel {
|
||||
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.includes("GroundUnit")); //TODO: more refined
|
||||
element.toggleAttribute("data-show-shots-intensity", 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-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", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) === "neutral");
|
||||
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)
|
||||
if (isAWACS)
|
||||
element.toggleAttribute("data-show-advanced-settings-button", isActiveAWACAS);
|
||||
else if (isTanker)
|
||||
else if (isTanker)
|
||||
element.toggleAttribute("data-show-advanced-settings-button", isActiveTanker);
|
||||
else
|
||||
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);
|
||||
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]]);
|
||||
@ -294,68 +314,46 @@ export class UnitControlPanel extends Panel {
|
||||
this.#AWACSSwitch.setValue(isActiveAWACAS, false);
|
||||
this.#onOffSwitch.setValue(onOff, false);
|
||||
this.#followRoadsSwitch.setValue(followRoads, false);
|
||||
this.#operateAsSwitch.setValue(operateAs? operateAs === "blue": undefined, false);
|
||||
this.#operateAsSwitch.setValue(operateAs ? operateAs === "blue" : undefined, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#updateRapidControls() {
|
||||
var options: { [key: string]: { text: string, tooltip: string, type: string } } | null = null;
|
||||
var contextActionSet = new ContextActionSet();
|
||||
var units = getApp().getUnitsManager().getSelectedUnits();
|
||||
|
||||
var selectedUnits = getApp().getUnitsManager().getSelectedUnits();
|
||||
|
||||
var showAltitudeChange = selectedUnits.some((unit: Unit) => {return ["Aircraft", "Helicopter"].includes(unit.getCategory());});
|
||||
this.getElement().querySelector("#climb")?.classList.toggle("hide", !showAltitudeChange);
|
||||
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);
|
||||
|
||||
/* Keep only the common "and" options, unless a single unit is selected */
|
||||
selectedUnits.forEach((unit: Unit) => {
|
||||
var unitOptions = unit.getActions();
|
||||
if (options === null) {
|
||||
options = unitOptions;
|
||||
} else {
|
||||
/* Delete all the "or" type options */
|
||||
for (let optionKey in options) {
|
||||
if (options[optionKey].type == "or") {
|
||||
delete options[optionKey];
|
||||
}
|
||||
}
|
||||
|
||||
/* Options of "and" type get shown if ALL units have it */
|
||||
for (let optionKey in options) {
|
||||
if (!(optionKey in unitOptions)) {
|
||||
delete options[optionKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
options = options ?? {};
|
||||
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 option in options) {
|
||||
|
||||
for (let key in contextActionSet.getContextActions()) {
|
||||
const contextAction = contextActionSet.getContextActions()[key];
|
||||
let button = document.createElement("button");
|
||||
button.title = options[option].tooltip;
|
||||
button.title = contextAction.getDescription();
|
||||
button.classList.add("ol-button", "unit-action-button");
|
||||
button.id = option;
|
||||
if (contextAction.getOptions().isScenic)
|
||||
button.classList.add("scenic-action");
|
||||
button.id = key;
|
||||
rapidControlsContainer.appendChild(button);
|
||||
button.onclick = () => {
|
||||
/* Since only common actions are shown in the rapid controls, we execute it only on the first unit */
|
||||
if (selectedUnits.length > 0)
|
||||
selectedUnits[0].executeAction(null, option);
|
||||
contextAction.executeCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#updateAdvancedSettingsDialog(units: Unit[])
|
||||
{
|
||||
if (units.length == 1)
|
||||
{
|
||||
#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;
|
||||
@ -368,7 +366,7 @@ export class UnitControlPanel extends Panel {
|
||||
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();
|
||||
@ -405,7 +403,7 @@ export class UnitControlPanel extends Panel {
|
||||
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"]);
|
||||
else if (isAWACS) /* Set AWACS specific options */
|
||||
@ -419,8 +417,7 @@ export class UnitControlPanel extends Panel {
|
||||
}
|
||||
}
|
||||
|
||||
#applyAdvancedSettings()
|
||||
{
|
||||
#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;
|
||||
@ -436,7 +433,7 @@ export class UnitControlPanel extends Panel {
|
||||
|
||||
/* TACAN */
|
||||
const TACAN: TACAN = {
|
||||
isOn: TACANCheckbox.checked? true: false,
|
||||
isOn: TACANCheckbox.checked ? true : false,
|
||||
channel: Number(TACANChannelInput.value),
|
||||
XY: this.#TACANXYDropdown.getValue(),
|
||||
callsign: TACANCallsignInput.value as string
|
||||
@ -448,18 +445,18 @@ export class UnitControlPanel extends Panel {
|
||||
const radio: Radio = {
|
||||
frequency: (radioMHz * 1000 + Number(radioDecimals.substring(1))) * 1000,
|
||||
callsign: this.#radioCallsignDropdown.getIndex() + 1,
|
||||
callsignNumber: Number(radioCallsignNumberInput.value)
|
||||
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
|
||||
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
|
||||
|
||||
@ -20,6 +20,7 @@ export class ServerManager {
|
||||
#demoEnabled = false;
|
||||
#previousMissionElapsedTime:number = 0; // Track if mission elapsed time is increasing (i.e. is the server paused)
|
||||
#serverIsPaused: boolean = false;
|
||||
#intervals: number[] = [];
|
||||
|
||||
constructor() {
|
||||
this.#lastUpdateTimes[UNITS_URI] = Date.now();
|
||||
@ -339,12 +340,14 @@ export class ServerManager {
|
||||
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 }
|
||||
@ -402,7 +405,11 @@ export class ServerManager {
|
||||
}
|
||||
|
||||
startUpdate() {
|
||||
window.setInterval(() => {
|
||||
/* 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);
|
||||
@ -410,9 +417,9 @@ export class ServerManager {
|
||||
return data.time;
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}, 1000));
|
||||
|
||||
window.setInterval(() => {
|
||||
this.#intervals.push(window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
|
||||
this.getAirbases((data: AirbasesData) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
@ -420,9 +427,9 @@ export class ServerManager {
|
||||
return data.time;
|
||||
});
|
||||
}
|
||||
}, 10000);
|
||||
}, 10000));
|
||||
|
||||
window.setInterval(() => {
|
||||
this.#intervals.push(window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE){
|
||||
this.getBullseye((data: BullseyesData) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
@ -430,9 +437,9 @@ export class ServerManager {
|
||||
return data.time;
|
||||
});
|
||||
}
|
||||
}, 10000);
|
||||
}, 10000));
|
||||
|
||||
window.setInterval(() => {
|
||||
this.#intervals.push(window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
|
||||
this.getLogs((data: any) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
@ -440,27 +447,27 @@ export class ServerManager {
|
||||
return data.time;
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}, 1000));
|
||||
|
||||
window.setInterval(() => {
|
||||
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);
|
||||
}, 250));
|
||||
|
||||
window.setInterval(() => {
|
||||
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);
|
||||
}, 250));
|
||||
|
||||
window.setInterval(() => {
|
||||
this.#intervals.push(window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
|
||||
this.getUnits((buffer: ArrayBuffer) => {
|
||||
var time = getApp().getUnitsManager()?.update(buffer);
|
||||
@ -484,10 +491,10 @@ export class ServerManager {
|
||||
}
|
||||
|
||||
}
|
||||
}, ( this.getServerIsPaused() ? 500 : 5000 ));
|
||||
}, ( this.getServerIsPaused() ? 500 : 5000 )));
|
||||
|
||||
// Mission clock and elapsed time
|
||||
window.setInterval( () => {
|
||||
this.#intervals.push(window.setInterval( () => {
|
||||
|
||||
if ( !this.getConnected() || this.#serverIsPaused ) {
|
||||
return;
|
||||
@ -501,16 +508,16 @@ export class ServerManager {
|
||||
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 );
|
||||
}, 1000));
|
||||
|
||||
window.setInterval(() => {
|
||||
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);
|
||||
}, 5000));
|
||||
}
|
||||
|
||||
refreshAll() {
|
||||
|
||||
60
client/src/unit/contextaction.ts
Normal file
@ -0,0 +1,60 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
25
client/src/unit/contextactionset.ts
Normal file
@ -0,0 +1,25 @@
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import { getApp } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class AircraftDatabase extends UnitDatabase {
|
||||
|
||||
@ -3,7 +3,7 @@ import { getApp } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
|
||||
export class UnitDatabase {
|
||||
export abstract class UnitDatabase {
|
||||
blueprints: { [key: string]: UnitBlueprint } = {};
|
||||
#url: string;
|
||||
|
||||
@ -31,9 +31,7 @@ export class UnitDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
return "";
|
||||
}
|
||||
abstract getCategory(): string;
|
||||
|
||||
/* Gets a specific blueprint by name */
|
||||
getByName(name: string) {
|
||||
@ -240,4 +238,15 @@ export class UnitDatabase {
|
||||
getSpawnPointsByName(name: string) {
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
getUnkownUnit(name: string): UnitBlueprint {
|
||||
return {
|
||||
name: name,
|
||||
enabled: true,
|
||||
coalition: 'neutral',
|
||||
era: 'N/A',
|
||||
label: name,
|
||||
shortLabel: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point, Circle } from 'leaflet';
|
||||
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point } from 'leaflet';
|
||||
import { getApp } from '..';
|
||||
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM, getGroundElevation, coalitionToEnum, nmToFt, nmToM } from '../other/utils';
|
||||
import { enumToCoalition, enumToEmissioNCountermeasure, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM, getGroundElevation, coalitionToEnum, nmToFt, nmToM } 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, GROUND_UNIT_AIR_DEFENCE_REGEX } from '../constants/constants';
|
||||
import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, GROUPING_ZOOM_TRANSITION, 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';
|
||||
@ -13,6 +13,8 @@ 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',
|
||||
@ -190,15 +192,17 @@ export abstract class Unit extends CustomMarker {
|
||||
|
||||
/* Deselect units if they are hidden */
|
||||
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
|
||||
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
|
||||
this.#updateMarker();
|
||||
this.setSelected(this.getSelected() && !this.getHidden());
|
||||
});
|
||||
|
||||
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
|
||||
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
|
||||
document.addEventListener("toggleMarkerVisibility", (ev: CustomEventInit) => {
|
||||
this.#updateMarker();
|
||||
this.setSelected(this.getSelected() && !this.getHidden());
|
||||
});
|
||||
|
||||
/* Update the marker when the visibility options change */
|
||||
document.addEventListener("mapVisibilityOptionsChanged", (ev: CustomEventInit) => {
|
||||
/* 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 */
|
||||
@ -212,7 +216,7 @@ export abstract class Unit extends CustomMarker {
|
||||
});
|
||||
}
|
||||
|
||||
/********************** Abstract methods *************************/
|
||||
/********************** Abstract methods *************************/
|
||||
/** Get the unit category string
|
||||
*
|
||||
* @returns string The unit category
|
||||
@ -228,9 +232,20 @@ export abstract class Unit extends CustomMarker {
|
||||
|
||||
/** Get the actions that this unit can perform
|
||||
*
|
||||
* @returns Object containing the available actions
|
||||
*/
|
||||
abstract getActions(): {[key: string]: { text: string, tooltip: string, type: string}};
|
||||
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)
|
||||
*
|
||||
@ -248,7 +263,7 @@ export abstract class Unit extends CustomMarker {
|
||||
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) {
|
||||
@ -320,7 +335,7 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
/* If the unit is selected or if the view is centered on this unit, sent the update signal so that other elements like the UnitControlPanel can be updated. */
|
||||
if (this.getSelected() || getApp().getMap().getCenterUnit() === this)
|
||||
if (this.getSelected() || getApp().getMap().getCenteredOnUnit() === this)
|
||||
document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
|
||||
}
|
||||
|
||||
@ -379,14 +394,6 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns string containing the marker category
|
||||
*/
|
||||
getMarkerCategory(): string {
|
||||
return getMarkerCategoryByName(this.getName());
|
||||
}
|
||||
|
||||
/** Get a database of information also in this unit's category
|
||||
*
|
||||
* @returns UnitDatabase
|
||||
@ -421,7 +428,7 @@ export abstract class Unit extends CustomMarker {
|
||||
else {
|
||||
this.#clearContacts();
|
||||
this.#clearPath();
|
||||
this.#clearTarget();
|
||||
this.#clearTargetPosition();
|
||||
}
|
||||
|
||||
/* When the group leader is selected, if grouping is active, all the other group members are also selected */
|
||||
@ -456,7 +463,7 @@ export abstract class Unit extends CustomMarker {
|
||||
return this.#selected;
|
||||
}
|
||||
|
||||
/** Set the number of the hotgroup to which the unit belongs
|
||||
/** Set the number of the hotgroup to which the unit belongss
|
||||
*
|
||||
* @param hotgroup (number)
|
||||
*/
|
||||
@ -498,7 +505,7 @@ export abstract class Unit extends CustomMarker {
|
||||
* @returns Unit[]
|
||||
*/
|
||||
getGroupMembers() {
|
||||
if (this.#group !== null)
|
||||
if (this.#group !== null)
|
||||
return this.#group.getMembers().filter((unit: Unit) => { return unit != this; })
|
||||
return [];
|
||||
}
|
||||
@ -508,7 +515,7 @@ export abstract class Unit extends CustomMarker {
|
||||
* @returns Unit The leader of the group
|
||||
*/
|
||||
getGroupLeader() {
|
||||
if (this.#group !== null)
|
||||
if (this.#group !== null)
|
||||
return this.#group.getLeader();
|
||||
return null;
|
||||
}
|
||||
@ -530,7 +537,7 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
getDatabaseEntry() {
|
||||
return this.getDatabase()?.getByName(this.#name);
|
||||
return this.getDatabase()?.getByName(this.#name) ?? this.getDatabase()?.getUnkownUnit(this.getName());
|
||||
}
|
||||
|
||||
getGroup() {
|
||||
@ -572,7 +579,7 @@ export abstract class Unit extends CustomMarker {
|
||||
|
||||
var iconOptions = this.getIconOptions();
|
||||
|
||||
/* Generate and append elements depending on active options */
|
||||
/* Generate and append elements depending on active options */
|
||||
/* Velocity vector */
|
||||
if (iconOptions.showVvi) {
|
||||
var vvi = document.createElement("div");
|
||||
@ -600,7 +607,7 @@ export abstract class Unit extends CustomMarker {
|
||||
/* 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.getMarkerCategory();
|
||||
marker = this.getDatabaseEntry()?.markerFile ?? this.getDefaultMarker();
|
||||
else
|
||||
marker = "aircraft";
|
||||
img.src = `/resources/theme/images/units/${marker}.svg`;
|
||||
@ -690,13 +697,11 @@ export abstract class Unit extends CustomMarker {
|
||||
/* 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.getCategory() == "GroundUnit" && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION &&
|
||||
(this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) &&
|
||||
!(this.getSelected()
|
||||
);
|
||||
(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.#alive);
|
||||
this.setHidden(hidden || !this.getAlive());
|
||||
}
|
||||
|
||||
setHidden(hidden: boolean) {
|
||||
@ -776,15 +781,11 @@ export abstract class Unit extends CustomMarker {
|
||||
return this.getDatabaseEntry()?.canRearm === true;
|
||||
}
|
||||
|
||||
canLandAtPoint() {
|
||||
return this.getCategory() === "Helicopter";
|
||||
}
|
||||
|
||||
canAAA() {
|
||||
return this.getDatabaseEntry()?.canAAA === true;
|
||||
}
|
||||
|
||||
indirectFire() {
|
||||
isIndirectFire() {
|
||||
return this.getDatabaseEntry()?.indirectFire === true;
|
||||
}
|
||||
|
||||
@ -941,6 +942,7 @@ export abstract class Unit extends CustomMarker {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Remove coalition
|
||||
scenicAAA() {
|
||||
var coalition = "neutral";
|
||||
if (this.getCoalition() === "red")
|
||||
@ -950,6 +952,7 @@ export abstract class Unit extends CustomMarker {
|
||||
getApp().getServerManager().scenicAAA(this.ID, coalition);
|
||||
}
|
||||
|
||||
// TODO: Remove coalition
|
||||
missOnPurpose() {
|
||||
var coalition = "neutral";
|
||||
if (this.getCoalition() === "red")
|
||||
@ -973,24 +976,6 @@ export abstract class Unit extends CustomMarker {
|
||||
getApp().getServerManager().setShotsIntensity(this.ID, shotsIntensity);
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
executeAction(e: any, action: string) {
|
||||
if (action === "center-map")
|
||||
getApp().getMap().centerOnUnit(this.ID);
|
||||
if (action === "attack")
|
||||
getApp().getUnitsManager().selectedUnitsAttackUnit(this.ID);
|
||||
else if (action === "refuel")
|
||||
getApp().getUnitsManager().selectedUnitsRefuel();
|
||||
else if (action === "group-ground" || action === "group-navy")
|
||||
getApp().getUnitsManager().selectedUnitsCreateGroup();
|
||||
else if (action === "scenic-aaa")
|
||||
getApp().getUnitsManager().selectedUnitsScenicAAA();
|
||||
else if (action === "miss-aaa")
|
||||
getApp().getUnitsManager().selectedUnitsMissOnPurpose();
|
||||
else if (action === "follow")
|
||||
this.#showFollowOptions(e);
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
onAdd(map: Map): this {
|
||||
super.onAdd(map);
|
||||
@ -1001,6 +986,56 @@ export abstract class Unit extends CustomMarker {
|
||||
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 ((<HTMLInputElement>dialog.querySelector(`#formation-${clock}`)).checked)
|
||||
break
|
||||
clock++;
|
||||
}
|
||||
var angleDeg = 360 - (clock - 1) * 45;
|
||||
var angleRad = deg2rad(angleDeg);
|
||||
var distance = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#distance`)?.querySelector("input")).value));
|
||||
var upDown = ftToM(parseInt((<HTMLInputElement>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 */
|
||||
@ -1039,102 +1074,20 @@ export abstract class Unit extends CustomMarker {
|
||||
});
|
||||
}
|
||||
|
||||
getActionOptions() {
|
||||
var options: { [key: string]: { text: string, tooltip: string, type: string } } | null = null;
|
||||
#onContextMenu(e: any) {
|
||||
var contextActionSet = new ContextActionSet();
|
||||
|
||||
var units = getApp().getUnitsManager().getSelectedUnits();
|
||||
units.push(this);
|
||||
if (!units.includes(this))
|
||||
units.push(this);
|
||||
|
||||
/* Keep only the common "or" options or any "and" option */
|
||||
units.forEach((unit: Unit) => {
|
||||
var unitOptions = unit.getActions();
|
||||
if (options === null) {
|
||||
options = unitOptions;
|
||||
} else {
|
||||
/* Options of "or" type get shown if any one unit has it*/
|
||||
for (let optionKey in unitOptions) {
|
||||
if (unitOptions[optionKey].type == "or") {
|
||||
options[optionKey] = unitOptions[optionKey];
|
||||
}
|
||||
}
|
||||
unit.appendContextActions(contextActionSet, this, null);
|
||||
})
|
||||
|
||||
/* Options of "and" type get shown if ALL units have it */
|
||||
for (let optionKey in options) {
|
||||
if (!(optionKey in unitOptions)) {
|
||||
delete options[optionKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return options ?? {};
|
||||
}
|
||||
|
||||
#onContextMenu(e: any) {
|
||||
var options = this.getActionOptions();
|
||||
|
||||
if (Object.keys(options).length > 0) {
|
||||
if (Object.keys(contextActionSet.getContextActions()).length > 0) {
|
||||
getApp().getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
|
||||
getApp().getMap().getUnitContextMenu().setOptions(options, (option: string) => {
|
||||
getApp().getMap().hideUnitContextMenu();
|
||||
this.executeAction(e, option);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#showFollowOptions(e: any) {
|
||||
var options: { [key: string]: { text: string, tooltip: string } } = {};
|
||||
|
||||
options = {
|
||||
'trail': { text: "Trail", tooltip: "Follow unit in trail formation" },
|
||||
'echelon-lh': { text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation" },
|
||||
'echelon-rh': { text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation" },
|
||||
'line-abreast-lh': { text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation" },
|
||||
'line-abreast-rh': { text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation" },
|
||||
'front': { text: "Front", tooltip: "Fly in front of unit" },
|
||||
'diamond': { text: "Diamond", tooltip: "Follow unit in diamond formation" },
|
||||
'custom': { text: "Custom", tooltip: "Set a custom formation position" },
|
||||
}
|
||||
|
||||
getApp().getMap().getUnitContextMenu().setOptions(options, (option: string) => {
|
||||
getApp().getMap().hideUnitContextMenu();
|
||||
this.#applyFollowOptions(option);
|
||||
});
|
||||
|
||||
getApp().getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
|
||||
}
|
||||
|
||||
#applyFollowOptions(action: string) {
|
||||
if (action === "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 ((<HTMLInputElement>dialog.querySelector(`#formation-${clock}`)).checked)
|
||||
break
|
||||
clock++;
|
||||
}
|
||||
var angleDeg = 360 - (clock - 1) * 45;
|
||||
var angleRad = deg2rad(angleDeg);
|
||||
var distance = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#distance`)?.querySelector("input")).value));
|
||||
var upDown = ftToM(parseInt((<HTMLInputElement>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().selectedUnitsFollowUnit(this.ID, { "x": x, "y": y, "z": z });
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
getApp().getUnitsManager().selectedUnitsFollowUnit(this.ID, undefined, action);
|
||||
getApp().getMap().getUnitContextMenu().setContextActions(contextActionSet);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1313,11 +1266,13 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
#clearPath() {
|
||||
for (let WP in this.#pathMarkers) {
|
||||
getApp().getMap().removeLayer(this.#pathMarkers[WP]);
|
||||
if (this.#pathPolyline.getLatLngs().length != 0) {
|
||||
for (let WP in this.#pathMarkers) {
|
||||
getApp().getMap().removeLayer(this.#pathMarkers[WP]);
|
||||
}
|
||||
this.#pathMarkers = [];
|
||||
this.#pathPolyline.setLatLngs([]);
|
||||
}
|
||||
this.#pathMarkers = [];
|
||||
this.#pathPolyline.setLatLngs([]);
|
||||
}
|
||||
|
||||
#drawContacts() {
|
||||
@ -1471,7 +1426,7 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
}
|
||||
else
|
||||
this.#clearTarget();
|
||||
this.#clearTargetPosition();
|
||||
}
|
||||
|
||||
#drawTargetPosition(targetPosition: LatLng) {
|
||||
@ -1480,10 +1435,25 @@ export abstract class Unit extends CustomMarker {
|
||||
if (!getApp().getMap().hasLayer(this.#targetPositionPolyline))
|
||||
this.#targetPositionPolyline.addTo(getApp().getMap());
|
||||
this.#targetPositionMarker.setLatLng(new LatLng(targetPosition.lat, targetPosition.lng));
|
||||
this.#targetPositionPolyline.setLatLngs([new LatLng(this.#position.lat, this.#position.lng), new LatLng(targetPosition.lat, targetPosition.lng)])
|
||||
|
||||
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)])
|
||||
}
|
||||
}
|
||||
|
||||
#clearTarget() {
|
||||
#clearTargetPosition() {
|
||||
if (getApp().getMap().hasLayer(this.#targetPositionMarker))
|
||||
this.#targetPositionMarker.removeFrom(getApp().getMap());
|
||||
|
||||
@ -1492,7 +1462,7 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
#onZoom(e: any) {
|
||||
if (this.checkZoomRedraw())
|
||||
if (this.checkZoomRedraw())
|
||||
this.#redrawMarker();
|
||||
this.#updateMarker();
|
||||
}
|
||||
@ -1516,35 +1486,24 @@ export abstract class AirUnit extends Unit {
|
||||
};
|
||||
}
|
||||
|
||||
getActions() {
|
||||
var options: { [key: string]: { text: string, tooltip: string, type: string } } = {};
|
||||
|
||||
/* Options if this unit is not selected */
|
||||
if (!this.getSelected()) {
|
||||
/* Someone else is selected */
|
||||
if (getApp().getUnitsManager().getSelectedUnits().length > 0) {
|
||||
options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons", type: "or" };
|
||||
options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position", type: "or" };
|
||||
} else {
|
||||
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" };
|
||||
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); });
|
||||
}
|
||||
}
|
||||
/* Options if this unit is selected*/
|
||||
else if (this.getSelected()) {
|
||||
/* This is the only selected unit */
|
||||
if (getApp().getUnitsManager().getSelectedUnits().length == 1) {
|
||||
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" };
|
||||
} else {
|
||||
options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position", type: "or" };
|
||||
}
|
||||
|
||||
options["refuel"] = { text: "Air to air refuel", tooltip: "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB.", type: "and" }; // TODO Add some way of knowing which aircraft can AAR
|
||||
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) });
|
||||
}
|
||||
/* All other options */
|
||||
else {
|
||||
/* Provision */
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1556,6 +1515,22 @@ export class Aircraft extends AirUnit {
|
||||
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 {
|
||||
@ -1566,6 +1541,21 @@ export class Helicopter extends AirUnit {
|
||||
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 {
|
||||
@ -1590,37 +1580,35 @@ export class GroundUnit extends Unit {
|
||||
};
|
||||
}
|
||||
|
||||
getActions() {
|
||||
var options: { [key: string]: { text: string, tooltip: string, type: string } } = {};
|
||||
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) });
|
||||
|
||||
/* Options if this unit is not selected */
|
||||
if (!this.getSelected()) {
|
||||
/* Someone else is selected */
|
||||
if (getApp().getUnitsManager().getSelectedUnits().length > 0) {
|
||||
options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons", type: "or" };
|
||||
} else {
|
||||
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" };
|
||||
}
|
||||
}
|
||||
/* Options if this unit is selected*/
|
||||
else if (this.getSelected()) {
|
||||
/* This is the only selected unit */
|
||||
if (getApp().getUnitsManager().getSelectedUnits().length == 1) {
|
||||
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" };
|
||||
} else {
|
||||
options["group-ground"] = { text: "Create group", tooltip: "Create a group from the selected units", type: "and" };
|
||||
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 (this.canAAA()) {
|
||||
options["scenic-aaa"] = { text: "Scenic AAA", tooltip: "Shoot AAA in the air without aiming at any target, when a enemy unit gets close enough. WARNING: works correctly only on neutral units, blue or red units will aim", type: "and" };
|
||||
options["miss-aaa"] = { text: "Miss on purpose AAA", tooltip: "Shoot AAA towards the closest enemy unit, but don't aim precisely. WARNING: works correctly only on neutral units, blue or red units will aim", type: "and" };
|
||||
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) });
|
||||
}
|
||||
}
|
||||
/* All other options */
|
||||
else {
|
||||
/* Provision */
|
||||
if (this.canAAA()) {
|
||||
contextActionSet.addContextAction(this, "scenic-aaa", "Scenic AAA", "Shoot AAA in the air without aiming at any target, when a 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", "Miss on purpose", "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
|
||||
});
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
@ -1645,9 +1633,9 @@ export class GroundUnit extends Unit {
|
||||
unitWhenGrouped = (member !== null ? member?.getDatabaseEntry()?.unitWhenGrouped : unitWhenGrouped);
|
||||
}
|
||||
if (unitWhenGrouped)
|
||||
return this.getDatabase()?.getByName(unitWhenGrouped);
|
||||
return this.getDatabase()?.getByName(unitWhenGrouped) ?? this.getDatabase()?.getUnkownUnit(this.getName());
|
||||
else
|
||||
return this.getDatabase()?.getByName(this.getName());
|
||||
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 */
|
||||
@ -1656,6 +1644,17 @@ export class GroundUnit extends Unit {
|
||||
(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 {
|
||||
@ -1680,36 +1679,21 @@ export class NavyUnit extends Unit {
|
||||
};
|
||||
}
|
||||
|
||||
getActions() {
|
||||
var options: { [key: string]: { text: string, tooltip: string, type: string } } = {};
|
||||
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) });
|
||||
|
||||
/* Options if this unit is not selected */
|
||||
if (!this.getSelected()) {
|
||||
/* Someone else is selected */
|
||||
if (getApp().getUnitsManager().getSelectedUnits().length > 0) {
|
||||
options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons", type: "or" };
|
||||
} else {
|
||||
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" };
|
||||
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); });
|
||||
}
|
||||
}
|
||||
/* Options if this unit is selected */
|
||||
else if (this.getSelected()) {
|
||||
/* This is the only selected unit */
|
||||
if (getApp().getUnitsManager().getSelectedUnits().length == 1) {
|
||||
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" };
|
||||
} else {
|
||||
options["group-navy"] = { text: "Create group", tooltip: "Create a group from the selected units", type: "and" };
|
||||
}
|
||||
}
|
||||
/* All other options */
|
||||
else {
|
||||
/* Provision */
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
getMarkerCategory() {
|
||||
return "navyunit";
|
||||
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() {
|
||||
@ -1720,4 +1704,12 @@ export class NavyUnit extends Unit {
|
||||
var blueprint = navyUnitDatabase.getByName(this.getName());
|
||||
return blueprint?.type ? blueprint.type : "";
|
||||
}
|
||||
|
||||
getMarkerCategory() {
|
||||
return "navyunit";
|
||||
}
|
||||
|
||||
getDefaultMarker() {
|
||||
return "navyunit";
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ 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 "./citiesDatabase";
|
||||
import { citiesDatabase } from "./databases/citiesdatabase";
|
||||
import { aircraftDatabase } from "./databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "./databases/helicopterdatabase";
|
||||
import { navyUnitDatabase } from "./databases/navyunitdatabase";
|
||||
@ -40,15 +40,15 @@ export class UnitsManager {
|
||||
|
||||
document.addEventListener('commandModeOptionsChanged', () => { Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility()) });
|
||||
document.addEventListener('contactsUpdated', (e: CustomEvent) => { this.#requestDetectionUpdate = true });
|
||||
document.addEventListener('copy', () => this.selectedUnitsCopy());
|
||||
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete());
|
||||
document.addEventListener('explodeSelectedUnits', (e: any) => this.selectedUnitsDelete(true, e.detail.type));
|
||||
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.pasteUnits());
|
||||
document.addEventListener('selectedUnitsChangeAltitude', (e: any) => { this.selectedUnitsChangeAltitude(e.detail.type) });
|
||||
document.addEventListener('selectedUnitsChangeSpeed', (e: any) => { this.selectedUnitsChangeSpeed(e.detail.type) });
|
||||
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() });
|
||||
@ -134,8 +134,8 @@ export class UnitsManager {
|
||||
this.#units[ID]?.setData(dataExtractor);
|
||||
}
|
||||
|
||||
/* Update the unit groups */
|
||||
for (let ID in this.#units) {
|
||||
/* Update the unit groups */
|
||||
for (let ID in this.#units) {
|
||||
const unit = this.#units[ID];
|
||||
const groupName = unit.getGroupName();
|
||||
|
||||
@ -145,7 +145,7 @@ export class UnitsManager {
|
||||
this.#groups[groupName] = new Group(groupName);
|
||||
|
||||
/* If the unit was not assigned to a group yet, assign it */
|
||||
if (unit.getGroup() === null)
|
||||
if (unit.getGroup() === null)
|
||||
this.#groups[groupName].addMember(unit);
|
||||
}
|
||||
}
|
||||
@ -264,6 +264,10 @@ export class UnitsManager {
|
||||
if (options.showProtectionReminder === true && numProtectedUnits > selectedUnits.length && selectedUnits.length === 0) {
|
||||
const messageText = (numProtectedUnits === 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 = <HTMLElement>document.querySelector("#unit-visibility-control button.lock");
|
||||
lock.classList.add("prompt");
|
||||
setTimeout(() => lock.classList.remove("prompt"), 4000);
|
||||
}
|
||||
|
||||
if (options.onlyOnePerGroup) {
|
||||
@ -321,11 +325,13 @@ export class UnitsManager {
|
||||
getUnitsVariable(variableGetter: CallableFunction, units: Unit[]) {
|
||||
if (units.length == 0)
|
||||
return undefined;
|
||||
return units.map((unit: Unit) => {
|
||||
return variableGetter(unit);
|
||||
})?.reduce((a: any, b: any) => {
|
||||
return a === b ? a : 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
|
||||
@ -353,23 +359,23 @@ export class UnitsManager {
|
||||
* @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.
|
||||
*/
|
||||
selectedUnitsAddDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
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 });
|
||||
|
||||
if (selectedUnits.length === 0)
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
/* 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.selectedUnitsComputeGroupDestination(latlng, rotation);
|
||||
unitDestinations = this.computeGroupDestination(latlng, rotation);
|
||||
else
|
||||
selectedUnits.forEach((unit: Unit) => { unitDestinations[unit.ID] = latlng; });
|
||||
|
||||
for (let idx in selectedUnits) {
|
||||
const unit = selectedUnits[idx];
|
||||
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())
|
||||
@ -382,18 +388,22 @@ export class UnitsManager {
|
||||
if (unit.ID in unitDestinations)
|
||||
unit.addDestination(unitDestinations[unit.ID]);
|
||||
}
|
||||
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, " new destination added");
|
||||
});
|
||||
this.#showActionMessage(units, " new destination added");
|
||||
}
|
||||
|
||||
/** Clear the destinations of all the selected units
|
||||
*
|
||||
*/
|
||||
selectedUnitsClearDestinations() {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: false });
|
||||
for (let idx in selectedUnits) {
|
||||
const unit = selectedUnits[idx];
|
||||
clearDestinations(units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: false });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
for (let idx in units) {
|
||||
const unit = units[idx];
|
||||
if (unit.getState() === "follow") {
|
||||
const leader = this.getUnitByID(unit.getLeaderID())
|
||||
if (leader && leader.getSelected())
|
||||
@ -409,179 +419,239 @@ export class UnitsManager {
|
||||
/** 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.
|
||||
*/
|
||||
selectedUnitsLandAt(latlng: LatLng) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].landAt(latlng);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, " landing");
|
||||
landAt(latlng: LatLng, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsChangeSpeed(speedChange: string) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].changeSpeed(speedChange);
|
||||
}
|
||||
changeSpeed(speedChange: string, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsChangeAltitude(altitudeChange: string) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].changeAltitude(altitudeChange);
|
||||
}
|
||||
changeAltitude(altitudeChange: string, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetSpeed(speed: number) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setSpeed(speed);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `setting speed to ${msToKnots(speed)} kts`);
|
||||
setSpeed(speed: number, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetSpeedType(speedType: string) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setSpeedType(speedType);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `setting speed type to ${speedType}`);
|
||||
setSpeedType(speedType: string, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetAltitude(altitude: number) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setAltitude(altitude);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `setting altitude to ${mToFt(altitude)} ft`);
|
||||
setAltitude(altitude: number, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetAltitudeType(altitudeType: string) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setAltitudeType(altitudeType);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `setting altitude type to ${altitudeType}`);
|
||||
setAltitudeType(altitudeType: string, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetROE(ROE: string) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setROE(ROE);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `ROE set to ${ROE}`);
|
||||
setROE(ROE: string, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetReactionToThreat(reactionToThreat: string) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setReactionToThreat(reactionToThreat);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `reaction to threat set to ${reactionToThreat}`);
|
||||
setReactionToThreat(reactionToThreat: string, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetEmissionsCountermeasures(emissionCountermeasure: string) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setEmissionsCountermeasures(emissionCountermeasure);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `emissions & countermeasures set to ${emissionCountermeasure}`);
|
||||
setEmissionsCountermeasures(emissionCountermeasure: string, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetOnOff(onOff: boolean) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setOnOff(onOff);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `unit active set to ${onOff}`);
|
||||
setOnOff(onOff: boolean, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetFollowRoads(followRoads: boolean) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setFollowRoads(followRoads);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `follow roads set to ${followRoads}`);
|
||||
setFollowRoads(followRoads: boolean, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetOperateAs(operateAsBool: boolean) {
|
||||
setOperateAs(operateAsBool: boolean, units: Unit[] | null = null) {
|
||||
var operateAs = operateAsBool ? "blue" : "red";
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setOperateAs(operateAs);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `operate as set to ${operateAs}`);
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsAttackUnit(ID: number) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].attackUnit(ID);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getUnitName()}`);
|
||||
attackUnit(ID: number, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsRefuel() {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].refuel();
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `sent to nearest tanker`);
|
||||
refuel(units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
units.forEach((unit: Unit) => unit.refuel());
|
||||
this.#showActionMessage(units, `sent to nearest tanker`);
|
||||
}
|
||||
|
||||
/** Instruct the selected units to follow another unit in a formation. Only works for aircrafts and helicopters.
|
||||
@ -589,8 +659,15 @@ export class UnitsManager {
|
||||
* @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.
|
||||
*/
|
||||
selectedUnitsFollowUnit(ID: number, offset?: { "x": number, "y": number, "z": number }, formation?: string) {
|
||||
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 });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
if (offset == undefined) {
|
||||
/* Simple formations with fixed offsets */
|
||||
offset = { "x": 0, "y": 0, "z": 0 };
|
||||
@ -603,16 +680,11 @@ export class UnitsManager {
|
||||
else offset = undefined;
|
||||
}
|
||||
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (selectedUnits.length === 0)
|
||||
return;
|
||||
|
||||
var count = 1;
|
||||
var xr = 0; var yr = 1; var zr = -1;
|
||||
var layer = 1;
|
||||
for (let idx in selectedUnits) {
|
||||
var unit = selectedUnits[idx];
|
||||
for (let idx in units) {
|
||||
var unit = units[idx];
|
||||
if (unit.ID !== ID) {
|
||||
if (offset != undefined)
|
||||
/* Offset is set, apply it */
|
||||
@ -634,51 +706,69 @@ export class UnitsManager {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getUnitName()}`);
|
||||
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.
|
||||
*/
|
||||
selectedUnitsBombPoint(latlng: LatLng) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].bombPoint(latlng);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `unit bombing point`);
|
||||
bombPoint(latlng: LatLng, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsCarpetBomb(latlng: LatLng) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].carpetBomb(latlng);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `unit carpet bombing point`);
|
||||
carpetBomb(latlng: LatLng, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsFireAtArea(latlng: LatLng) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].fireAtArea(latlng);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `unit firing at area`);
|
||||
fireAtArea(latlng: LatLng, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSimulateFireFight(latlng: LatLng) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
simulateFireFight(latlng: LatLng, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
getGroundElevation(latlng, (response: string) => {
|
||||
var groundElevation: number | null = null;
|
||||
try {
|
||||
@ -686,70 +776,86 @@ export class UnitsManager {
|
||||
} catch {
|
||||
console.warn("Simulate fire fight: could not retrieve ground elevation")
|
||||
}
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].simulateFireFight(latlng, groundElevation);
|
||||
}
|
||||
units?.forEach((unit: Unit) => unit.simulateFireFight(latlng, groundElevation));
|
||||
});
|
||||
this.#showActionMessage(selectedUnits, `unit simulating fire fight`);
|
||||
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.
|
||||
*/
|
||||
selectedUnitsScenicAAA() {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].scenicAAA();
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `unit set to perform scenic AAA`);
|
||||
scenicAAA(units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
units.forEach((unit: Unit) => unit.scenicAAA());
|
||||
this.#showActionMessage(units, `unit set to perform scenic AAA`);
|
||||
}
|
||||
|
||||
/** Instruct units to enter into 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.
|
||||
*/
|
||||
selectedUnitsMissOnPurpose() {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].missOnPurpose();
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `unit set to perform miss-on-purpose AAA`);
|
||||
missOnPurpose(units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsLandAtPoint(latlng: LatLng) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
landAtPoint(latlng: LatLng, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
|
||||
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].landAtPoint(latlng);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `unit landing at point`);
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetShotsScatter(shotsScatter: number) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setShotsScatter(shotsScatter);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `shots scatter set to ${shotsScatter}`);
|
||||
setShotsScatter(shotsScatter: number, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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.
|
||||
*/
|
||||
selectedUnitsSetShotsIntensity(shotsIntensity: number) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setShotsIntensity(shotsIntensity);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `shots intensity set to ${shotsIntensity}`);
|
||||
setShotsIntensity(shotsIntensity: number, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
units.forEach((unit: Unit) => unit.setShotsIntensity(shotsIntensity));
|
||||
this.#showActionMessage(units, `shots intensity set to ${shotsIntensity}`);
|
||||
}
|
||||
|
||||
/*********************** Control operations on selected units ************************/
|
||||
@ -773,15 +879,18 @@ export class UnitsManager {
|
||||
/** Groups the selected units in a single (DCS) group, if all the units have the same category
|
||||
*
|
||||
*/
|
||||
selectedUnitsCreateGroup() {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: false, showProtectionReminder: true });
|
||||
if (this.getUnitsCategories(selectedUnits).length == 1) {
|
||||
var units: { ID: number, location: LatLng }[] = [];
|
||||
for (let idx in selectedUnits) {
|
||||
var unit = selectedUnits[idx];
|
||||
units.push({ ID: unit.ID, location: unit.getPosition() });
|
||||
}
|
||||
getApp().getServerManager().cloneUnits(units, true, 0 /* No spawn points, we delete the original units */);
|
||||
createGroup(units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: false, showProtectionReminder: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
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`);
|
||||
}
|
||||
@ -790,33 +899,40 @@ export class UnitsManager {
|
||||
/** 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.
|
||||
*/
|
||||
selectedUnitsSetHotgroup(hotgroup: number) {
|
||||
setHotgroup(hotgroup: number, units: Unit[] | null = null) {
|
||||
this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHotgroup(null));
|
||||
this.selectedUnitsAddToHotgroup(hotgroup);
|
||||
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.
|
||||
*/
|
||||
selectedUnitsAddToHotgroup(hotgroup: number) {
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setHotgroup(hotgroup);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `added to hotgroup ${hotgroup}`);
|
||||
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
|
||||
*/
|
||||
selectedUnitsDelete(explosion: boolean = false, explosionType: string = "") {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeProtected: true }); /* Can be applied to humans too */
|
||||
const selectionContainsAHuman = selectedUnits.some((unit: Unit) => {
|
||||
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 */
|
||||
|
||||
if (units.length === 0)
|
||||
return;
|
||||
|
||||
const selectionContainsAHuman = units.some((unit: Unit) => {
|
||||
return unit.getHuman() === true;
|
||||
});
|
||||
|
||||
@ -825,14 +941,12 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
const doDelete = (explosion = false, explosionType = "", immediate = false) => {
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].delete(explosion, explosionType, immediate);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `deleted`);
|
||||
units?.forEach((unit: Unit) => unit.delete(explosion, explosionType, immediate));
|
||||
this.#showActionMessage(units as Unit[], `deleted`);
|
||||
}
|
||||
|
||||
if (selectedUnits.length >= DELETE_SLOW_THRESHOLD)
|
||||
this.#showSlowDeleteDialog(selectedUnits).then((action: any) => {
|
||||
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")
|
||||
@ -847,21 +961,28 @@ export class UnitsManager {
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
selectedUnitsComputeGroupDestination(latlng: LatLng, rotation: number) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true });
|
||||
computeGroupDestination(latlng: LatLng, rotation: number, units: Unit[] | null = null) {
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true });
|
||||
|
||||
if (units.length === 0)
|
||||
return {};
|
||||
|
||||
/* Compute the center of the group */
|
||||
var len = units.length;
|
||||
var center = { x: 0, y: 0 };
|
||||
selectedUnits.forEach((unit: Unit) => {
|
||||
units.forEach((unit: Unit) => {
|
||||
var mercator = latLngToMercator(unit.getPosition().lat, unit.getPosition().lng);
|
||||
center.x += mercator.x / selectedUnits.length;
|
||||
center.y += mercator.y / selectedUnits.length;
|
||||
center.x += mercator.x / len;
|
||||
center.y += mercator.y / len;
|
||||
});
|
||||
|
||||
/* Compute the distances from the center of the group */
|
||||
var unitDestinations: { [key: number]: LatLng } = {};
|
||||
selectedUnits.forEach((unit: Unit) => {
|
||||
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 };
|
||||
|
||||
@ -883,9 +1004,18 @@ export class UnitsManager {
|
||||
/** Copy the selected units and store their properties in memory
|
||||
*
|
||||
*/
|
||||
selectedUnitsCopy() {
|
||||
copy(units: Unit[] | null = null) {
|
||||
if ( !getApp().getContextManager().getCurrentContext().getAllowUnitCopying() )
|
||||
return;
|
||||
|
||||
if (units === null)
|
||||
units = this.getSelectedUnits({ excludeHumans: true });
|
||||
|
||||
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(this.getSelectedUnits().map((unit: Unit) => { return unit.getData() }))); /* Can be applied to humans too */
|
||||
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`);
|
||||
}
|
||||
|
||||
@ -894,7 +1024,10 @@ export class UnitsManager {
|
||||
*
|
||||
* @returns True if units were pasted successfully
|
||||
*/
|
||||
pasteUnits() {
|
||||
paste() {
|
||||
if ( !getApp().getContextManager().getCurrentContext().getAllowUnitPasting() )
|
||||
return;
|
||||
|
||||
let spawnPoints = 0;
|
||||
|
||||
/* If spawns are restricted, check that the user has the necessary spawn points */
|
||||
@ -1081,7 +1214,7 @@ export class UnitsManager {
|
||||
#onKeyUp(event: KeyboardEvent) {
|
||||
if (!keyEventWasInInput(event)) {
|
||||
if (event.key === "Delete")
|
||||
this.selectedUnitsDelete();
|
||||
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));
|
||||
}
|
||||
@ -1130,9 +1263,9 @@ export class UnitsManager {
|
||||
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`);
|
||||
}
|
||||
|
||||
#showSlowDeleteDialog(selectedUnits: Unit[]) {
|
||||
#showSlowDeleteDialog(units: Unit[]) {
|
||||
let button: HTMLButtonElement | null = null;
|
||||
const deletionTime = Math.round(selectedUnits.length * DELETE_CYCLE_TIME).toString();
|
||||
const deletionTime = Math.round(units.length * DELETE_CYCLE_TIME).toString();
|
||||
const dialog = this.#slowDeleteDialog;
|
||||
const element = dialog.getElement();
|
||||
const listener = (ev: MouseEvent) => {
|
||||
@ -1140,7 +1273,7 @@ export class UnitsManager {
|
||||
button = ev.target;
|
||||
}
|
||||
|
||||
element.querySelectorAll(".deletion-count").forEach(el => el.innerHTML = selectedUnits.length.toString());
|
||||
element.querySelectorAll(".deletion-count").forEach(el => el.innerHTML = units.length.toString());
|
||||
element.querySelectorAll(".deletion-time").forEach(el => el.innerHTML = deletionTime);
|
||||
dialog.show();
|
||||
|
||||
@ -1160,9 +1293,9 @@ export class UnitsManager {
|
||||
|
||||
#showNumberOfSelectedProtectedUnits() {
|
||||
const map = getApp().getMap();
|
||||
const selectedUnits = this.getSelectedUnits();
|
||||
const numSelectedUnits = selectedUnits.length;
|
||||
const numProtectedUnits = selectedUnits.filter((unit: Unit) => map.unitIsProtected(unit)).length;
|
||||
const 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`);
|
||||
@ -1172,6 +1305,6 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
#unitIsProtected(unit: Unit) {
|
||||
return getApp().getMap().unitIsProtected(unit)
|
||||
return getApp().getMap().getIsUnitProtected(unit)
|
||||
}
|
||||
}
|
||||
@ -36,17 +36,9 @@ export class Weapon extends CustomMarker {
|
||||
super(new LatLng(0, 0), { riseOnHover: true, keyboard: false });
|
||||
|
||||
this.ID = ID;
|
||||
|
||||
/* Deselect units if they are hidden */
|
||||
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
|
||||
window.setTimeout(() => { !this.getHidden() }, 300);
|
||||
});
|
||||
|
||||
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
|
||||
window.setTimeout(() => { !this.getHidden() }, 300);
|
||||
});
|
||||
|
||||
document.addEventListener("mapVisibilityOptionsChanged", (ev: CustomEventInit) => {
|
||||
/* Update the marker when the options change */
|
||||
document.addEventListener("mapOptionsChanged", (ev: CustomEventInit) => {
|
||||
this.#updateMarker();
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<div id="map-contextmenu" class="ol-context-menu" oncontextmenu="return false;">
|
||||
<div id="active-coalition-label" data-coalition="blue"></div>
|
||||
<div class="upper-bar ol-panel">
|
||||
<div id="coalition-switch" class="ol-switch ol-coalition-switch"></div>
|
||||
<div class="switch-control coalition no-label"><div id="coalition-switch" class="ol-switch"></div></div>
|
||||
<button data-coalition="blue" id="aircraft-spawn-button" title="Spawn aircraft" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "aircraft" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/aircraft.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="helicopter-spawn-button" title="Spawn helicopter" data-on-click="mapContextMenuShow"
|
||||
@ -16,7 +16,6 @@
|
||||
data-on-click-params='{ "type": "more" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/more.svg" inject-svg></button>
|
||||
</div>
|
||||
<div id="more-options-button-bar" class="upper-bar ol-panel hide">
|
||||
<div id="coalition-switch" class="ol-switch ol-coalition-switch"></div>
|
||||
<button data-coalition="blue" id="navyunit-spawn-button" title="Spawn navy unit" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "navyunit" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/navyunit.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="smoke-spawn-button" title="Spawn smoke" data-on-click="mapContextMenuShow"
|
||||
|
||||
@ -3,14 +3,18 @@
|
||||
<head>
|
||||
<title>Olympus client</title>
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/olympus.css" />
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/leaflet/leaflet.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/leaflet/leaflet-gesture-handling.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/leaflet/leaflet.css" />
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/leaflet/leaflet-gesture-handling.css" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/resources/theme/theme.css" /> <!-- Theme specifc css, autorouted to point to active theme -->
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;600;700;800&display=swap">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;600;700;800&display=swap" />
|
||||
|
||||
<link rel="icon" href="/images/favicons/favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="images/favicons/apple-touch-icon.png"/>
|
||||
<link rel="manifest" href="/images/favicons/site.webmanifest" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -46,6 +50,12 @@
|
||||
|
||||
<!-- Grayout effect of the background when login prompt is visible -->
|
||||
<div id="gray-out"></div>
|
||||
|
||||
<!-- Loading screen -->
|
||||
<div id="loading-screen">
|
||||
<img src="images/olympus-500x500.png">
|
||||
<div>Loading DCS Olympus...</div>
|
||||
</div>
|
||||
|
||||
<script src="javascripts/bundle.js"></script>
|
||||
</body>
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
|
||||
<div id="unit-controls" class="ol-scrollable">
|
||||
<div id="flight-data">
|
||||
<h4>Controls</h4>
|
||||
<div id="speed-slider" class="ol-slider-container flight-control-ol-slider">
|
||||
<dl class="ol-data-grid">
|
||||
<dt>Speed</dt>
|
||||
@ -43,6 +42,8 @@
|
||||
<h5 id="categories-tooltip">Multiple categories selected</h5>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div id="roe">
|
||||
<h4>Rules of engagement</h4>
|
||||
<div id="roe-buttons-container" class="ol-group ol-button-box ol-option-button">
|
||||
@ -78,27 +79,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tanker-on" class="switch-control">
|
||||
<div id="tanker-on" class="switch-control yes-no">
|
||||
<h4>Enable tanker <img src="/resources/theme/images/icons/circle-question-regular.svg" title="Instructs the unit to operate as AAR tanker. A/A TACAN, radio frequency and callsign set in Settings dialog."></h4>
|
||||
<div id="tanker-on-switch" class="ol-switch"></div>
|
||||
</div>
|
||||
|
||||
<div id="AWACS-on" class="switch-control">
|
||||
<div id="AWACS-on" class="switch-control yes-no">
|
||||
<h4>Airborne Early Warning <img src="/resources/theme/images/icons/circle-question-regular.svg" title="Enables datalink and AI radio calls. Radio frequency and callsign set in Settings dialog."></h4>
|
||||
<div id="AWACS-on-switch" class="ol-switch"></div>
|
||||
</div>
|
||||
|
||||
<div id="operate-as" class="switch-control">
|
||||
<div id="operate-as" class="switch-control coalition">
|
||||
<h4>Operate as <img src="/resources/theme/images/icons/circle-question-regular.svg" title="Determines if the unit will target red or blue units when performing scenic tasks."></h4>
|
||||
<div id="operate-as-switch" class="ol-switch"></div>
|
||||
</div>
|
||||
|
||||
<div id="ai-on-off" class="switch-control">
|
||||
<div id="ai-on-off" class="switch-control yes-no">
|
||||
<h4>Unit active <img src="/resources/theme/images/icons/circle-question-regular.svg" title="Toggling this disables unit AI completely. It will no longer move, react or emit radio waves."></h4>
|
||||
<div id="on-off-switch" class="ol-switch" title=""></div>
|
||||
</div>
|
||||
|
||||
<div id="follow-roads" class="switch-control">
|
||||
<div id="follow-roads" class="switch-control yes-no">
|
||||
<h4>Follow roads <img src="/resources/theme/images/icons/circle-question-regular.svg" title=""></h4>
|
||||
<div id="follow-roads-switch" class="ol-switch"></div>
|
||||
</div>
|
||||
@ -107,14 +108,15 @@
|
||||
<hr />
|
||||
|
||||
<div id="advanced-settings-div">
|
||||
<button id="advanced-settings-button" class="ol-button-settings" data-on-click="showAdvancedSettings">Settings</button>
|
||||
<button id="advanced-settings-button" class="ol-button-settings ol-box-shadow" data-on-click="showAdvancedSettings">Settings</button>
|
||||
<div id="delete-options" class="ol-select">
|
||||
<div class="ol-select-value ol-select-warning">
|
||||
Delete unit
|
||||
<img src="/resources/theme/images/icons/chevron-down.svg" inject-svg />
|
||||
</div>
|
||||
<div class="ol-select-options">
|
||||
<div><button class="ol-button-white" data-on-click="deleteSelectedUnits" title="Immediately remove the unit from the simulation"><img src="/resources/theme/images/icons/trash-can-regular.svg" inject-svg>Delete</button></div>
|
||||
<div><hr></div>
|
||||
<div class="hr"><hr></div>
|
||||
<div><button class="ol-button-warning" data-on-click="explodeSelectedUnits" data-on-click-params='{ "type": "normal" }' title="Normal explosion"><img src="/resources/theme/images/icons/explosion-solid.svg" inject-svg>Blow up</button></div>
|
||||
<div><button class="ol-button-warning" data-on-click="explodeSelectedUnits" data-on-click-params='{ "type": "secondary" }' title="The unit will keep exploding at random intervals, simulating ammunition cooking"><img src="/resources/theme/images/icons/burst-solid.svg" inject-svg>Cook off</button></div>
|
||||
<div><button class="ol-button-warning" data-on-click="explodeSelectedUnits" data-on-click-params='{ "type": "phosphorous" }' title="White phosphorous explosion"><img src="/resources/theme/images/icons/smog-solid.svg" inject-svg>Phosp.</button></div>
|
||||
|
||||
@ -6,10 +6,10 @@
|
||||
<div class="ol-select-options">
|
||||
<div id="toolbar-summary">
|
||||
<h3>DCS Olympus</h3>
|
||||
<div class="accent-green app-version-number">version v0.4.6-alpha</div>
|
||||
<div class="accent-green app-version-number">version v0.4.8-alpha</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://www.discord.com" target="_blank">Discord</a>
|
||||
<a href="https://discord.gg/wWXyVVBZT7" target="_blank">Discord</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://github.com/Pax1601/DCSOlympus" target="_blank">Github</a>
|
||||
@ -28,7 +28,7 @@
|
||||
|
||||
<div class="ol-group">
|
||||
<div id="map-type" class="ol-select">
|
||||
<div class="ol-select-value"><img src="resources/theme/images/icons/map-source.svg" inject-svg>ArcGIS Satellite</div>
|
||||
<div class="ol-select-value"><img src="resources/theme/images/icons/map-source.svg" inject-svg><span class="ol-select-value-text">ArcGIS Satellite</span></div>
|
||||
<div class="ol-select-options">
|
||||
<!-- Here the available map sources will be listed-->
|
||||
</div>
|
||||
|
||||
BIN
img/configurator_logo.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
img/olympus_configurator.ico
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
img/olympus_server.ico
Normal file
|
After Width: | Height: | Size: 16 KiB |
@ -1,5 +1,6 @@
|
||||
#define nwjsFolder "C:\Users\dpass\Documents\nwjs\"
|
||||
#define version "v0.4.6-alpha"
|
||||
#define nwjsFolder "..\..\nwjs\"
|
||||
#define nodejsFolder "..\..\node\"
|
||||
#define version "v0.4.8-alpha"
|
||||
|
||||
[Setup]
|
||||
AppName=DCS Olympus
|
||||
@ -11,6 +12,7 @@ UninstallFilesDir={app}\Mods\Services\Olympus
|
||||
SetupIconFile="..\img\olympus.ico"
|
||||
DirExistsWarning=no
|
||||
AppendDefaultDirName=no
|
||||
LicenseFile="..\LEGAL"
|
||||
|
||||
[Messages]
|
||||
WizardSelectDir=Select the location of DCS's Saved Games folder
|
||||
@ -39,7 +41,28 @@ Source: "..\client\routes\*"; DestDir: "{app}\Mods\Services\Olympus\client\route
|
||||
Source: "..\client\views\*"; DestDir: "{app}\Mods\Services\Olympus\client\views"; Flags: ignoreversion recursesubdirs;
|
||||
Source: "..\client\*.*"; DestDir: "{app}\Mods\Services\Olympus\client"; Flags: ignoreversion;
|
||||
Source: "..\img\olympus.ico"; DestDir: "{app}\Mods\Services\Olympus\img"; Flags: ignoreversion;
|
||||
Source: "{#nwjsFolder}\*.*"; DestDir: "{app}\Mods\Services\Olympus\client"; Flags: ignoreversion recursesubdirs;
|
||||
Source: "..\img\olympus_server.ico"; DestDir: "{app}\Mods\Services\Olympus\img"; Flags: ignoreversion;
|
||||
Source: "..\img\olympus_configurator.ico"; DestDir: "{app}\Mods\Services\Olympus\img"; Flags: ignoreversion;
|
||||
Source: "..\img\configurator_logo.png"; DestDir: "{app}\Mods\Services\Olympus\img"; Flags: ignoreversion;
|
||||
Source: "{#nwjsFolder}\*.*"; DestDir: "{app}\Mods\Services\Olympus\client"; Flags: ignoreversion recursesubdirs; Check: CheckLocalInstall
|
||||
Source: "{#nodejsFolder}\*.*"; DestDir: "{app}\Mods\Services\Olympus\client"; Flags: ignoreversion recursesubdirs; Check: CheckServerInstall
|
||||
Source: "..\scripts\python\configurator\dist\configurator.exe"; DestDir: "{app}\Mods\Services\Olympus"; Flags: ignoreversion;
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\Mods\Services\Olympus\configurator.exe"; Parameters: -a {code:GetAddress} -c {code:GetClientPort} -b {code:GetBackendPort} -p {code:GetPassword} -bp {code:GetBluePassword} -rp {code:GetRedPassword}
|
||||
|
||||
[Registry]
|
||||
Root: HKCU; Subkey: "Environment"; ValueType: string; ValueName: "DCSOLYMPUS_PATH"; ValueData: "{app}\Mods\Services\Olympus"; Flags: preservestringtype
|
||||
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};%DCSOLYMPUS_PATH%\bin"; Check: NeedsAddPath('%DCSOLYMPUS_PATH%\bin');
|
||||
|
||||
[Setup]
|
||||
; Tell Windows Explorer to reload the environment
|
||||
ChangesEnvironment=yes
|
||||
|
||||
[Icons]
|
||||
Name: "{userdesktop}\DCS Olympus Client"; Filename: "{app}\Mods\Services\Olympus\client\nw.exe"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus.ico"; Check: CheckLocalInstall
|
||||
Name: "{userdesktop}\DCS Olympus Server"; Filename: "{app}\Mods\Services\Olympus\client\node.exe"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus_server.ico"; Parameters: ".\bin\www"; Check: CheckServerInstall
|
||||
Name: "{userdesktop}\DCS Olympus Configurator"; Filename: "{app}\Mods\Services\Olympus\configurator.exe"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus_configurator.ico"; Check: CheckServerInstall
|
||||
|
||||
[Code]
|
||||
function NeedsAddPath(Param: string): boolean;
|
||||
@ -58,13 +81,412 @@ begin
|
||||
Result := Pos(';' + Param + ';', ';' + OrigPath + ';') = 0;
|
||||
end;
|
||||
|
||||
[Registry]
|
||||
Root: HKCU; Subkey: "Environment"; ValueType: string; ValueName: "DCSOLYMPUS_PATH"; ValueData: "{app}\Mods\Services\Olympus"; Flags: preservestringtype
|
||||
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};%DCSOLYMPUS_PATH%\bin"; Check: NeedsAddPath('%DCSOLYMPUS_PATH%\bin');
|
||||
|
||||
[Setup]
|
||||
; Tell Windows Explorer to reload the environment
|
||||
ChangesEnvironment=yes
|
||||
[Code]
|
||||
var
|
||||
lblLocalInstall: TLabel;
|
||||
lblLocalInstallInstructions: TNewStaticText;
|
||||
lblServerInstall: TLabel;
|
||||
lblServerInstallInstructions: TNewStaticText;
|
||||
lblClientPort: TLabel;
|
||||
lblBackendPort: TLabel;
|
||||
lblPassword: TLabel;
|
||||
lblBluePassword: TLabel;
|
||||
lblRedPassword: TLabel;
|
||||
txtLocalInstall: TNewRadioButton;
|
||||
txtServerInstall: TNewRadioButton;
|
||||
txtClientPort: TEdit;
|
||||
txtBackendPort: TEdit;
|
||||
txtPassword: TPasswordEdit;
|
||||
txtBluePassword: TPasswordEdit;
|
||||
txtRedPassword: TPasswordEdit;
|
||||
AddressPage: Integer;
|
||||
PasswordPage: Integer;
|
||||
lblPasswordInstructions: TNewStaticText;
|
||||
|
||||
procedure AcceptNumbersOnlyKeyPress(Sender: TObject; var Key: Char);
|
||||
var
|
||||
KeyCode: Integer;
|
||||
begin
|
||||
// allow only numbers
|
||||
KeyCode := Ord(Key);
|
||||
if not ((KeyCode = 8) or ((KeyCode >= 48) and (KeyCode <= 57))) then
|
||||
Key := #0;
|
||||
end;
|
||||
|
||||
procedure frmAddress_Activate(Page: TWizardPage);
|
||||
begin
|
||||
end;
|
||||
|
||||
function frmAddress_ShouldSkipPage(Page: TWizardPage): Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
function frmAddress_BackButtonClick(Page: TWizardPage): Boolean;
|
||||
begin
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function frmAddress_NextButtonClick(Page: TWizardPage): Boolean;
|
||||
begin
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
procedure frmAddress_CancelButtonClick(Page: TWizardPage; var Cancel, Confirm: Boolean);
|
||||
begin
|
||||
end;
|
||||
|
||||
function frmAddress_CreatePage(PreviousPageId: Integer): Integer;
|
||||
var
|
||||
Page: TWizardPage;
|
||||
begin
|
||||
Page := CreateCustomPage(
|
||||
PreviousPageId,
|
||||
'DCS Olympus configuration',
|
||||
'Setup DCS Olympus connectivity'
|
||||
);
|
||||
|
||||
{ lblLocalInstall }
|
||||
lblLocalInstall := TLabel.Create(Page);
|
||||
with lblLocalInstall do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(30);
|
||||
Top := ScaleY(14);
|
||||
Width := ScaleX(35);
|
||||
Height := ScaleY(10);
|
||||
Font.Style := [fsBold];
|
||||
Caption := 'Local installation';
|
||||
end;
|
||||
|
||||
{ lblLocalInstallInstructions }
|
||||
lblLocalInstallInstructions := TNewStaticText.Create(Page);
|
||||
with lblLocalInstallInstructions do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(30);
|
||||
Top := ScaleY(31);
|
||||
Width := ScaleX(340);
|
||||
Height := ScaleY(23);
|
||||
WordWrap := True;
|
||||
Caption := 'Select this to install DCS Olympus locally. DCS Olympus will not be reachable by external clients (i.e. browsers running on different PCs)';
|
||||
end;
|
||||
|
||||
{ txtLocalInstall }
|
||||
txtLocalInstall := TNewRadioButton.Create(Page);
|
||||
with txtLocalInstall do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(10);
|
||||
Top := ScaleY(12);
|
||||
Width := ScaleX(185);
|
||||
Height := ScaleY(21);
|
||||
TabOrder := 0;
|
||||
Checked := True
|
||||
end;
|
||||
|
||||
{ lblServerInstall }
|
||||
lblServerInstall := TLabel.Create(Page);
|
||||
with lblServerInstall do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(30);
|
||||
Top := ScaleY(76);
|
||||
Width := ScaleX(52);
|
||||
Height := ScaleY(13);
|
||||
Font.Style := [fsBold];
|
||||
Caption := 'Dedicated server installation';
|
||||
end;
|
||||
|
||||
{ lblServerInstallInstructions }
|
||||
lblServerInstallInstructions := TNewStaticText.Create(Page);
|
||||
with lblServerInstallInstructions do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(30);
|
||||
Top := ScaleY(93);
|
||||
Width := ScaleX(340);
|
||||
Height := ScaleY(13);
|
||||
WordWrap := True;
|
||||
Caption := 'Select this to install DCS Olympus on a dedicated server. DCS Olympus will be reachable by external clients. NOTE: to enable external connections, TCP port forwarding must be enabled on the selected ports.';
|
||||
end;
|
||||
|
||||
{ txtServerInstall }
|
||||
txtServerInstall := TNewRadioButton.Create(Page);
|
||||
with txtServerInstall do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(10);
|
||||
Top := ScaleY(72);
|
||||
Width := ScaleX(185);
|
||||
Height := ScaleY(21);
|
||||
TabOrder := 1;
|
||||
end;
|
||||
|
||||
{ lblClientPort }
|
||||
lblClientPort := TLabel.Create(Page);
|
||||
with lblClientPort do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(24);
|
||||
Top := ScaleY(168);
|
||||
Width := ScaleX(46);
|
||||
Height := ScaleY(13);
|
||||
Caption := 'Webserver port';
|
||||
end;
|
||||
|
||||
{ txtClientPort }
|
||||
txtClientPort := TEdit.Create(Page);
|
||||
with txtClientPort do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(180);
|
||||
Top := ScaleY(165);
|
||||
Width := ScaleX(185);
|
||||
Height := ScaleY(21);
|
||||
Text := '3000';
|
||||
OnKeyPress := @AcceptNumbersOnlyKeyPress;
|
||||
TabOrder := 3;
|
||||
end;
|
||||
|
||||
{ lblBackendPort }
|
||||
lblBackendPort := TLabel.Create(Page);
|
||||
with lblBackendPort do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(24);
|
||||
Top := ScaleY(198);
|
||||
Width := ScaleX(46);
|
||||
Height := ScaleY(13);
|
||||
Caption := 'Backend port';
|
||||
end;
|
||||
|
||||
{ txtBackendPort }
|
||||
txtBackendPort := TEdit.Create(Page);
|
||||
with txtBackendPort do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(180);
|
||||
Top := ScaleY(195);
|
||||
Width := ScaleX(185);
|
||||
Height := ScaleY(21);
|
||||
Text := '3001';
|
||||
OnKeyPress := @AcceptNumbersOnlyKeyPress;
|
||||
TabOrder := 4;
|
||||
end;
|
||||
|
||||
with Page do
|
||||
begin
|
||||
OnActivate := @frmAddress_Activate;
|
||||
OnShouldSkipPage := @frmAddress_ShouldSkipPage;
|
||||
OnBackButtonClick := @frmAddress_BackButtonClick;
|
||||
OnNextButtonClick := @frmAddress_NextButtonClick;
|
||||
OnCancelButtonClick := @frmAddress_CancelButtonClick;
|
||||
end;
|
||||
|
||||
Result := Page.ID;
|
||||
end;
|
||||
|
||||
procedure frmPassword_Activate(Page: TWizardPage);
|
||||
begin
|
||||
end;
|
||||
|
||||
function frmPassword_ShouldSkipPage(Page: TWizardPage): Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
function frmPassword_BackButtonClick(Page: TWizardPage): Boolean;
|
||||
begin
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function frmPassword_NextButtonClick(Page: TWizardPage): Boolean;
|
||||
begin
|
||||
if (Trim(txtPassword.Text) <> '') and (Trim(txtBluePassword.Text) <> '') and (Trim(txtRedPassword.Text) <> '') then begin
|
||||
Result := True;
|
||||
end else
|
||||
begin
|
||||
MsgBox('All password fields must be filled to proceed.', mbInformation, MB_OK);
|
||||
Result := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure frmPassword_CancelButtonClick(Page: TWizardPage; var Cancel, Confirm: Boolean);
|
||||
begin
|
||||
end;
|
||||
|
||||
function frmPassword_CreatePage(PreviousPageId: Integer): Integer;
|
||||
var
|
||||
Page: TWizardPage;
|
||||
begin
|
||||
Page := CreateCustomPage(
|
||||
PreviousPageId,
|
||||
'DCS Olympus passwords',
|
||||
'Set DCS Olympus Admin and Commander passwords'
|
||||
);
|
||||
|
||||
{ lblPassword }
|
||||
lblPassword := TLabel.Create(Page);
|
||||
with lblPassword do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(24);
|
||||
Top := ScaleY(28);
|
||||
Width := ScaleX(46);
|
||||
Height := ScaleY(13);
|
||||
Caption := 'Game Master password';
|
||||
end;
|
||||
|
||||
{ txtPassword }
|
||||
txtPassword := TPasswordEdit.Create(Page);
|
||||
with txtPassword do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(180);
|
||||
Top := ScaleY(25);
|
||||
Width := ScaleX(185);
|
||||
Height := ScaleY(21);
|
||||
TabOrder := 2;
|
||||
end;
|
||||
|
||||
{ lblBluePassword }
|
||||
lblBluePassword := TLabel.Create(Page);
|
||||
with lblBluePassword do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(24);
|
||||
Top := ScaleY(58);
|
||||
Width := ScaleX(46);
|
||||
Height := ScaleY(13);
|
||||
Caption := 'Blue Commander password';
|
||||
end;
|
||||
|
||||
{ txtBluePassword }
|
||||
txtBluePassword := TPasswordEdit.Create(Page);
|
||||
with txtBluePassword do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(180);
|
||||
Top := ScaleY(55);
|
||||
Width := ScaleX(185);
|
||||
Height := ScaleY(21);
|
||||
TabOrder := 2;
|
||||
end;
|
||||
|
||||
{ lblRedPassword }
|
||||
lblRedPassword := TLabel.Create(Page);
|
||||
with lblRedPassword do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(24);
|
||||
Top := ScaleY(88);
|
||||
Width := ScaleX(46);
|
||||
Height := ScaleY(13);
|
||||
Caption := 'Red Commander password';
|
||||
end;
|
||||
|
||||
{ txtRedPassword }
|
||||
txtRedPassword := TPasswordEdit.Create(Page);
|
||||
with txtRedPassword do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(180);
|
||||
Top := ScaleY(85);
|
||||
Width := ScaleX(185);
|
||||
Height := ScaleY(21);
|
||||
TabOrder := 2;
|
||||
end;
|
||||
|
||||
|
||||
{ lblPasswordInstructions }
|
||||
lblPasswordInstructions := TNewStaticText.Create(Page);
|
||||
with lblPasswordInstructions do
|
||||
begin
|
||||
Parent := Page.Surface;
|
||||
Left := ScaleX(24);
|
||||
Top := ScaleY(120);
|
||||
Width := ScaleX(340);
|
||||
Height := ScaleY(13);
|
||||
WordWrap := True;
|
||||
Caption := 'Passwords can be changed in the future by editing the file "olympus.json". For more information, see the DCS Olympus Wiki';
|
||||
end;
|
||||
|
||||
with Page do
|
||||
begin
|
||||
OnActivate := @frmPassword_Activate;
|
||||
OnShouldSkipPage := @frmPassword_ShouldSkipPage;
|
||||
OnBackButtonClick := @frmPassword_BackButtonClick;
|
||||
OnNextButtonClick := @frmPassword_NextButtonClick;
|
||||
OnCancelButtonClick := @frmPassword_CancelButtonClick;
|
||||
end;
|
||||
|
||||
Result := Page.ID;
|
||||
end;
|
||||
|
||||
procedure InitializeWizard();
|
||||
begin
|
||||
{this page will come after welcome page}
|
||||
AddressPage := frmAddress_CreatePage(wpSelectDir);
|
||||
PasswordPage:= frmPassword_CreatePage(AddressPage);
|
||||
end;
|
||||
|
||||
function CheckLocalInstall(): boolean;
|
||||
begin
|
||||
if txtLocalInstall.Checked then begin
|
||||
Result := True
|
||||
end else
|
||||
begin
|
||||
Result := False
|
||||
end
|
||||
end;
|
||||
|
||||
function CheckServerInstall(): boolean;
|
||||
begin
|
||||
if txtLocalInstall.Checked then begin
|
||||
Result := False
|
||||
end else
|
||||
begin
|
||||
Result := True
|
||||
end
|
||||
end;
|
||||
|
||||
function GetAddress(Value: string): string;
|
||||
begin
|
||||
if txtLocalInstall.Checked then begin
|
||||
Result := 'localhost'
|
||||
end else
|
||||
begin
|
||||
Result := '*'
|
||||
end
|
||||
end;
|
||||
|
||||
function GetClientPort(Value: string): string;
|
||||
begin
|
||||
Result := txtClientPort.Text;
|
||||
end;
|
||||
|
||||
function GetBackendPort(Value: string): string;
|
||||
begin
|
||||
Result := txtBackendPort.Text;
|
||||
end;
|
||||
|
||||
function GetPassword(Value: string): string;
|
||||
begin
|
||||
Result := txtPassword.Text;
|
||||
end;
|
||||
|
||||
function GetBluePassword(Value: string): string;
|
||||
begin
|
||||
Result := txtBluePassword.Text;
|
||||
end;
|
||||
|
||||
function GetRedPassword(Value: string): string;
|
||||
begin
|
||||
Result := txtRedPassword.Text;
|
||||
end;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[Icons]
|
||||
Name: "{userdesktop}\DCS Olympus Client"; Filename: "{app}\Mods\Services\Olympus\client\nw.exe"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus.ico"
|
||||
|
||||
@ -15,7 +15,7 @@ declare_plugin(self_ID,
|
||||
shortName = "Olympus",
|
||||
fileMenuName = "Olympus",
|
||||
|
||||
version = "v0.4.6-alpha",
|
||||
version = "v0.4.8-alpha",
|
||||
state = "installed",
|
||||
developerName= "DCS Refugees 767 squadron",
|
||||
info = _("DCS Olympus is a mod for DCS World. It allows users to spawn, control, task, group, and remove units from a DCS World server using a real-time map interface, similarly to Real Time Strategy games. The user interface also provides useful informations units, like loadouts, fuel, tasking, and so on. In the future, more features for DCS World GCI and JTAC will be available."),
|
||||
|
||||
36
olympus.json
@ -1,19 +1,19 @@
|
||||
{
|
||||
"server": {
|
||||
"address": "localhost",
|
||||
"port": 30000
|
||||
},
|
||||
"authentication": {
|
||||
"gameMasterPassword": "password",
|
||||
"blueCommanderPassword": "bluepassword",
|
||||
"redCommanderPassword": "redpassword"
|
||||
},
|
||||
"client": {
|
||||
"port": 3000,
|
||||
"elevationProvider": {
|
||||
"provider": "https://srtm.fasma.org/{lat}{lng}.SRTMGL3S.hgt.zip",
|
||||
"username": null,
|
||||
"password": null
|
||||
}
|
||||
}
|
||||
}
|
||||
"server": {
|
||||
"address": "localhost",
|
||||
"port": 3001
|
||||
},
|
||||
"authentication": {
|
||||
"gameMasterPassword": "4b8823ed9e5c2392ab4a791913bb8ce41956ea32e308b760eefb97536746dd33",
|
||||
"blueCommanderPassword": "302bcbaf2a3fdcf175b689bf102d6cdf9328f68a13d4096101bba806482bfed9",
|
||||
"redCommanderPassword": "b0ea4230c1558c5313165eda1bdb7fced008ca7f2ca6b823fb4d26292f309098"
|
||||
},
|
||||
"client": {
|
||||
"port": 3000,
|
||||
"elevationProvider": {
|
||||
"provider": "https://srtm.fasma.org/{lat}{lng}.SRTMGL3S.hgt.zip",
|
||||
"username": null,
|
||||
"password": null
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
local version = "v0.4.6-alpha"
|
||||
local version = "v0.4.8-alpha"
|
||||
|
||||
local debug = false -- True enables debug printing using DCS messages
|
||||
|
||||
@ -180,7 +180,7 @@ function Olympus.buildTask(groupName, options)
|
||||
if options ['altitudeType'] then
|
||||
if options ['altitudeType'] == "AGL" then
|
||||
local groundHeight = 0
|
||||
if group then
|
||||
if group ~= nil then
|
||||
local groupPos = mist.getLeadPos(group)
|
||||
groundHeight = land.getHeight({x = groupPos.x, y = groupPos.z})
|
||||
end
|
||||
@ -287,7 +287,7 @@ end
|
||||
function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedType, category, taskOptions)
|
||||
Olympus.debug("Olympus.move " .. groupName .. " (" .. lat .. ", " .. lng ..") " .. altitude .. "m " .. altitudeType .. " ".. speed .. "m/s " .. category .. " " .. Olympus.serializeTable(taskOptions), 2)
|
||||
local group = Group.getByName(groupName)
|
||||
if group then
|
||||
if group ~= nil then
|
||||
if category == "Aircraft" then
|
||||
local startPoint = mist.getLeadPos(group)
|
||||
local endPoint = coord.LLtoLO(lat, lng, 0)
|
||||
@ -765,9 +765,9 @@ end
|
||||
|
||||
-- Find a database entry by ID
|
||||
function Olympus.findInDatabase(ID)
|
||||
for idx, unit in pairs(Olympus.cloneDatabase) do
|
||||
if unit ~= nil and unit["ID"] == ID then
|
||||
return unit
|
||||
for idx, unitRecord in pairs(Olympus.cloneDatabase) do
|
||||
if unitRecord ~= nil and unitRecord["ID"] == ID then
|
||||
return unitRecord
|
||||
end
|
||||
end
|
||||
return nil
|
||||
@ -789,11 +789,11 @@ function Olympus.clone(cloneTable, deleteOriginal)
|
||||
-- All the units in the table will be cloned in a single group
|
||||
for idx, cloneData in pairs(cloneTable) do
|
||||
local ID = cloneData.ID
|
||||
local unit = Olympus.findInDatabase(ID)
|
||||
local unitRecord = Olympus.findInDatabase(ID)
|
||||
|
||||
if unit ~= nil then
|
||||
if unitRecord ~= nil then
|
||||
-- Update the data of the cloned unit
|
||||
local unitTable = mist.utils.deepCopy(unit)
|
||||
local unitTable = mist.utils.deepCopy(unitRecord)
|
||||
|
||||
local point = coord.LLtoLO(cloneData['lat'], cloneData['lng'], 0)
|
||||
if unitTable then
|
||||
@ -803,8 +803,8 @@ function Olympus.clone(cloneTable, deleteOriginal)
|
||||
end
|
||||
|
||||
if countryID == nil and category == nil then
|
||||
countryID = unit["country"]
|
||||
if unit["category"] == Unit.Category.AIRPLANE then
|
||||
countryID = unitRecord["country"]
|
||||
if unitRecord["category"] == Unit.Category.AIRPLANE then
|
||||
category = 'plane'
|
||||
route = {
|
||||
["points"] =
|
||||
@ -823,7 +823,7 @@ function Olympus.clone(cloneTable, deleteOriginal)
|
||||
},
|
||||
},
|
||||
}
|
||||
elseif unit["category"] == Unit.Category.HELICOPTER then
|
||||
elseif unitRecord["category"] == Unit.Category.HELICOPTER then
|
||||
category = 'helicopter'
|
||||
route = {
|
||||
["points"] =
|
||||
@ -842,9 +842,9 @@ function Olympus.clone(cloneTable, deleteOriginal)
|
||||
},
|
||||
},
|
||||
}
|
||||
elseif unit["category"] == Unit.Category.GROUND_UNIT then
|
||||
elseif unitRecord["category"] == Unit.Category.GROUND_UNIT then
|
||||
category = 'vehicle'
|
||||
elseif unit["category"] == Unit.Category.SHIP then
|
||||
elseif unitRecord["category"] == Unit.Category.SHIP then
|
||||
category = 'ship'
|
||||
end
|
||||
end
|
||||
@ -884,7 +884,7 @@ end
|
||||
function Olympus.delete(ID, explosion, explosionType)
|
||||
Olympus.debug("Olympus.delete " .. ID .. " " .. tostring(explosion), 2)
|
||||
local unit = Olympus.getUnitByID(ID)
|
||||
if unit then
|
||||
if unit ~= nil and unit:isExist() then
|
||||
if unit:getPlayerName() or explosion then
|
||||
if explosionType == nil then
|
||||
explosionType = "normal"
|
||||
@ -903,7 +903,7 @@ end
|
||||
function Olympus.setTask(groupName, taskOptions)
|
||||
Olympus.debug("Olympus.setTask " .. groupName .. " " .. Olympus.serializeTable(taskOptions), 2)
|
||||
local group = Group.getByName(groupName)
|
||||
if group then
|
||||
if group ~= nil then
|
||||
local task = Olympus.buildTask(groupName, taskOptions);
|
||||
Olympus.debug("Olympus.setTask " .. Olympus.serializeTable(task), 20)
|
||||
if task then
|
||||
@ -917,7 +917,7 @@ end
|
||||
function Olympus.resetTask(groupName)
|
||||
Olympus.debug("Olympus.resetTask " .. groupName, 2)
|
||||
local group = Group.getByName(groupName)
|
||||
if group then
|
||||
if group ~= nil then
|
||||
group:getController():resetTask()
|
||||
Olympus.debug("Olympus.resetTask completed successfully", 2)
|
||||
end
|
||||
@ -927,7 +927,7 @@ end
|
||||
function Olympus.setCommand(groupName, command)
|
||||
Olympus.debug("Olympus.setCommand " .. groupName .. " " .. Olympus.serializeTable(command), 2)
|
||||
local group = Group.getByName(groupName)
|
||||
if group then
|
||||
if group ~= nil then
|
||||
group:getController():setCommand(command)
|
||||
Olympus.debug("Olympus.setCommand completed successfully", 2)
|
||||
end
|
||||
@ -937,7 +937,7 @@ end
|
||||
function Olympus.setOption(groupName, optionID, optionValue)
|
||||
Olympus.debug("Olympus.setOption " .. groupName .. " " .. optionID .. " " .. tostring(optionValue), 2)
|
||||
local group = Group.getByName(groupName)
|
||||
if group then
|
||||
if group ~= nil then
|
||||
group:getController():setOption(optionID, optionValue)
|
||||
Olympus.debug("Olympus.setOption completed successfully", 2)
|
||||
end
|
||||
@ -947,7 +947,7 @@ end
|
||||
function Olympus.setOnOff(groupName, onOff)
|
||||
Olympus.debug("Olympus.setOnOff " .. groupName .. " " .. tostring(onOff), 2)
|
||||
local group = Group.getByName(groupName)
|
||||
if group then
|
||||
if group ~= nil then
|
||||
group:getController():setOnOff(onOff)
|
||||
Olympus.debug("Olympus.setOnOff completed successfully", 2)
|
||||
end
|
||||
@ -965,11 +965,11 @@ function Olympus.setUnitsData(arg, time)
|
||||
index = index + 1
|
||||
-- Only the indexes between startIndex and endIndex are handled. This is a simple way to spread the update load over many cycles
|
||||
if index > startIndex then
|
||||
if unit ~= nil then
|
||||
if unit ~= nil and unit:isExist() then
|
||||
local table = {}
|
||||
|
||||
-- Get the object category in Olympus name
|
||||
local objectCategory = unit:getCategory()
|
||||
local objectCategory = Object.getCategory(unit)
|
||||
if objectCategory == Object.Category.UNIT then
|
||||
if unit:getDesc().category == Unit.Category.AIRPLANE then
|
||||
table["category"] = "Aircraft"
|
||||
@ -1091,11 +1091,11 @@ function Olympus.setWeaponsData(arg, time)
|
||||
|
||||
-- Only the indexes between startIndex and endIndex are handled. This is a simple way to spread the update load over many cycles
|
||||
if index > startIndex then
|
||||
if weapon ~= nil then
|
||||
if weapon ~= nil and weapon:isExist() then
|
||||
local table = {}
|
||||
|
||||
-- Get the object category in Olympus name
|
||||
local objectCategory = weapon:getCategory()
|
||||
local objectCategory = Object.getCategory(weapon)
|
||||
if objectCategory == Object.Category.WEAPON then
|
||||
if weapon:getDesc().category == Weapon.Category.MISSILE then
|
||||
table["category"] = "Missile"
|
||||
@ -1216,7 +1216,7 @@ function Olympus.initializeUnits()
|
||||
if mist and mist.DBs and mist.DBs.MEunitsById then
|
||||
for id, unitsTable in pairs(mist.DBs.MEunitsById) do
|
||||
local unit = Unit.getByName(unitsTable["unitName"])
|
||||
if unit then
|
||||
if unit ~= nil and unit:isExist() then
|
||||
Olympus.units[unit["id_"]] = unit
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
local version = 'v0.4.6-alpha'
|
||||
local version = 'v0.4.8-alpha'
|
||||
|
||||
Olympus = {}
|
||||
Olympus.OlympusDLL = nil
|
||||
|
||||
3
scripts/python/.vscode/launch.json
vendored
@ -10,8 +10,7 @@
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
"args": ["groundunit"]
|
||||
"justMyCode": true
|
||||
}
|
||||
]
|
||||
}
|
||||
5
scripts/python/configurator/build_configurator.bat
Normal file
@ -0,0 +1,5 @@
|
||||
python -m venv venv
|
||||
call ./venv/Scripts/activate
|
||||
pip install pyinstaller
|
||||
pip install PySimpleGUI
|
||||
python -m PyInstaller configurator.py --onefile --noconsole
|
||||
136
scripts/python/configurator/configurator.py
Normal file
@ -0,0 +1,136 @@
|
||||
import argparse, json
|
||||
import PySimpleGUI as sg
|
||||
import hashlib
|
||||
|
||||
# Apply the values to the olympus.json config file
|
||||
def apply_values(args):
|
||||
with open("olympus.json", "r") as fp:
|
||||
config = json.load(fp)
|
||||
|
||||
if args.address is not None and args.address != "":
|
||||
config["server"]["address"] = args.address
|
||||
print(f"Address set to {args.address}")
|
||||
else:
|
||||
print("No address provided, skipping...")
|
||||
|
||||
if args.backendPort is not None and args.backendPort != "":
|
||||
# The backend port must be a numerical value
|
||||
if args.backendPort.isdecimal():
|
||||
config["server"]["port"] = int(args.backendPort)
|
||||
print(f"Backend port set to {args.backendPort}")
|
||||
else:
|
||||
print(f"Invalid backend port provided: {args.backendPort}")
|
||||
else:
|
||||
print("No backend port provided, skipping...")
|
||||
|
||||
if args.clientPort is not None and args.clientPort != "":
|
||||
# The client port must be a numerical value
|
||||
if args.clientPort.isdecimal():
|
||||
config["client"]["port"] = int(args.clientPort)
|
||||
print(f"Client port set to {args.clientPort}")
|
||||
else:
|
||||
print(f"Invalid client port provided: {args.clientPort}")
|
||||
else:
|
||||
print("No client port provided, skipping...")
|
||||
|
||||
if args.password is not None and args.password != "":
|
||||
config["authentication"]["gameMasterPassword"] = hashlib.sha256(args.password.encode()).hexdigest()
|
||||
print(f"Game Master password set to {args.password}")
|
||||
else:
|
||||
print("No Game Master password provided, skipping...")
|
||||
|
||||
if args.bluePassword is not None and args.bluePassword != "":
|
||||
config["authentication"]["blueCommanderPassword"] = hashlib.sha256(args.bluePassword.encode()).hexdigest()
|
||||
print(f"Blue Commander password set to {args.bluePassword}")
|
||||
else:
|
||||
print("No Blue Commander password provided, skipping...")
|
||||
|
||||
if args.redPassword is not None and args.redPassword != "":
|
||||
config["authentication"]["redCommanderPassword"] = hashlib.sha256(args.redPassword.encode()).hexdigest()
|
||||
print(f"Red Commander password set to {args.redPassword}")
|
||||
else:
|
||||
print("No Red Commander password provided, skipping...")
|
||||
|
||||
with open("olympus.json", "w") as fp:
|
||||
json.dump(config, fp, indent = 4)
|
||||
|
||||
def main():
|
||||
# Parse the input arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="DCS Olympus configurator",
|
||||
description="This software allows to edit the DCS Olympus configuration file",
|
||||
epilog="")
|
||||
|
||||
parser.add_argument("-a", "--address")
|
||||
parser.add_argument("-c", "--clientPort")
|
||||
parser.add_argument("-b", "--backendPort")
|
||||
parser.add_argument("-p", "--password")
|
||||
parser.add_argument("-bp", "--bluePassword")
|
||||
parser.add_argument("-rp", "--redPassword")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# If no argument was provided, start the GUI
|
||||
if args.address is None and \
|
||||
args.backendPort is None and \
|
||||
args.clientPort is None and \
|
||||
args.password is None and \
|
||||
args.bluePassword is None and \
|
||||
args.redPassword is None:
|
||||
print(f"No arguments provided, starting in graphical mode")
|
||||
|
||||
with open("olympus.json", "r") as fp:
|
||||
config = json.load(fp)
|
||||
|
||||
old_values = {}
|
||||
window = sg.Window("DCS Olympus configurator",
|
||||
[[sg.T("DCS Olympus configurator", font=("Helvetica", 14, "bold")), sg.Push(), sg.Image(".\\img\\configurator_logo.png", size = (50, 50))],
|
||||
[sg.T("")],
|
||||
[sg.T("Address"), sg.Push(), sg.In(size=(30, 10), default_text=config["server"]["address"], key="address")],
|
||||
[sg.T("Webserver port"), sg.Push(), sg.In(size=(30, 10), default_text=config["client"]["port"], key="clientPort", enable_events=True)],
|
||||
[sg.T("Backend port"), sg.Push(), sg.In(size=(30, 10), default_text=config["server"]["port"], key="backendPort", enable_events=True)],
|
||||
[sg.T("Game Master password"), sg.Push(), sg.In(size=(30, 10), password_char="*", key="password")],
|
||||
[sg.T("Blue Commander password"), sg.Push(), sg.In(size=(30, 10), password_char="*", key="bluePassword")],
|
||||
[sg.T("Red Commander password"), sg.Push(), sg.In(size=(30, 10), password_char="*", key="redPassword")],
|
||||
[sg.T("Note: Empty fields will retain their original values")],
|
||||
[sg.T("")],
|
||||
[sg.B("Apply"), sg.B("Exit"), sg.T("Remember to restart DCS Server and any running mission", font=("Helvetica", 10, "bold"))]],
|
||||
icon = ".\\img\\olympus_configurator.ico")
|
||||
|
||||
while True:
|
||||
event, values = window.read()
|
||||
|
||||
# The backend and client ports must be numerical. Enforce it.
|
||||
if event == "backendPort":
|
||||
if values["backendPort"].isdecimal() or values["backendPort"] == "":
|
||||
old_values["backendPort"] = values["backendPort"]
|
||||
else:
|
||||
window["backendPort"].update(old_values["backendPort"] if "backendPort" in old_values else config["server"]["port"])
|
||||
|
||||
if event == "clientPort":
|
||||
if values["clientPort"].isdecimal() or values["clientPort"] == "":
|
||||
old_values["clientPort"] = values["clientPort"]
|
||||
else:
|
||||
window["clientPort"].update(old_values["clientPort"] if "clientPort" in old_values else config["client"]["port"])
|
||||
|
||||
# Update the config file
|
||||
if event == "Apply":
|
||||
args.address = values["address"]
|
||||
args.backendPort = values["backendPort"]
|
||||
args.clientPort = values["clientPort"]
|
||||
args.password = values["password"]
|
||||
args.bluePassword = values["bluePassword"]
|
||||
args.redPassword = values["redPassword"]
|
||||
apply_values(args)
|
||||
sg.Popup("DCS Olympus configuration updated")
|
||||
|
||||
if event == sg.WIN_CLOSED or event == "Exit":
|
||||
window.close()
|
||||
break
|
||||
|
||||
# If any argument was provided, run in headless mode
|
||||
else:
|
||||
apply_values(args)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
37
scripts/python/configurator/configurator.spec
Normal file
@ -0,0 +1,37 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['configurator.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='configurator',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
@ -1,424 +0,0 @@
|
||||
Name,Enabled,Database,Type,Label,Short label,Coalition,Era,Can target point,Can rearm,Acquisition range [m],Engagement range [m],Description,Abilities (comma separate values),Notes,ChatGPT description
|
||||
A-10C_2,yes,Aircraft,Aircraft,A-10C Warthog,10,blue,Late Cold War,yes,no,,,"2 jet engine, straight wing, 1 crew, attack aircraft. Warthog",Boom AAR,"Fox 2, gun, Dumb bombs, smart bombs, rockets, ATGMs, can AAR, subsonic,",
|
||||
A-20G,yes,Aircraft,Aircraft,A-20G Havoc,A20,blue,WW2,yes,no,,,"2 propeller, straight wing, 3 crew, medium attack bomber. Havoc",,"Dumb bomb, gun, subsonic,",
|
||||
A-50,yes,Aircraft,Aircraft,A-50 Mainstay,A50,red,Late Cold War,no,no,,,"4 jet engine, swept wing, 15 crew. NATO reporting name: Mainstay",Airbourne early warning,"Airbourne early warning, subsonic,",
|
||||
AJS37,yes,Aircraft,Aircraft,AJS37 Viggen,37,blue,Mid Cold War,yes,no,,,"Single jet engine, delta wing, 1 crew, attack aircraft. Viggen",,"Fox 2, dumb bombs, rockets and ATGMs, antiship, supersonic",
|
||||
An-26B,yes,Aircraft,Aircraft,An-26B Curl,26,red,Mid Cold War,no,no,,,"2 turboprop, straight wing, 5 crew, cargo and passenger aircraft. NATO reporting name: Curl",,"Subsonic,",
|
||||
An-30M,yes,Aircraft,Aircraft,An-30M Clank,30,red,Mid Cold War,no,no,,,"2 turboprop, straight wing, 7 crew, weather reseach aircraft. NATO reporting name: Clank",,"Subsonic,",
|
||||
AV8BNA,yes,Aircraft,Aircraft,AV8BNA Harrier,8,blue,Late Cold War,yes,no,,,"Single jet engine, swept wing, 1 crew, all weather, VTOL attack aircraft. Harrier",Drogue AAR,"Fox 2 and gunpod, Dumb and smart bombs, ATGMs, SEAD, can AAR, transonic,",
|
||||
B-1B,yes,Aircraft,Aircraft,B-1B Lancer,1,blue,Late Cold War,yes,no,,,"4 jet engine, swing wing, 2 crew bomber. Lancer",,"dumb and smart bombs, supersonic,",
|
||||
B-52H,yes,Aircraft,Aircraft,B-52H Stratofortress,52,blue,Early Cold War,yes,no,,,"8 jet engine, swept wing, 6 crew bomber. Stratofortress",,"Dumb and smart bombs, subsonic,",
|
||||
Bf-109K-4,yes,Aircraft,Aircraft,Bf-109K-4 Fritz,109,red,WW2,yes,no,,,"Single propeller, straight wing, 1 crew. 109",,"Guns, Subsonic,",
|
||||
C-101CC,yes,Aircraft,Aircraft,C-101CC,101,blue,Late Cold War,yes,no,,,"Single jet engine, swept wing, 2 crew, light attack trainer. Aviojet",,"Fox 2, Dumb bombs, rockets, gunpod, subsonic,",
|
||||
C-130,yes,Aircraft,Aircraft,C-130 Hercules,130,blue,Early Cold War,no,no,,,"4 turboprop, stright wing, 3 crew. Hercules",,Subsonic,
|
||||
C-17A,yes,Aircraft,Aircraft,C-17A Globemaster,C17,blue,Modern,no,no,,,"4 jet engine, swept wing, 3 crew. Globemaster",,Subsonic,
|
||||
E-2C,yes,Aircraft,Aircraft,E-2C Hawkeye,2C,blue,Mid Cold War,no,no,,,"2 turboprop, straight wing, 5 crew. Hawkeye",Airbourne early warning,Subsonic,
|
||||
E-3A,yes,Aircraft,Aircraft,E-3A Sentry,E3,blue,Mid Cold War,no,no,,,"4 jet engine, swept wing, 17 crew. Sentry",Airbourne early warning,Subsonic,
|
||||
F-117A,yes,Aircraft,Aircraft,F-117A Nighthawk,117,blue,Late Cold War,yes,no,,,"2 jet engine, delta wing, 1 crew. Nighthawk",,"Smart bombs, Subsonic",
|
||||
F-14A-135-GR,yes,Aircraft,Aircraft,F-14A-135-GR Tomcat,14A,blue,Mid Cold War,yes,no,,,"2 Jet engine, swing wing, 2 crew. Tomcat",Drogue AAR,"Fox 1,2,3, Gun, Dumb bombs, laser bombs and rockets, Can AAR, supersonic",
|
||||
F-14B,yes,Aircraft,Aircraft,F-14B Tomcat,14B,blue,Late Cold War,yes,no,,,"2 Jet engine, swing wing, 2 crew. Tomcat",Drogue AAR,"Fox 1,2,3, Gun, Dumb bombs, laser bombs and rockets, Can AAR, supersonic",
|
||||
F-15C,yes,Aircraft,Aircraft,F-15C Eagle,15,blue,Late Cold War,yes,no,,,"2 Jet engine, swept wing, 2 crew, all weather fighter. Eagle.",Boom AAR,"Fox 1,2,3, Gun, Can AAR, supersonic",
|
||||
F-15E,yes,Aircraft,Aircraft,F-15E Strike Eagle,15,blue,Late Cold War,yes,no,,,"2 Jet engine, swept wing, 2 crew, all weather fighter and strike. Strike Eagle.",Boom AAR,"Fox 1,2,3, Gun, Dumb bombs, smart bombs, standoff, Can AAR, supersonic",
|
||||
F-16C_50,yes,Aircraft,Aircraft,F-16C Viper,16,blue,Late Cold War,yes,no,,,"Single jet engine, swept wing, 1 crew, all weather fighter and strike. Viper.",Boom AAR,"Fox 2, 3 and gun, Dumb and smart bombs, rockets and ATGMs, standoff, Can AAR, supersonic,",
|
||||
F-4E,yes,Aircraft,Aircraft,F-4E Phantom II,4,blue,Mid Cold War,yes,no,,,"2 Jet engine, swept wing, 2 crew. Phantom",Drogue AAR,"Fox 1,2, and Gun, Dumb bombs, laser bombs and rockets, Can AAR, supersonic",
|
||||
F-5E-3,yes,Aircraft,Aircraft,F-5E Tiger,5,blue,Mid Cold War,yes,no,,,"2 Jet engine, swept wing, single crew. Tiger",,"Fox 1,2, and Gun, Dumb bombs, laser bombs and rockets, supersonic",
|
||||
F-86F Sabre,yes,Aircraft,Aircraft,F-86F Sabre,86,blue,Early Cold War,yes,no,,,"Single engine, swept wing, 1 crew. Sabre",,"Fox 2 and Gun, Dumb bombs and rockets, Transonic",
|
||||
FA-18C_hornet,yes,Aircraft,Aircraft,F/A-18C,18,blue,Late Cold War,yes,no,,,"2 Jet engine, swept wing, 1 crew, fighter and strike. Hornet",Drogue AAR,"Fox 1,2,3, Gun, Dumb bombs and smart bombs, rockets and ATGMs, antiship, SEAD, standoff, Can AAR, Supersonic",
|
||||
FW-190A8,yes,Aircraft,Aircraft,FW-190A8 Bosch,190A8,red,WW2,yes,no,,,"Single propellor, straight wing, 1 crew. Shrike",,"Guns, dumb bombs and rockets, Subsonic",
|
||||
FW-190D9,yes,Aircraft,Aircraft,FW-190D9 Jerry,190D9,red,WW2,yes,no,,,"Single propellor, straight wing, 1 crew. Shrike",,"Guns, dumb bombs and rockets, Subsonic",
|
||||
H-6J,yes,Aircraft,Aircraft,H-6J Badger,H6,red,Mid Cold War,yes,no,,,"2 jet engine, swept wing, 4 crew bomber. Badger",,"Dumb bombs, standoff, antiship, Subsonic",
|
||||
I-16,yes,Aircraft,Aircraft,I-16,I16,red,WW2,yes,no,,,"Single propellor, straight wing, 1 crew. Ishak",,"Guns, dumb bombs and rockets, Subsonic",
|
||||
IL-76MD,yes,Aircraft,Aircraft,IL-76MD Candid,76,red,Mid Cold War,no,no,,,"4 jet engine, swept wing, 5 crew. Cargo and passenger aircraft. NATO reporting name: Candid",,Subsonic,
|
||||
IL-78M,yes,Aircraft,Aircraft,IL-78M Midas,78,red,Late Cold War,no,no,,,"4 jet engine, swept wing, 6 crew. Tanker aircraft. NATO reporting name: Midas","Tanker, Drogue AAR","Droge tanker, Subsonic",
|
||||
J-11A,yes,Aircraft,Aircraft,J-11A Flaming Dragon,11,red,Modern,yes,no,,,"2 Jet engine, swept wing, 1 crew, fighter and strike. NATO reporting name: Flanker",,"Fox 1, 2, 3, and Gun, Dumb bombs and rockets, Supersonic",
|
||||
JF-17,yes,Aircraft,Aircraft,JF-17 Thunder,17,red,Modern,yes,no,,,"Single jet engine, swept wing, 1 crew, fighter and strike. Geoff",Drogue AAR,"Fox 1,2,3, Gun, Dumb bombs and smart bombs, rockets and ATGMs, antiship, SEAD, standoff, Can AAR, Supersonic",
|
||||
KC-135,yes,Aircraft,Aircraft,KC-135 Stratotanker,35,blue,Early Cold War,no,no,,,"4 jet engine, swept wing, 3 crew. Tanker aircraft. Stratotanker","Tanker, Boom AAR","Boom tanker, Subsonic",
|
||||
KC135MPRS,yes,Aircraft,Aircraft,KC-135 MPRS Stratotanker,35M,blue,Early Cold War,no,no,,,"4 jet engine, swept wing, 3 crew. Tanker aircraft. Stratotanker","Tanker, Drogue AAR","Droge tanker, Subsonic",
|
||||
L-39ZA,yes,Aircraft,Aircraft,L-39ZA,39,red,Mid Cold War,yes,no,,,"Single jet engine, swept wing, 2 crew, light attack trainer. Aviojet",,"Fox 2, Dumb bombs, rockets, gunpod, subsonic,",
|
||||
M-2000C,yes,Aircraft,Aircraft,M-2000C Mirage,M2,blue,Late Cold War,yes,no,,,"Single jet engine, swept wing, 1 crew, fighter and strike.",Drogue AAR,"Fox 1, 2, gun, Dumb bombs and laser bombs, Can AAR Supersonic",
|
||||
MB-339A,yes,Aircraft,Aircraft,MB-339A,39,blue,Mid Cold War,yes,no,,,"Single jet engine, swept wing, 2 crew, light attack trainer. Aviojet",,"Fox 2, Dumb bombs, rockets, gunpod, subsonic,",
|
||||
MiG-15bis,yes,Aircraft,Aircraft,MiG-15 Fagot,M15,red,Early Cold War,yes,no,,,"Single jet engine, swept wing, 1 crew. Fagot",,"Gun, Dumb bombs, Transonic",
|
||||
MiG-19P,yes,Aircraft,Aircraft,MiG-19 Farmer,19,red,Early Cold War,yes,no,,,"Single jet engine, swept wing, 1 crew. Farmer",,"Fox 2, gun, Dumb bombs and rockets, Supersonic",
|
||||
MiG-21Bis,yes,Aircraft,Aircraft,MiG-21 Fishbed,21,red,Mid Cold War,yes,no,,,"Single jet engine, swept wing, 1 crew. Fishbed",,"Fox 1, fox 2, gun, Dumb bombs, nukes, ATGMs, and rockets, Supersonic",
|
||||
MiG-23MLD,yes,Aircraft,Aircraft,MiG-23 Flogger,23,red,Mid Cold War,yes,no,,,"Single jet engine, swing wing, 1 crew. Flogger",,"Fox1, fox 2, gun, Dumb bombs and rockets, Supersonic",
|
||||
MiG-25PD,yes,Aircraft,Aircraft,MiG-25PD Foxbat,25,red,Mid Cold War,yes,no,,,"2 jet engine, swept wing, 1 crew. Foxbat",,"Fox1, fox 2, Supersonic",
|
||||
MiG-25RBT,yes,Aircraft,Aircraft,MiG-25RBT Foxbat,25,red,Mid Cold War,yes,no,,,"2 jet engine, swept wing, 1 crew. Foxbat",,"Fox 2, Dumb bombs, Supersonic",
|
||||
MiG-27K,yes,Aircraft,Aircraft,MiG-27K Flogger-D,27,red,Mid Cold War,yes,no,,,"Single jet engine, swing wing, 1 crew. Flogger",,"Fox 2 and gun, Dumb bombs and laser bombs, ATGMs, SEAD, Rockets, Supersonic",
|
||||
MiG-29A,yes,Aircraft,Aircraft,MiG-29A Fulcrum,29A,red,Late Cold War,yes,no,,,"2 jet engine, swept wing, 1 crew. Flanker",Drogue AAR,"Fox 1 and fox 2, Gun, Dumb bombs, Rockets, Can AAR, Supersonic",
|
||||
MiG-29S,yes,Aircraft,Aircraft,MiG-29S Fulcrum,29S,red,Late Cold War,yes,no,,,"2 jet engine, swept wing, 1 crew. Flanker",Drogue AAR,"Fox 1 and fox 2, Gun, Dumb bombs, Rockets, Can AAR, Supersonic",
|
||||
MiG-31,yes,Aircraft,Aircraft,MiG-31 Foxhound,31,red,Late Cold War,yes,no,,,"2 jet engine, swept wing, 2 crew. Foxhound",Drogue AAR,"Fox 1 and fox 2, Gun, Can AAR, Supersonic",
|
||||
Mirage-F1EE,yes,Aircraft,Aircraft,Mirage-F1EE,F1EE,blue,Mid Cold War,yes,no,,,"Single Jet engine, swept wing, 1 crew.",Drogue AAR,"Fox 1, fox 2, and gun, Dumb and laser bombs, and rockets, Can AAR, Supersonic",
|
||||
MosquitoFBMkVI,yes,Aircraft,Aircraft,Mosquito FB MkVI,Mo,blue,WW2,yes,no,,,"2 propeller, straight wing, 2 crew. Mosquito.",,"Gun, dumb bomb, and rockets, Subsonic",
|
||||
MQ-9 Reaper,yes,Aircraft,Aircraft,MQ-9 Reaper,9,blue,Modern,yes,no,,,"Single turboprop, straight wing, attack aircraft. Reaper",,"Laser bombs, smart bombs, ATGMs, subsonic",
|
||||
P-47D-40,yes,Aircraft,Aircraft,P-47D Thunderbolt,P47,blue,WW2,yes,no,,,"Single propellor, straight wing, 1 crew. Thunderbolt",,"Gun, dumb bomb, and rockets, Subsonic",
|
||||
P-51D-30-NA,yes,Aircraft,Aircraft,P-51D Mustang,P51,blue,WW2,yes,no,,,"Single propellor, straight wing, 1 crew. Mustang",,"Gun, dumb bomb, and rockets, Subsonic",
|
||||
S-3B Tanker,yes,Aircraft,Aircraft,S-3B Tanker,S3B,blue,Early Cold War,no,no,,,"2 jet engine, straight wing, 4 crew. Viking","Tanker, Drogue AAR",Subsonic,
|
||||
Su-17M4,yes,Aircraft,Aircraft,Su-17M4 Fitter,17M,red,Mid Cold War,yes,no,,,"Single jet engine, swept wing, 1 crew. Fitter",,"Fox 2 and gun, dumb bombs and rockets, Transonic",
|
||||
Su-24M,yes,Aircraft,Aircraft,Su-24M Fencer,24,red,Mid Cold War,yes,no,,,"2 jet engine, swing wing, 2 crew. Fencer",,"Fox 2 and gun, Dumb and laser bombs, and rockets, Supersonic",
|
||||
Su-25,yes,Aircraft,Aircraft,Su-25A Frogfoot,S25,red,Late Cold War,yes,no,,,"2 jet engine, swept wing, 1 crew. Frogfoot",,"Fox 2 and gun, Dumb bombs, rockets, and ATGMs, Subsonic",
|
||||
Su-25,yes,Aircraft,Aircraft,Su-25T Frogfoot,S25,red,Late Cold War,yes,no,,,"2 jet engine, swept wing, 1 crew. Frogfoot",,"Fox 2 and gun, Dumb bombs, rockets, SEAD and ATGMs, Subsonic",
|
||||
Su-27,yes,Aircraft,Aircraft,Su-27 Flanker,27,red,Late Cold War,yes,no,,,"2 jet engine, swept wing, 1 crew. Flanker",Drogue AAR,"Fox 1 and fox 2, Gun, Dumb bombs, Rockets, Can AAR, Supersonic",
|
||||
Su-30,yes,Aircraft,Aircraft,Su-30 Super Flanker,30,red,Late Cold War,yes,no,,,"2 jet engine, swept wing, 1 crew. Flanker",Drogue AAR,"Fox 1,2,3, Gun, Dumb bombs, smart bombs, ATGMS, anti-ship and SEAD, Can AAR, supersonic",
|
||||
Su-33,yes,Aircraft,Aircraft,Su-33 Navy Flanker,33,red,Late Cold War,yes,no,,,"2 jet engine, swept wing, 1 crew. Flanker",Drogue AAR,"Fox 1 and fox 2, Gun, Dumb bombs, Rockets, Can AAR, Supersonic",
|
||||
Su-34,yes,Aircraft,Aircraft,Su-34 Hellduck,34,red,Modern,yes,no,,,"2 Jet engine, swept wing, 2 crew, all weather fighter and strike. Fullback",Drogue AAR,"Fox 1,2,3, Gun, Dumb bombs, smart bombs, anti-ship, SEAD, Can AAR, supersonic",
|
||||
Tornado GR4,yes,Aircraft,Aircraft,Tornado GR4,GR4,blue,Late Cold War,yes,no,,,"2 jet engine, swing wing, 2 crew, all weather strike.",Drogue AAR,"Fox 2 and gun, Dumb and laser bombs, Anti-ship and SEAD, Can AAR",
|
||||
Tornado IDS,yes,Aircraft,Aircraft,Tornado IDS,IDS,blue,Late Cold War,yes,no,,,"2 jet engine, swing wing, 2 crew, all weather strike.",Drogue AAR,"Fox 2 and gun, Dumb and laser bombs, Anti-ship and SEAD, Can AAR",
|
||||
Tu-142,yes,Aircraft,Aircraft,Tu-142 Bear,142,red,Mid Cold War,yes,no,,,"4 turboprop, swept wing, 11 crew, bomber. Bear",,"Anti-ship missiles, Subsonic",
|
||||
Tu-160,yes,Aircraft,Aircraft,Tu-160 Blackjack,160,red,Late Cold War,yes,no,,,"4 jet engine, swing wing, 4 crew bomber. Blackjack",,"Anti-ship missiles, Supersonic",
|
||||
Tu-22M3,yes,Aircraft,Aircraft,Tu-22M3 Backfire,T22,red,Late Cold War,yes,no,,,"2 jet engine, swing wing, 4 crew bomber. Backfire",,"Dumb bombs and anti-ship missiles, Supersonic",
|
||||
Tu-95MS,yes,Aircraft,Aircraft,Tu-95MS Bear,95,red,Mid Cold War,yes,no,,,"4 turboprop, swept wing, 6 crew, bomber. Bear",,"Anti-ship missiles, Subsonic",
|
||||
1L13 EWR,yes,Ground Unit,EW Radar,Box Spring,1L13 EWR,red,Late Cold War,no,no,300000,0,EWR built on a truck trailer,,"500km range, 40km altitude",Box Spring: Mobile electronic warfare system designed to disrupt enemy communication and Radar systems.
|
||||
2B11 mortar,yes,Ground Unit,Artillery,2B11 mortar,2B11 mortar,red,Late Cold War,yes,no,0,7000,Man portable 120mm mortar,,"0,5km-7km 12-15 rpm","2B11 mortar: Soviet 120mm towed mortar, known for its portability and effective indirect fire support."
|
||||
2S6 Tunguska,yes,Ground Unit,AAA,SA-19 Tunguska,SA-19,red,Late Cold War,yes,no,18000,8000,2K22 Tunguska. Tracked self-propelled anti-aircraft 30mm guns and missiles,,"Can move, Radar gun, optical missiles, 5nm/12,000ft","SA-19 Tunguska: Russian self-propelled anti-aircraft weapon system, combining both guns and missiles for air defense."
|
||||
55G6 EWR,yes,Ground Unit,EW Radar,Tall Rack,55G6 EWR,red,Early Cold War,no,no,400000,0,EWR built on a truck trailer,,"500km range, 40km altitude",Tall Rack: Naval electronic warfare system designed for electronic countermeasures.
|
||||
5p73 s-125 ln,yes,Ground Unit,SAM Launcher,SA-3 Launcher,5p73 s-125 ln,red,Early Cold War,yes,no,0,18000,4 SA-3 missiles on a static emplacement. Requires grouping with SA-3 components,,10nm range,"SA-3 Launcher: Soviet surface-to-air missile launcher, part of the SA-3 Goa air defense system."
|
||||
AA8,yes,Ground Unit,Unarmed,Firefighter Vehicle AA-7.2/60,Firefighter Vehicle AA-7.2/60,,,no,no,0,0,,,,Firefighter Vehicle AA-7.2/60: Firefighting vehicle equipped with a 7.2m telescopic ladder.
|
||||
AAV7,yes,Ground Unit,Armoured Personnel Carrier,AAV7,AAV7,blue,Mid Cold War,yes,no,0,1200,Amphibious assault vehicle. Tracked,,"12,7mm machine gun, 64 km/h road, 13,5 km/h water",AAV7: Amphibious Assault Vehicle used by the United States Marine Corps for troop transport.
|
||||
Allies_Director,no,Ground Unit,Unarmed,Allies Rangefinder (DRT),Allies Rangefinder (DRT),,,no,no,30000,0,,,,Allies Rangefinder (DRT): Allied artillery rangefinder for accurate target distance measurement.
|
||||
ATMZ-5,yes,Ground Unit,Unarmed,ATMZ-5,ATMZ-5,red,Early Cold War,no,no,0,0,Refueler truck. Wheeled,,"unarmed, 75 km/h on road",ATMZ-5: Soviet pontoon bridge and ferry system used for river crossings.
|
||||
ATZ-10,yes,Ground Unit,Unarmed,ATZ-10,ATZ-10,red,Early Cold War,no,no,0,0,Refueler truck. Wheeled,,"unarmed, 75 km/h on road",ATZ-10: Soviet military refueling vehicle used to refuel tanks and other military vehicles.
|
||||
ATZ-5,yes,Ground Unit,Unarmed,Refueler ATZ-5,Refueler ATZ-5,,,no,no,0,0,,,,Refueler ATZ-5: Mobile refueling vehicle used for fueling military vehicles.
|
||||
ATZ-60_Maz,yes,Ground Unit,Unarmed,Refueler ATZ-60 Tractor (MAZ-7410),Refueler ATZ-60 Tractor (MAZ-7410),,,no,no,0,0,,,,Refueler ATZ-60 Tractor (MAZ-7410): Military refueling tractor used for ground vehicle refueling.
|
||||
Bedford_MWD,yes,Ground Unit,Unarmed,Truck Bedford,Truck Bedford,,,no,no,0,0,,,,Truck Bedford: British military truck used for various logistical purposes.
|
||||
Blitz_36-6700A,yes,Ground Unit,Unarmed,Truck Opel Blitz,Truck Opel Blitz,,,no,yes,0,0,,,,Truck Opel Blitz: German military truck used during World War II.
|
||||
BMD-1,yes,Ground Unit,Infantry Fighting Vehicle,BMD-1,BMD-1,red,Mid Cold War,yes,no,0,3000,Infantry fighting vehicle. Tracked. Amphibious,,"73mm gun, 7,62 gun, 70 km/h road, 10 km/h water",BMD-1: Airborne infantry fighting vehicle with amphibious capabilities used by the Soviet Union.
|
||||
BMP-1,yes,Ground Unit,Infantry Fighting Vehicle,BMP-1,BMP-1,red,Mid Cold War,yes,no,0,3000,Infantry fighting vehicle. Tracked. Amphibious,,"ATGM, 73mm gun, 7,62 gun, 65 km/h road, 7 km/h water","BMP-1: Soviet infantry fighting vehicle, a pioneer in combining heavy firepower with troop transport."
|
||||
BMP-2,yes,Ground Unit,Infantry Fighting Vehicle,BMP-2,BMP-2,red,Mid Cold War,yes,no,0,3000,Infantry fighting vehicle. Tracked. Amphibious,,"ATGM, 30mm gun, 7,62 gun, 65 km/h road, 7 km/h water","BMP-2: Upgraded version of the BMP-1, featuring enhanced weaponry and protection."
|
||||
BMP-3,yes,Ground Unit,Infantry Fighting Vehicle,BMP-3,BMP-3,red,Late Cold War,yes,no,0,4000,Infantry fighting vehicle. Tracked. Amphibious,,"ATGM, 100mm gun, 30mm gun, 7,62 gun, 65 km/h road, 7 km/h water",BMP-3: Modern Russian infantry fighting vehicle with significant improvements in firepower and mobility.
|
||||
bofors40,yes,Ground Unit,AAA,AAA Bofors 40mm,AAA Bofors 40mm,,,yes,no,0,4000,,,,AAA Bofors 40mm: Swedish-designed anti-aircraft gun with a 40mm caliber.
|
||||
Boxcartrinity,yes,Ground Unit,Carriage,Flatcar,Flatcar,,,no,no,0,0,,,,Flatcar: Railway wagon with a flat platform for transporting heavy equipment.
|
||||
BRDM-2,yes,Ground Unit,Armoured Car,BRDM-2,BRDM-2,red,Early Cold War,no,no,0,1600,Scout car. Wheeled. Amphibious,,"14,5mm gun, 7,62mm gun, 100 km/h road, 10 km/h water",BRDM-2: Amphibious armored reconnaissance vehicle used by various countries.
|
||||
BTR_D,yes,Ground Unit,Armoured Personnel Carrier,BTR_D,BTR_D,red,Mid Cold War,yes,no,0,3000,Armoured persononel carrier. Tracked.,,"ATGM, 7,62mm gun, 61 km/h road",BTR-D: Soviet airborne multi-purpose tracked vehicle designed for troop transport.
|
||||
BTR-80,yes,Ground Unit,Armoured Personnel Carrier,BTR-80,BTR-80,red,Late Cold War,yes,no,0,1600,Armoured persononel carrier. Wheeled. Amphibious,,"14,5mm gun, 7,62mm gun, 80 km/h road, 10 km/h water",BTR-80: 8x8 wheeled armored personnel carrier known for its versatility and mobility.
|
||||
BTR-82A,yes,Ground Unit,Infantry Fighting Vehicle,Infantry Fighting Vehicle BTR-82A,Infantry Fighting Vehicle BTR-82A,,,yes,no,0,2000,,,,Infantry Fighting Vehicle BTR-82A: Russian infantry fighting vehicle known for its versatility.
|
||||
Bunker,yes,Ground Unit,Structure,Bunker,Bunker,,,no,no,0,800,Concrete bunker. Structure. Fixed Position.,,"12,7 mm machine gun,",Bunker: Fortified military structure providing protection and strategic position.
|
||||
CCKW_353,no,Ground Unit,Unarmed,"Truck GMC ""Jimmy"" 6x6","Truck GMC ""Jimmy"" 6x6",,,no,no,0,0,,,,"Truck GMC ""Jimmy"" 6x6: American military truck used for various logistical purposes."
|
||||
Centaur_IV,no,Ground Unit,Tank,Tk Centaur IV CS,Tk Centaur IV CS,,,yes,no,0,6000,,,,Tk Centaur IV CS: British cruiser tank variant with close support modifications.
|
||||
Challenger2,yes,Ground Unit,Tank,Challenger2,Challenger2,blue,Modern,yes,no,0,3500,Main battle tank. Tracked. Modern and heavily armoured.,,"120mm gun, 7,62 mm gun x 2, 59 km/h road, 40 km/h off,",Challenger 2: British main battle tank known for its armor protection and firepower.
|
||||
Chieftain_mk3,yes,Ground Unit,Tank,Tank Chieftain Mk.3,Tank Chieftain Mk.3,,,yes,no,0,3500,,,,Tank Chieftain Mk.3: British main battle tank known for its heavy armor.
|
||||
Churchill_VII,no,Ground Unit,Tank,Tk Churchill VII,Tk Churchill VII,,,yes,no,0,3000,,,,Tk Churchill VII: British infantry tank known for its heavy armor.
|
||||
Coach a passenger,yes,Ground Unit,Carriage,Passenger Car,Passenger Car,,,no,no,0,0,,,,Passenger Car: Railway carriage for passenger transport.
|
||||
Coach a platform,yes,Ground Unit,Carriage,Coach Platform,Coach Platform,,,no,no,0,0,,,,Coach Platform: Railway wagon with a flat platform for transporting heavy equipment.
|
||||
Coach a tank blue,yes,Ground Unit,Carriage,Tank Car blue,Tank Car blue,,,no,no,0,0,,,,Tank Car blue: Railway tank car used for transporting liquids.
|
||||
Coach a tank yellow,yes,Ground Unit,Carriage,Tank Car yellow,Tank Car yellow,,,no,no,0,0,,,,Tank Car yellow: Railway tank car used for transporting liquids.
|
||||
Coach cargo,yes,Ground Unit,Carriage,Freight Van,Freight Van,,,no,no,0,0,,,,Freight Van: Railway wagon used for transporting goods.
|
||||
Coach cargo open,yes,Ground Unit,Carriage,Open Wagon,Open Wagon,,,no,no,0,0,,,,Open Wagon: Open railway wagon for bulk cargo.
|
||||
Cobra,yes,Ground Unit,Armoured Car,Otokar Cobra,Cobra,blue,Modern,yes,no,0,1200,"Armoured car, MRAP. Wheeled.",,"12,7 mm machine gun,","Otokar Cobra: Turkish armored vehicle used for reconnaissance, patrol, and security missions."
|
||||
Cromwell_IV,no,Ground Unit,Tank,Tk Cromwell IV,Tk Cromwell IV,,,yes,no,0,3000,,,,Tk Cromwell IV: British cruiser tank used during World War II.
|
||||
Daimler_AC,no,Ground Unit,Armoured Car,Car Daimler Armored,Car Daimler Armored,,,yes,no,0,2000,,,,Car Daimler Armored: British armored car used for reconnaissance.
|
||||
Dog Ear radar,yes,Ground Unit,SAM Track Radar,Dog Ear,Dog Ear Radar,red,Mid Cold War,no,no,35000,0,9S80-1 Sborka Mobile. Tracked fire control Radar that can integrate with missile and gun systems.,,"90 km detection range, 35 km tracking range,",Dog Ear: Mobile Radar system used for early warning and target acquisition.
|
||||
DR_50Ton_Flat_Wagon,no,Ground Unit,Carriage,DR 50-ton flat wagon,DR 50-ton flat wagon,,,no,no,0,0,,,,DR 50-ton flat wagon: Railway flat wagon with a capacity for heavy loads.
|
||||
DRG_Class_86,no,Ground Unit,Locomotive,Loco DRG Class 86,Loco DRG Class 86,,,no,no,0,0,,,,Loco DRG Class 86: German steam locomotive used for transportation.
|
||||
Electric locomotive,yes,Ground Unit,Locomotive,Loco VL80 Electric,Loco VL80 Electric,,,no,no,0,0,,,,Loco VL80 Electric: Electric locomotive used for transportation.
|
||||
Elefant_SdKfz_184,no,Ground Unit,Tank,Self Propelled Gun Elefant TD,Self Propelled Gun Elefant TD,,,yes,no,0,6000,,,,Self Propelled Gun Elefant TD: German tank destroyer with heavy armor.
|
||||
ES44AH,yes,Ground Unit,Locomotive,Loco ES44AH,Loco ES44AH,,,no,no,0,0,,,,Loco ES44AH: Diesel-electric locomotive used for freight transportation.
|
||||
fire_control,no,Ground Unit,Structure,Bunker with Fire Control Center,Bunker with Fire Control Center,,,no,no,0,1100,,,,Bunker with Fire Control Center: Fortified bunker with integrated fire control.
|
||||
flak18,yes,Ground Unit,AAA,"AAA 8,8cm Flak 18","AAA 8,8cm Flak 18",,,yes,no,0,5000,,,,"AAA 8,8cm Flak 18: German anti-aircraft gun with an 88mm caliber."
|
||||
Flakscheinwerfer_37,no,Ground Unit,AAA,SL Flakscheinwerfer 37,SL Flakscheinwerfer 37,,,yes,no,15000,15000,,,,SL Flakscheinwerfer 37: German searchlight used for anti-aircraft defense.
|
||||
FPS-117,yes,Ground Unit,EW Radar,EW Radar,EWR AN/FPS-117 Radar,,,no,no,463000,0,,,,EWR AN/FPS-117 Radar: Early warning Radar system used for surveillance.
|
||||
FPS-117 Dome,yes,Ground Unit,EW Radar,EWR AN/FPS-117 Radar (domed),EWR AN/FPS-117 Radar (domed),,,no,no,400000,0,,,,EWR AN/FPS-117 Radar (domed): Early warning Radar system with a domed radome.
|
||||
FPS-117 ECS,yes,Ground Unit,EW Radar,EWR AN/FPS-117 ECS,EWR AN/FPS-117 ECS,,,no,no,0,0,,,,EWR AN/FPS-117 ECS: Early warning Radar system with an environmental control system.
|
||||
FuMG-401,no,Ground Unit,EW Radar,EWR FuMG-401 Freya LZ,EWR FuMG-401 Freya LZ,,,no,no,160000,0,,,,EWR FuMG-401 Freya LZ: German early warning Radar system.
|
||||
FuSe-65,no,Ground Unit,EW Radar,EWR FuSe-65 Würzburg-Riese,EWR FuSe-65 Würzburg-Riese,,,no,no,60000,0,,,,EWR FuSe-65 Würzburg-Riese: German Radar system used for target acquisition.
|
||||
GAZ-3307,yes,Ground Unit,Unarmed,GAZ-3307,GAZ-3307,red,Early Cold War,no,no,0,0,"Civilian truck, single axle, wheeled",,56 mph,GAZ-3307: Soviet/Russian military truck used for various logistics purposes.
|
||||
GAZ-3308,yes,Ground Unit,Unarmed,GAZ-3308,GAZ-3308,red,Early Cold War,no,yes,0,0,"Military truck, single axle, canvas covered cargo bay. wheeled",,"Rearms ground units of same coaltion, 56 mph","GAZ-3308: Military version of the GAZ-3307, widely used for transportation."
|
||||
GAZ-66,yes,Ground Unit,Unarmed,GAZ-66,GAZ-66,red,Early Cold War,no,yes,0,0,"Military truck, single axle, open cargo bay. wheeled",,90 km/h,GAZ-66: Soviet/Russian military truck known for its off-road capabilities.
|
||||
generator_5i57,yes,Ground Unit,Unarmed,Diesel Power Station 5I57A,Diesel Power Station 5I57A,,,no,no,0,0,,,,Diesel Power Station 5I57A: Mobile diesel power station used for electricity generation.
|
||||
Gepard,yes,Ground Unit,AAA,Gepard,Gepard,blue,Late Cold War,yes,no,15000,4000,Tracked self-propelled anti-aircraft 35mm guns,,"65 km/h road, Range 3km",Gepard: German anti-aircraft tank designed to protect armored formations.
|
||||
German_covered_wagon_G10,no,Ground Unit,Carriage,Wagon G10 (Germany),Wagon G10 (Germany),,,no,no,0,0,,,,Wagon G10 (Germany): German railway wagon for transporting goods.
|
||||
German_tank_wagon,no,Ground Unit,Carriage,Tank Car (Germany),Tank Car (Germany),,,no,no,0,0,,,,Tank Car (Germany): German railway tank car for transporting liquids.
|
||||
Grad_FDDM,yes,Ground Unit,Artillery,Grad MRL FDDM (FC),Grad MRL FDDM (FC),,,yes,no,0,1000,,,,Grad MRL FDDM (FC): Multiple rocket launcher system with fire direction and control module.
|
||||
Grad-URAL,yes,Ground Unit,Unarmed,Grad,Grad,red,Mid Cold War,no,no,0,19000,"Military truck, single axle, open cargo bay. wheeled",,"80 km/h, Rearms ground units of same coaltion?",Grad: Multiple rocket launcher system capable of delivering devastating firepower.
|
||||
Hawk cwar,yes,Ground Unit,SAM Search Radar,Hawk Continous Wave Acquisition Radar,Hawk cwar,blue,Early Cold War,no,no,70000,0,Hawk site Aquisition Radar,,70km range,"Hawk Continuous Wave Acquisition Radar: Part of the Hawk air defense system, used for target acquisition."
|
||||
Hawk ln,yes,Ground Unit,SAM Launcher,Hawk Launcher,Hawk ln,blue,Late Cold War,no,no,0,45000,Hawk site missile laucher. 3 missiles. Needs rest of site to fuction,,,Hawk Launcher: Mobile launcher for the Hawk surface-to-air missile system.
|
||||
Hawk pcp,yes,Ground Unit,SAM Support vehicle,Hawk Platoon Command Post,Hawk pcp,blue,Late Cold War,no,no,0,0,Hawk site command post. Medium sized trailer.,,,Hawk Platoon Command Post: Command and control center for the Hawk air defense system.
|
||||
Hawk SAM Battery,yes,Ground Unit,SAM Site,Hawk SAM Battery,Hawk SAM Battery,blue,Early Cold War,no,no,90000,0,Multiple unit SAM site,,"25nm range, >50,000ft alititude",Hawk SAM Battery: Surface-to-air missile system providing air defense capabilities.
|
||||
Hawk sr,yes,Ground Unit,SAM Search Radar,Hawk Search Radar,Hawk sr,blue,Early Cold War,no,no,90000,0,Hawk site search Radar. Medium sized trailer,,,Hawk Search Radar: Radar system used in conjunction with the Hawk air defense system.
|
||||
Hawk tr,yes,Ground Unit,SAM Track Radar,Hawk Track Radar,Hawk tr,blue,Early Cold War,no,no,90000,0,Hawk site track Radar. Medium sized trailer,,,Hawk Track Radar: Radar system used for tracking targets in the Hawk air defense system.
|
||||
HEMTT TFFT,yes,Ground Unit,Unarmed,HEMTT TFFT,HEMTT TFFT,blue,Late Cold War,no,no,0,0,"Military truck, 2 axle, firefigther. wheeled",,40kn on road,HEMTT TFFT: Heavy Expanded Mobility Tactical Truck used for transport and logistics.
|
||||
HL_B8M1,yes,Ground Unit,Artillery,MLRS HL with B8M1 80mm,MLRS HL with B8M1 80mm,,,yes,no,5000,5000,,,,MLRS HL with B8M1 80mm: Self-propelled multiple rocket launcher system with 80mm rockets.
|
||||
HL_DSHK,yes,Ground Unit,Armoured Car,Scout HL with DSHK 12.7mm,Scout HL with DSHK 12.7mm,,,yes,no,5000,1200,,,,Scout HL with DSHK 12.7mm: Scout vehicle with a mounted DShK heavy machine gun.
|
||||
HL_KORD,yes,Ground Unit,Armoured Car,Scout HL with KORD 12.7mm,Scout HL with KORD 12.7mm,,,yes,no,5000,1200,,,,Scout HL with KORD 12.7mm: Scout vehicle with a mounted KORD heavy machine gun.
|
||||
HL_ZU-23,yes,Ground Unit,AAA,SPAAA HL with ZU-23,SPAAA HL with ZU-23,,,yes,no,5000,2500,,,,SPAAA HL with ZU-23: Self-propelled anti-aircraft artillery with dual ZU-23 autocannons.
|
||||
Horch_901_typ_40_kfz_21,yes,Ground Unit,Unarmed,LUV Horch 901 Staff Car,LUV Horch 901 Staff Car,,,no,no,0,0,,,,LUV Horch 901 Staff Car: German military staff car.
|
||||
house1arm,yes,Ground Unit,Structure,house1arm,house1arm,,,no,no,0,800,,,,house1arm: Fortified building with additional armor.
|
||||
house2arm,yes,Ground Unit,Structure,house2arm,house2arm,,,no,no,0,800,,,,house2arm: Fortified building with additional armor.
|
||||
houseA_arm,yes,Ground Unit,Structure,houseA_arm,houseA_arm,,,no,no,0,800,,,,houseA_arm: Fortified building with additional armor.
|
||||
HQ-7_LN_EO,yes,Ground Unit,SAM Track Radar,HQ-7 LN Electro-Optics,HQ-7 LN Electro-Optics,,,no,no,8000,12000,,,,HQ-7 LN Electro-Optics: Chinese air defense system with electro-optical tracking.
|
||||
HQ-7_LN_SP,yes,Ground Unit,SAM Launcher,HQ-7 Self-Propelled LN,HQ-7 Self-Propelled LN,,,no,no,15000,15000,,,,HQ-7 Self-Propelled LN: Chinese self-propelled air defense system.
|
||||
HQ-7_STR_SP,yes,Ground Unit,SAM Track Radar,HQ-7 Self-Propelled STR,HQ-7 Self-Propelled STR,,,no,no,30000,0,,,,HQ-7 Self-Propelled STR: Chinese air defense system with Radar tracking.
|
||||
Hummer,yes,Ground Unit,Armoured Car,Hummer,Hummer,blue,Mid Cold War,no,no,0,0,"Military car, single axle, wheeled",,113 km/h road,"Hummer: Versatile military vehicle used for various purposes, including transport and reconnaissance."
|
||||
hy_launcher,yes,Ground Unit,Missile system,AShM SS-N-2 Silkworm,AShM SS-N-2 Silkworm,,,yes,no,100000,100000,,,,AShM SS-N-2 Silkworm: Soviet anti-ship missile system.
|
||||
Igla manpad INS,yes,Ground Unit,MANPADS,SA-18 Igla manpad INS,Igla manpad INS,red,Late Cold War,no,no,5000,5200,9K38/SA-18 Man portable air defence. Heatseaker,,"5,2km range, 3,5km altitude","SA-18 Igla manpad INS: Portable, man-portable air defense system designed for infantry use."
|
||||
IKARUS Bus,yes,Ground Unit,Unarmed,IKARUS Bus,IKARUS Bus,red,Mid Cold War,no,no,0,0,Civilian Bus. Yellow. Bendy bus,,80km/h road,IKARUS Bus: Military transport bus used for troop movement.
|
||||
Infantry AK,yes,Ground Unit,Infantry,Infantry AK,Infantry AK,red,Mid Cold War,yes,no,0,500,Single infantry carrying AK-74,,8kn max speed,"Infantry AK: Standard issue assault rifle for infantry, known for its reliability and firepower."
|
||||
Infantry AK Ins,yes,Ground Unit,Infantry,Insurgent AK-74,Insurgent AK-74,,,yes,no,0,500,,,,Insurgent AK-74: Variant of the AK-74 rifle used by insurgent forces.
|
||||
Infantry AK ver2,yes,Ground Unit,Infantry,Infantry AK-74 Rus ver2,Infantry AK-74 Rus ver2,,,yes,no,0,500,,,,Infantry AK-74 Rus ver2: Variant of the AK-74 rifle used by Russian infantry.
|
||||
Infantry AK ver3,yes,Ground Unit,Infantry,Infantry AK-74 Rus ver3,Infantry AK-74 Rus ver3,,,yes,no,0,500,,,,Infantry AK-74 Rus ver3: Variant of the AK-74 rifle used by Russian infantry.
|
||||
Infantry Animated,yes,Ground Unit,Infantry,Infantry,Infantry,,,yes,no,0,500,,,,Infantry: Standard infantry equipped for ground combat.
|
||||
Jagdpanther_G1,no,Ground Unit,Tank,Self Propelled Gun Jagdpanther TD,Self Propelled Gun Jagdpanther TD,,,yes,no,0,5000,,,,Self Propelled Gun Jagdpanther TD: German tank destroyer based on the Panther chassis.
|
||||
JagdPz_IV,no,Ground Unit,Tank,Self Propelled Gun Jagdpanzer IV TD,Self Propelled Gun Jagdpanzer IV TD,,,yes,no,0,3000,,,,Self Propelled Gun Jagdpanzer IV TD: German tank destroyer based on the Panzer IV chassis.
|
||||
JTAC,yes,Ground Unit,Infantry,JTAC,JTAC,,,no,no,0,0,,,,JTAC: Joint Terminal Attack Controller responsible for coordinating air support.
|
||||
KAMAZ Truck,yes,Ground Unit,Unarmed,KAMAZ Truck,KAMAZ Truck,red,Mid Cold War,no,yes,0,0,"Military truck, 2 axle, wheeled",,"Rearms ground units of same coaltion, 85 km/h on road,",KAMAZ Truck: Russian military truck used for various logistical purposes.
|
||||
KDO_Mod40,no,Ground Unit,AAA,AAA Kdo.G.40,AAA Kdo.G.40,,,yes,no,30000,0,,,,AAA Kdo.G.40: German mobile anti-aircraft command vehicle.
|
||||
KrAZ6322,yes,Ground Unit,Unarmed,Truck KrAZ-6322 6x6,Truck KrAZ-6322 6x6,,,no,yes,0,0,,,,Truck KrAZ-6322 6x6: Ukrainian military truck used for various logistical purposes.
|
||||
KS-19,yes,Ground Unit,AAA,AAA KS-19 100mm,AAA KS-19 100mm,,,yes,no,0,20000,,,,AAA KS-19 100mm: Soviet towed anti-aircraft gun with a 100mm caliber.
|
||||
Kub 1S91 str,yes,Ground Unit,SAM Search/Track Radar,SA-6 Straight flush,Kub 1S91 str,red,Mid Cold War,no,no,70000,0,"SA-6/Kub search and track Radar, tracked.",,"75km detection, 28km tracking, 44 km/h on road",SA-6 Straight flush: Soviet mobile surface-to-air missile system with Radar guidance.
|
||||
Kub 2P25 ln,yes,Ground Unit,SAM Launcher,SA-6 Launcher,Kub 2P25 ln,red,Late Cold War,no,no,0,25000,SA-6/Kub launcher. 3 missiles. Tracked. Needs rest of site to function,,"24km range 14km altitude, 44 km/h on road",SA-6 Launcher: Mobile launcher for the SA-6 Straight flush surface-to-air missile system.
|
||||
Kubelwagen_82,yes,Ground Unit,Unarmed,LUV Kubelwagen Jeep,LUV Kubelwagen Jeep,,,no,no,0,0,,,,LUV Kubelwagen Jeep: German military vehicle used for reconnaissance.
|
||||
Land_Rover_101_FC,yes,Ground Unit,Unarmed,Truck Land Rover 101 FC,Truck Land Rover 101 FC,,,no,no,0,0,,,,Truck Land Rover 101 FC: Military truck based on the Land Rover platform.
|
||||
Land_Rover_109_S3,yes,Ground Unit,Unarmed,LUV Land Rover 109,LUV Land Rover 109,,,no,no,0,0,,,,LUV Land Rover 109: Light utility vehicle based on the Land Rover platform.
|
||||
LARC-V,yes,Ground Unit,Unarmed,LARC-V,LARC-V,,,no,no,500,0,,,,"LARC-V: Lighter Amphibious Resupply Cargo, amphibious cargo vehicle."
|
||||
LAV-25,yes,Ground Unit,Infantry Fighting Vehicle,LAV-25,LAV-25,blue,Late Cold War,yes,no,0,2500,Infantry fighter vehicle. Wheeled. Amphibious,,"25mm gun, 7,62 gun, 100 km/h road, 9,6 km/h water",LAV-25: Light armored vehicle used for reconnaissance and security missions.
|
||||
LAZ Bus,yes,Ground Unit,Unarmed,LAZ Bus,LAZ Bus,red,Early Cold War,no,no,0,0,Civilian bus. Single Axle. Wheeled,,80 km/h road,LAZ Bus: Military transport bus used for troop movement.
|
||||
Leclerc,yes,Ground Unit,Tank,Leclerc,Leclerc,blue,Modern,yes,no,0,3500,Main battle tank. Tracked. Modern and heavily armoured.,,"120mm gun, 12,7mm gun, 72 km/h road",Leclerc: French main battle tank known for its advanced features and firepower.
|
||||
LeFH_18-40-105,no,Ground Unit,Artillery,FH LeFH-18 105mm,FH LeFH-18 105mm,,,yes,no,0,10500,,,,FH LeFH-18 105mm: German towed field howitzer with a 105mm caliber.
|
||||
Leopard-2,yes,Ground Unit,Tank,Leopard-2,Leopard-2,blue,Late Cold War,yes,no,0,3500,Main battle tank. Tracked. Modern and heavily armoured.,,"120mm gun, 7,62 mm gun x 2, 72 km/h road",Leopard-2: German main battle tank recognized for its high level of protection and mobility.
|
||||
leopard-2A4,yes,Ground Unit,Tank,Tank Leopard-2A4,Tank Leopard-2A4,,,yes,no,0,3500,,,,Tank Leopard-2A4: Earlier version of the German Leopard 2 main battle tank.
|
||||
leopard-2A4_trs,yes,Ground Unit,Tank,Tank Leopard-2A4 Trs,Tank Leopard-2A4 Trs,,,yes,no,0,3500,,,,Tank Leopard-2A4 Trs: Leopard 2 main battle tank with additional armor.
|
||||
Leopard-2A5,yes,Ground Unit,Tank,Tank Leopard-2A5,Tank Leopard-2A5,,,yes,no,0,3500,,,,Tank Leopard-2A5: Upgraded version of the German Leopard 2 main battle tank.
|
||||
Leopard1A3,yes,Ground Unit,Tank,Leopard1A3,Leopard1A3,blue,Mid Cold War,yes,no,0,2500,Main battle tank. Tracked. Heavily armoured.,,"105mm gun, 7,62 mm gun x 2, 65 km/h road",Leopard1A3: Earlier version of the German Leopard main battle tank series.
|
||||
LiAZ Bus,yes,Ground Unit,Unarmed,Bus LiAZ-677,Bus LiAZ-677,,,no,no,0,0,,,,Bus LiAZ-677: Military transport bus used for troop movement.
|
||||
Locomotive,yes,Ground Unit,Locomotive,Loco CHME3T,Loco CHME3T,,,no,no,0,0,,,,Loco CHME3T: Diesel-electric shunting locomotive.
|
||||
M 818,yes,Ground Unit,Unarmed,M 818,M 818,blue,Early Cold War,no,yes,0,0,???,,,M 818: Military truck used for various logistical purposes.
|
||||
M-1 Abrams,yes,Ground Unit,Tank,M-1 Abrams,M-1 Abrams,blue,Late Cold War,yes,no,0,3500,Main battle tank. Tracked. Modern and heavily armoured.,,"120mm gun, 12,7mm gun, 7,62 mm gun x 2, 66,7 km/h road","M-1 Abrams: Iconic U.S. main battle tank, known for its firepower and armor."
|
||||
M-109,yes,Ground Unit,Artillery,M-109 Paladin,M-109,blue,Early Cold War,yes,no,0,22000,???,,,M-109 Paladin: Self-propelled howitzer used for artillery support.
|
||||
M-113,yes,Ground Unit,Armoured Personnel Carrier,M-113,M-113,blue,Early Cold War,yes,no,0,1200,Armoured personnel carrier. Tracked. Amphibious,,"12,7mm gun, 60,7 km/h road, 5,8 km/h water",M-113: Armored personnel carrier used by various armed forces.
|
||||
M-2 Bradley,yes,Ground Unit,Infantry Fighting Vehicle,M-2A2 Bradley,M-2 Bradley,blue,Late Cold War,yes,no,0,3800,Infantry fighting vehicle. Tracked.,,"ATGM, 100mm gun, 25mm gun, 7,62 gun, 66 km/h road",M-2A2 Bradley: Infantry fighting vehicle used by the U.S. Army.
|
||||
M-60,yes,Ground Unit,Tank,M-60,M-60,blue,Early Cold War,yes,no,0,8000,Main battle tank. Tracked. Heavily armoured.,,"105mm gun, 12,7mm gun, 7,62 mm gun, 48 km/h road, 19 km/h off,",M-60: Main battle tank used by the United States and other countries.
|
||||
M1_37mm,no,Ground Unit,AAA,AAA M1 37mm,AAA M1 37mm,,,yes,no,0,5700,,,,AAA M1 37mm: American towed anti-aircraft gun with a 37mm caliber.
|
||||
M10_GMC,no,Ground Unit,Tank,Self Propelled Gun M10 GMC TD,Self Propelled Gun M10 GMC TD,,,yes,no,0,6000,,,,Self Propelled Gun M10 GMC TD: American tank destroyer based on the M4 Sherman chassis.
|
||||
M1043 HMMWV Armament,yes,Ground Unit,Armoured Car,HMMWV M2 Browning,HMMWV M2,blue,Late Cold War,yes,no,0,1200,"Military car, single axle, wheeled",,"12,7mm gun, 113 km/h road",HMMWV M2 Browning: Humvee variant equipped with a heavy machine gun for firepower.
|
||||
M1045 HMMWV TOW,yes,Ground Unit,Armoured Car,HMMWV TOW,HMMWV TOW,red,Late Cold War,yes,no,0,3800,"Military car, single axle, wheeled",,"ATGM, 113 km/h road",HMMWV TOW: Humvee variant equipped with TOW anti-tank missiles.
|
||||
M1097 Avenger,yes,Ground Unit,SAM,M1097 Avenger,M1097 Avenger,blue,Modern,yes,no,5200,4500,"Military car, single axle, wheeled",,"Stinger SAM, 12,7mm gun, 113 km/h road",M1097 Avenger: Mobile air defense system based on the HMMWV platform.
|
||||
M1126 Stryker ICV,yes,Ground Unit,Armoured Personnel Carrier,Stryker MG,Stryker MG,blue,Modern,yes,no,0,1200,Armoured personnel carrier. Wheeled.,,"12,7mm gun, 96 km/h road",Stryker MG: Infantry carrier vehicle with a mounted machine gun for support.
|
||||
M1128 Stryker MGS,yes,Ground Unit,Self Propelled Gun,M1128 Stryker MGS,M1128 Stryker MGS,blue,Modern,yes,no,0,4000,Self propelled gun. Wheeled.,,"105mm gun, 7,62mm gun, 96 km/h road",M1128 Stryker MGS: Mobile gun system variant of the Stryker used for fire support.
|
||||
M1134 Stryker ATGM,yes,Ground Unit,Armoured Personnel Carrier,Stryker ATGM,Stryker ATGM,blue,Modern,yes,no,0,3800,Armoured personnel carrier. Wheeled.,,"ATGM, 12,7mm gun, 96 km/h road",Stryker ATGM: Stryker variant equipped with anti-tank guided missiles.
|
||||
M12_GMC,no,Ground Unit,Artillery,SPH M12 GMC 155mm,SPH M12 GMC 155mm,,,yes,no,0,18300,,,,SPH M12 GMC 155mm: American self-propelled howitzer with a 155mm gun.
|
||||
M2A1_halftrack,yes,Ground Unit,Armoured Personnel Carrier,Armoured Personnel Carrier M2A1 Halftrack,Armoured Personnel Carrier M2A1 Halftrack,,,yes,no,0,1200,,,,Armoured Personnel Carrier M2A1 Halftrack: Armored personnel carrier with both tracks and wheels.
|
||||
M2A1-105,no,Ground Unit,Artillery,FH M2A1 105mm,FH M2A1 105mm,,,yes,no,0,11500,,,,FH M2A1 105mm: American towed field howitzer with a 105mm caliber.
|
||||
M30_CC,no,Ground Unit,Unarmed,Ammo M30 Cargo Carrier,Ammo M30 Cargo Carrier,,,no,no,0,1200,,,,Ammo M30 Cargo Carrier: Ammunition carrier used for transporting artillery ammunition.
|
||||
M4_Sherman,yes,Ground Unit,Tank,Tk M4 Sherman,Tk M4 Sherman,,,yes,no,0,3000,,,,Tk M4 Sherman: American medium tank used during World War II.
|
||||
M4_Tractor,no,Ground Unit,Unarmed,Tractor M4 High Speed,Tractor M4 High Speed,,,no,no,0,1200,,,,Tractor M4 High Speed: American high-speed tractor used for towing artillery.
|
||||
M45_Quadmount,no,Ground Unit,AAA,AAA M45 Quadmount HB 12.7mm,AAA M45 Quadmount HB 12.7mm,,,yes,no,0,1500,,,,AAA M45 Quadmount HB 12.7mm: American anti-aircraft weapon with four 12.7mm machine guns.
|
||||
M48 Chaparral,yes,Ground Unit,SAM,M48 Chaparral,M48 Chaparral,blue,Late Cold War,no,no,10000,8500,,,,M48 Chaparral: Surface-to-air missile system mounted on a tracked vehicle.
|
||||
M4A4_Sherman_FF,no,Ground Unit,Tank,Tk M4A4 Sherman Firefly,Tk M4A4 Sherman Firefly,,,yes,no,0,3000,,,,Tk M4A4 Sherman Firefly: British modified version of the M4 Sherman tank with a 17-pounder gun.
|
||||
M6 Linebacker,yes,Ground Unit,SAM,M6 Linebacker,M6 Linebacker,blue,Late Cold War,no,no,8000,4500,,,,M6 Linebacker: Anti-aircraft variant of the M2 Bradley infantry fighting vehicle.
|
||||
M8_Greyhound,no,Ground Unit,Armoured Car,Scout M8 Greyhound AC,Scout M8 Greyhound AC,,,yes,no,0,2000,,,,Scout M8 Greyhound AC: American armored car used for reconnaissance.
|
||||
M978 HEMTT Tanker,yes,Ground Unit,Unarmed,M978 HEMTT Tanker,M978 HEMTT Tanker,blue,Mid Cold War,no,no,0,0,,,,M978 HEMTT Tanker: Heavy Expanded Mobility Tactical Truck used for fuel transportation.
|
||||
Marder,yes,Ground Unit,Infantry Fighting Vehicle,Marder,Marder,blue,Late Cold War,yes,no,0,1500,,,,Marder: German infantry fighting vehicle used by the German Army.
|
||||
Maschinensatz_33,no,Ground Unit,AAA,Maschinensatz 33 Gen,Maschinensatz 33 Gen,,,yes,no,0,0,,,,Maschinensatz 33 Gen: German power generator.
|
||||
MAZ-6303,yes,Ground Unit,Unarmed,MAZ-6303,MAZ-6303,red,Mid Cold War,no,no,0,0,,,,MAZ-6303: Soviet/Russian military truck used for various logistical purposes.
|
||||
MCV-80,yes,Ground Unit,Infantry Fighting Vehicle,Warrior Infantry Fighting Vehicle,Warrior,blue,Late Cold War,yes,no,0,2500,,,,Warrior Infantry Fighting Vehicle: British infantry fighting vehicle known for its versatility and protection.
|
||||
Merkava_Mk4,yes,Ground Unit,Tank,Tank Merkava IV,Tank Merkava IV,,,yes,no,0,3500,,,,Tank Merkava IV: Israeli main battle tank known for its advanced features and protection.
|
||||
MLRS,yes,Ground Unit,Rocket Artillery,M270,M270,blue,Late Cold War,yes,no,0,32000,,,,M270: Multiple rocket launcher system capable of launching various types of rockets.
|
||||
MLRS FDDM,yes,Ground Unit,Artillery,MRLS FDDM (FC),MRLS FDDM (FC),,,yes,no,0,1200,,,,MRLS FDDM (FC): Multiple rocket launcher system with fire direction and control module.
|
||||
MTLB,yes,Ground Unit,Armoured Personnel Carrier,MT-LB,MT-LB,red,Mid Cold War,yes,no,0,1000,,,,MT-LB: Soviet multi-purpose tracked vehicle used for troop transport and logistics.
|
||||
NASAMS_Command_Post,yes,Ground Unit,SAM Support vehicle,SAM NASAMS C2,SAM NASAMS C2,,,no,no,0,0,,,,SAM NASAMS C2: Command and control system for the NASAMS surface-to-air missile system.
|
||||
NASAMS_LN_B,yes,Ground Unit,SAM Launcher,SAM NASAMS LN AIM-120B,SAM NASAMS LN AIM-120B,,,no,no,0,15000,,,,SAM NASAMS LN AIM-120B: Launcher unit for the NASAMS surface-to-air missile system with AIM-120B missiles.
|
||||
NASAMS_LN_C,yes,Ground Unit,SAM Launcher,SAM NASAMS LN AIM-120C,SAM NASAMS LN AIM-120C,,,no,no,0,15000,,,,SAM NASAMS LN AIM-120C: Launcher unit for the NASAMS surface-to-air missile system with AIM-120C missiles.
|
||||
NASAMS_radar_MPQ64F1,yes,Ground Unit,SAM Search Radar,SAM NASAMS SR MPQ64F1,SAM NASAMS SR MPQ64F1,,,no,no,50000,0,,,,SAM NASAMS SR MPQ64F1: Surface-to-air missile system with Radar guidance.
|
||||
Osa 9A33 ln,yes,Ground Unit,SAM Launcher,SA-8 Launcher,Osa 9A33 ln,red,Mid Cold War,no,no,30000,10300,,,,SA-8 Launcher: Mobile launcher for the SA-8 Gecko surface-to-air missile system.
|
||||
outpost,yes,Ground Unit,Structure,outpost,outpost,,,no,no,0,800,,,,outpost: Military outpost for surveillance and control.
|
||||
outpost_road,yes,Ground Unit,Structure,outpost_road,outpost_road,,,no,no,0,800,,,,outpost_road: Military outpost with a roadblock.
|
||||
p-19 s-125 sr,yes,Ground Unit,SAM Search Radar,SA-3 Flat Face B,Flat Face B,red,Mid Cold War,no,no,160000,0,,,,SA-3 Flat Face B: Mobile Radar system used in conjunction with the SA-3 Goa air defense system.
|
||||
Pak40,no,Ground Unit,Artillery,FH Pak 40 75mm,FH Pak 40 75mm,,,yes,no,0,3000,,,,FH Pak 40 75mm: German towed anti-tank gun with a 75mm caliber.
|
||||
Paratrooper AKS-74,yes,Ground Unit,Infantry,Paratrooper AKS-74,Paratrooper AKS-74,red,Modern,yes,no,0,500,,,,Paratrooper AKS-74: Modified version of the AK-74 for use by airborne forces.
|
||||
Paratrooper RPG-16,yes,Ground Unit,Infantry,Paratrooper RPG-16,Paratrooper RPG-16,red,Modern,yes,no,0,500,,,,Paratrooper RPG-16: Anti-tank rocket launcher used by airborne forces.
|
||||
Patriot AMG,yes,Ground Unit,SAM Support vehicle,Patriot Antenna Mast Group,Patriot AMG,blue,Modern,no,no,0,0,,,,"Patriot Antenna Mast Group: Part of the Patriot missile system, used for communication."
|
||||
Patriot cp,yes,Ground Unit,SAM Support vehicle,Patriot Command Post,Patriot cp,blue,Late Cold War,no,no,0,0,,,,Patriot Command Post: Mobile command post for the Patriot missile system.
|
||||
Patriot ECS,yes,Ground Unit,SAM Support vehicle,Patriot Engagement Control Station,Patriot ECS,blue,Modern,no,no,0,0,,,,Patriot Engagement Control Station: Command and control center for the Patriot missile system.
|
||||
Patriot EPP,yes,Ground Unit,SAM Support vehicle,Patriot Electric Power Plant,Patriot EPP,blue,Late Cold War,no,no,0,0,,,,Patriot Electric Power Plant: Power generation unit for the Patriot missile system.
|
||||
Patriot ln,yes,Ground Unit,SAM Launcher,Patriot Launcher,Patriot ln,blue,Late Cold War,no,no,0,100000,,,,Patriot Launcher: Mobile launcher for the Patriot surface-to-air missile system.
|
||||
Patriot site,yes,Ground Unit,SAM Site,Patriot site,Patriot site,blue,Late Cold War,no,no,160000,0,,,,Patriot site: Operational site for the Patriot missile system.
|
||||
Patriot str,yes,Ground Unit,SAM Search/Track Radar,Patriot Search/Track Radar,Patriot str,blue,Late Cold War,no,no,160000,0,,,,Patriot Search/Track Radar: Radar system used for target tracking in the Patriot missile system.
|
||||
PLZ05,yes,Ground Unit,Artillery,PLZ-05,PLZ-05,,,yes,no,0,23500,,,,PLZ-05: Chinese self-propelled howitzer with a 155mm gun.
|
||||
Predator GCS,yes,Ground Unit,Unarmed,Predator GCS,Predator GCS,blue,Late Cold War,no,no,0,0,,,,Predator GCS: Ground Control Station for the MQ-1 Predator unmanned aerial vehicle.
|
||||
Predator TrojanSpirit,yes,Ground Unit,Unarmed,Predator TrojanSpirit,Predator TrojanSpirit,blue,Late Cold War,no,no,0,0,,,,Predator TrojanSpirit: Electronic warfare system used for intelligence and reconnaissance.
|
||||
PT_76,yes,Ground Unit,Tank,LT PT-76,LT PT-76,,,yes,no,0,2000,,,,LT PT-76: Soviet amphibious light tank used for reconnaissance.
|
||||
Pz_IV_H,yes,Ground Unit,Tank,Tk PzIV H,Tk PzIV H,,,yes,no,0,3000,,,,Tk PzIV H: German medium tank used during World War II.
|
||||
Pz_V_Panther_G,no,Ground Unit,Tank,Tk Panther G (Pz V),Tk Panther G (Pz V),,,yes,no,0,3000,,,,Tk Panther G (Pz V): German medium tank used during World War II.
|
||||
QF_37_AA,no,Ground Unit,AAA,"AAA QF 3.7""","AAA QF 3.7""",,,yes,no,0,9000,,,,"AAA QF 3.7"": British anti-aircraft gun with a 3.7-inch caliber."
|
||||
rapier_fsa_blindfire_radar,yes,Ground Unit,SAM Track Radar,SAM Rapier Blindfire TR,SAM Rapier Blindfire TR,,,no,no,30000,0,,,,SAM Rapier Blindfire TR: Vehicle used for target acquisition in the Rapier air defense system.
|
||||
rapier_fsa_launcher,yes,Ground Unit,SAM Launcher,SAM Rapier LN,SAM Rapier LN,,,no,no,30000,6800,,,,SAM Rapier LN: Self-propelled anti-aircraft missile system with Radar guidance.
|
||||
rapier_fsa_optical_tracker_unit,yes,Ground Unit,SAM Track Radar,SAM Rapier Tracker,SAM Rapier Tracker,,,no,no,20000,0,,,,SAM Rapier Tracker: Vehicle used for tracking targets in the Rapier air defense system.
|
||||
RD_75,yes,Ground Unit,EW Radar,SAM SA-2 S-75 RD-75 Amazonka RF,SAM SA-2 S-75 RD-75 Amazonka RF,,,no,no,100000,0,,,,SAM SA-2 S-75 RD-75 Amazonka RF: Soviet surface-to-air missile system with Amazonka RF Radar.
|
||||
RLS_19J6,yes,Ground Unit,SAM Search Radar,SA-5 Thin Shield,RLS 19J6,Red,Mid Cold War,no,no,150000,0,,,,SA-5 Thin Shield: Soviet long-range surface-to-air missile system.
|
||||
Roland ADS,yes,Ground Unit,SAM,Roland ADS,Roland ADS,blue,Late Cold War,no,no,12000,8000,,,,Roland ADS: Mobile short-range air defense system.
|
||||
Roland radar,yes,Ground Unit,SAM Search Radar,Roland Search Radar,Roland Radar,blue,Mid Cold War,no,no,35000,0,,,,Roland Search Radar: Radar system used in conjunction with the Roland air defense system.
|
||||
RPC_5N62V,yes,Ground Unit,SAM Track Radar,SA-5 Square Pair,RPC 5N62V,Red,Mid Cold War,no,no,400000,0,,,,SA-5 Square Pair: Mobile launcher for the SA-5 Thin Shield surface-to-air missile system.
|
||||
S_75_ZIL,yes,Ground Unit,Unarmed,S-75 Tractor (ZIL-131),S-75 Tractor (ZIL-131),,,no,no,0,0,,,,S-75 Tractor (ZIL-131): Tractor used for transporting components of the S-75 surface-to-air missile system.
|
||||
S_75M_Volhov,yes,Ground Unit,SAM Launcher,SA-2 Launcher,S75M Volhov,Red,Early Cold War,no,no,0,43000,,,,SA-2 Launcher: Mobile launcher for the SA-2 Guideline surface-to-air missile system.
|
||||
S-200_Launcher,yes,Ground Unit,SAM Launcher,SA-5 Launcher,S-200 Launcher,Red,Mid Cold War,no,no,0,255000,,,,SA-5 Launcher: Mobile launcher for the SA-5 Thin Shield surface-to-air missile system.
|
||||
S-300PS 40B6M tr,yes,Ground Unit,SAM Track Radar,SA-10 Tin Shield,S-300PS 40B6M tr,red,Late Cold War,no,no,160000,0,,,,SA-10 Tin Shield: Radar system used in conjunction with the SA-10 Grumble air defense system.
|
||||
S-300PS 40B6MD sr,yes,Ground Unit,SAM Search Radar,SA-10 Clam Shell,S-300PS 40B6MD sr,red,Late Cold War,no,no,60000,0,,,,SA-10 Clam Shell: Mobile launcher for the SA-10 Grumble surface-to-air missile system.
|
||||
S-300PS 54K6 cp,yes,Ground Unit,SAM Support vehicle,SA-10 Command Post,S-300PS 54K6 cp,red,Late Cold War,no,no,0,0,,,,SA-10 Command Post: Command and control center for the SA-10 Grumble air defense system.
|
||||
S-300PS 5P85C ln,yes,Ground Unit,SAM Launcher,SA-10 Launcher (5P85C),S-300PS 5P85C ln,red,Late Cold War,no,no,0,120000,,,,SA-10 Launcher (5P85C): Mobile launcher for the SA-10 Grumble surface-to-air missile system.
|
||||
S-300PS 5P85D ln,yes,Ground Unit,SAM Launcher,SA-10 Launcher (5P85D),S-300PS 5P85D ln,red,Late Cold War,no,no,0,120000,,,,SA-10 Launcher (5P85D): Mobile launcher for the SA-10 Grumble surface-to-air missile system.
|
||||
S-300PS 64H6E sr,yes,Ground Unit,SAM Search Radar,SA-10 Big Bird,S-300PS 64H6E sr,red,Late Cold War,no,no,160000,0,,,,SA-10 Big Bird: Early warning Radar system used in conjunction with the SA-10 Grumble system.
|
||||
S-60_Type59_Artillery,yes,Ground Unit,AAA,AAA S-60 57mm,AAA S-60 57mm,,,yes,no,5000,6000,,,,AAA S-60 57mm: Soviet towed anti-aircraft gun with a 57mm caliber.
|
||||
SA-10 SAM Battery,yes,Ground Unit,SAM Site,SA-10 SAM Battery,SA-10 SAM Battery,red,Late Cold War,no,no,,,,,,SA-10 SAM Battery: Operational site for the SA-10 Grumble air defense system.
|
||||
SA-11 Buk CC 9S470M1,yes,Ground Unit,SAM Support vehicle,SA-11 Command Post,SA-11 Buk CC 9S470M1,red,Late Cold War,no,no,0,0,,,,SA-11 Command Post: Command and control center for the SA-11 Gadfly air defense system.
|
||||
SA-11 Buk LN 9A310M1,yes,Ground Unit,SAM Launcher,SA-11 Launcher,SA-11 Buk LN 9A310M1,red,Late Cold War,no,no,50000,35000,,,,SA-11 Launcher: Mobile launcher for the SA-11 Gadfly surface-to-air missile system.
|
||||
SA-11 Buk SR 9S18M1,yes,Ground Unit,SAM Search Radar,SA-11 Snown Drift,SA-11 Buk SR 9S18M1,red,Mid Cold War,no,no,100000,0,,,,SA-11 Snown Drift: Early warning Radar system used in conjunction with the SA-11 Gadfly system.
|
||||
SA-11 SAM Battery,yes,Ground Unit,SAM Site,SA-11 SAM Battery,SA-11 SAM Battery,red,Late Cold War,no,no,,,,,,SA-11 SAM Battery: Operational site for the SA-11 Gadfly air defense system.
|
||||
SA-18 Igla comm,yes,Ground Unit,MANPADS,"MANPADS SA-18 Igla ""Grouse"" C2","MANPADS SA-18 Igla ""Grouse"" C2",,,no,no,5000,0,,,,"MANPADS SA-18 Igla ""Grouse"" C2: Portable, man-portable air defense system with command and control."
|
||||
SA-18 Igla manpad,yes,Ground Unit,MANPADS,SA-18 Igla manpad,SA-18 Igla manpad,red,Late Cold War,no,no,5000,5200,,,,"SA-18 Igla manpad: Portable, man-portable air defense system designed for infantry use."
|
||||
SA-18 Igla-S comm,yes,Ground Unit,MANPADS,"MANPADS SA-18 Igla-S ""Grouse"" C2","MANPADS SA-18 Igla-S ""Grouse"" C2",,,no,no,5000,0,,,,"MANPADS SA-18 Igla-S ""Grouse"" C2: Upgraded version of the SA-18 Igla manpad with command and control."
|
||||
SA-18 Igla-S manpad,yes,Ground Unit,MANPADS,SA-18 Igla-S manpad,SA-18 Igla-S manpad,red,Late Cold War,no,no,5000,5200,,,,SA-18 Igla-S manpad: Upgraded version of the SA-18 Igla manpad with improved capabilities.
|
||||
SA-2 SAM Battery,yes,Ground Unit,SAM Site,SA-2 SAM Battery,SA-2 SAM Battery,red,Early Cold War,no,no,,,,,,SA-2 SAM Battery: Operational site for the SA-2 Guideline surface-to-air missile system.
|
||||
SA-3 SAM Battery,yes,Ground Unit,SAM Site,SA-3 SAM Battery,SA-3 SAM Battery,red,Early Cold War,no,no,,,,,,SA-3 SAM Battery: Operational site for the SA-3 Goa surface-to-air missile system.
|
||||
SA-5 SAM Battery,yes,Ground Unit,SAM Site,SA-5 SAM Battery,SA-5 SAM Battery,Red,Mid Cold War,no,no,,,,,,SA-5 SAM Battery: Operational site for the SA-5 Gammon surface-to-air missile system.
|
||||
SA-6 SAM Battery,yes,Ground Unit,SAM Site,SA-6 SAM Battery,SA-6 SAM Battery,red,Mid Cold War,no,no,,,"2K12 Kub. Tracked self propelled straight fush Radars, and TELs. 3 missiles per TEL.",,"Can move, Semi Active Radar guided, 22nm/26,000ft",SA-6 SAM Battery: Operational site for the SA-6 Gainful surface-to-air missile system.
|
||||
Sandbox,yes,Ground Unit,Structure,Sandbox,Sandbox,,,no,no,0,800,,,,Sandbox: Mobile Radar system used for tracking and fire control.
|
||||
SAU 2-C9,yes,Ground Unit,Artillery,SAU Nona,SAU Nona,red,Mid Cold War,yes,no,0,7000,,,,SAU Nona: Self-propelled artillery system featuring a 120mm smoothbore mortar.
|
||||
SAU Akatsia,yes,Ground Unit,Artillery,SAU Akatsia,SAU Akatsia,red,Mid Cold War,yes,no,0,17000,,,,SAU Akatsia: Soviet self-propelled howitzer with a 152mm gun.
|
||||
SAU Gvozdika,yes,Ground Unit,Artillery,SAU Gvozdika,SAU Gvozdika,red,Mid Cold War,yes,no,0,15000,,,,SAU Gvozdika: Self-propelled artillery system with a 122mm gun.
|
||||
SAU Msta,yes,Ground Unit,Artillery,SAU Msta,SAU Msta,red,Late Cold War,yes,no,0,23500,,,,SAU Msta: Russian self-propelled howitzer featuring a 152mm gun.
|
||||
Scud_B,yes,Ground Unit,Missile system,SSM SS-1C Scud-B,SSM SS-1C Scud-B,,,yes,no,0,320000,,,,SSM SS-1C Scud-B: Tactical ballistic missile system used for ground attack.
|
||||
Sd_Kfz_2,yes,Ground Unit,Unarmed,LUV Kettenrad,LUV Kettenrad,,,no,no,0,0,,,,LUV Kettenrad: German tracked motorcycle used for reconnaissance.
|
||||
Sd_Kfz_234_2_Puma,no,Ground Unit,Armoured Car,Scout Puma AC,Scout Puma AC,,,yes,no,0,2000,,,,Scout Puma AC: German armored reconnaissance vehicle.
|
||||
Sd_Kfz_251,yes,Ground Unit,Armoured Personnel Carrier,Armoured Personnel Carrier Sd.Kfz.251 Halftrack,Armoured Personnel Carrier Sd.Kfz.251 Halftrack,,,yes,no,0,1100,,,,Armoured Personnel Carrier Sd.Kfz.251 Halftrack: German armored personnel carrier with tracks and wheels.
|
||||
Sd_Kfz_7,yes,Ground Unit,Unarmed,Tractor Sd.Kfz.7 Art'y Tractor,Tractor Sd.Kfz.7 Art'y Tractor,,,no,no,0,0,,,,Tractor Sd.Kfz.7 Art'y Tractor: German half-track used for towing artillery.
|
||||
Self Propelled GunH_Dana,yes,Ground Unit,Artillery,SPH Dana vz77 152mm,SPH Dana vz77 152mm,,,yes,no,0,18700,,,,SPH Dana vz77 152mm: Self-propelled howitzer with a 152mm gun used by the Czech military.
|
||||
Silkworm_SR,yes,Ground Unit,Missile system,AShM Silkworm SR,AShM Silkworm SR,,,yes,no,200000,0,,,,AShM Silkworm SR: Mobile launcher for the SS-N-2 Silkworm anti-ship missile.
|
||||
SK_C_28_naval_gun,no,Ground Unit,Artillery,Gun 15cm SK C/28 Naval in Bunker,Gun 15cm SK C/28 Naval in Bunker,,,no,no,0,20000,,,,Gun 15cm SK C/28 Naval in Bunker: Naval gun emplaced in a bunker for coastal defense.
|
||||
SKP-11,yes,Ground Unit,Unarmed,SKP-11,SKP-11,red,Early Cold War,no,no,0,0,,,,SKP-11: Mobile Radar system used for target acquisition and tracking.
|
||||
Smerch,yes,Ground Unit,Rocket Artillery,Smerch,Smerch,red,Late Cold War,yes,no,0,70000,,,,Smerch: Multiple rocket launcher system capable of launching large-caliber rockets.
|
||||
Smerch_HE,yes,Ground Unit,Artillery,MLRS 9A52 Smerch HE 300mm,MLRS 9A52 Smerch HE 300mm,,,yes,no,0,70000,,,,MLRS 9A52 Smerch HE 300mm: Heavy multiple rocket launcher system with a 300mm caliber.
|
||||
snr s-125 tr,yes,Ground Unit,SAM Track Radar,SA-3 Low Blow,snr s-125 tr,red,Early Cold War,no,no,100000,0,,,,SA-3 Low Blow: Mobile launcher for the SA-3 Goa surface-to-air missile system.
|
||||
SNR_75V,yes,Ground Unit,SAM Track Radar,SA-2 Fan Song,SNR 75V,Red,Early Cold War,no,no,100000,0,,,,SA-2 Fan Song: Radar system used in conjunction with the SA-2 Guideline surface-to-air missile system.
|
||||
Soldier AK,yes,Ground Unit,Infantry,Soldier AK,Soldier AK,red,Early Cold War,yes,no,0,500,,,,"Soldier AK: Standard issue assault rifle for infantry, known for its reliability and firepower."
|
||||
Soldier M249,yes,Ground Unit,Infantry,Soldier M249,Soldier M249,blue,Late Cold War,yes,no,0,700,,,,Soldier M249: Light machine gun used by infantry for sustained firepower.
|
||||
Soldier M4,yes,Ground Unit,Infantry,Soldier M4,Soldier M4,blue,Mid Cold War,yes,no,0,500,,,,Soldier M4: Standard issue carbine used by infantry.
|
||||
Soldier M4 GRG,yes,Ground Unit,Infantry,Soldier M4 GRG,Soldier M4 GRG,blue,Mid Cold War,yes,no,0,500,,,,"Soldier M4 GRG: Grenadier variant of the M4 carbine, equipped for grenade launching."
|
||||
Soldier RPG,yes,Ground Unit,Infantry,Soldier RPG,Soldier RPG,red,Mid Cold War,yes,no,0,500,,,,Soldier RPG: Portable rocket launcher used for anti-armor purposes.
|
||||
Soldier stinger,yes,Ground Unit,MANPADS,MANPADS Stinger,MANPADS Stinger,,,no,no,5000,4500,,,,"MANPADS Stinger: Portable, man-portable air defense system designed for infantry use."
|
||||
soldier_mauser98,no,Ground Unit,Infantry,Infantry Mauser 98,Infantry Mauser 98,,,yes,no,0,500,,,,Infantry Mauser 98: German bolt-action rifle used during World War II.
|
||||
soldier_wwii_br_01,no,Ground Unit,Infantry,Infantry SMLE No.4 Mk-1,Infantry SMLE No.4 Mk-1,,,yes,no,0,500,,,,Infantry SMLE No.4 Mk-1: British bolt-action rifle used during World War II.
|
||||
soldier_wwii_us,no,Ground Unit,Infantry,Infantry M1 Garand,Infantry M1 Garand,,,yes,no,0,500,,,,Infantry M1 Garand: Standard issue semi-automatic rifle used by the U.S. military.
|
||||
SON_9,yes,Ground Unit,AAA,AAA Fire Can SON-9,AAA Fire Can SON-9,,,yes,no,55000,0,,,,AAA Fire Can SON-9: Mobile anti-aircraft Radar system.
|
||||
Stinger comm,yes,Ground Unit,MANPADS,Stinger comm,Stinger comm,blue,Late Cold War,no,no,5000,0,,,,Stinger comm: Mobile communication system used in conjunction with Stinger air defense.
|
||||
Stinger comm dsr,yes,Ground Unit,MANPADS,Stinger comm dsr,Stinger comm dsr,red,Late Cold War,no,no,5000,0,,,,Stinger comm dsr: Ground-based air defense system with a MANPADS launcher.
|
||||
Strela-1 9P31,yes,Ground Unit,SAM,SA-9 Strela-1 9P31,Strela-1 9P31,red,Late Cold War,no,no,5000,4200,,,,SA-9 Strela-1 9P31: Mobile short-range air defense system with infrared homing missiles.
|
||||
Strela-10M3,yes,Ground Unit,SAM,SA-13 Strela-10M3,Strela-10M3,red,Late Cold War,no,no,8000,5000,,,,SA-13 Strela-10M3: Mobile short-range air defense system with infrared homing missiles.
|
||||
Stug_III,no,Ground Unit,Tank,Self Propelled Gun StuG III G AG,Self Propelled Gun StuG III G AG,,,yes,no,0,3000,,,,Self Propelled Gun StuG III G AG: German assault gun based on the Sturmgeschütz III chassis.
|
||||
Stug_IV,no,Ground Unit,Tank,Self Propelled Gun StuG IV AG,Self Propelled Gun StuG IV AG,,,yes,no,0,3000,,,,Self Propelled Gun StuG IV AG: German assault gun based on the Panzer IV chassis.
|
||||
SturmPzIV,no,Ground Unit,Tank,Self Propelled Gun Brummbaer AG,Self Propelled Gun Brummbaer AG,,,yes,no,0,4500,,,,Self Propelled Gun Brummbaer AG: German assault gun with a 150mm gun.
|
||||
Suidae,yes,Ground Unit,Unarmed,Suidae,Suidae,,Modern,no,no,0,0,,,,Suidae: Chinese 6x6 wheeled armored personnel carrier.
|
||||
T-55,yes,Ground Unit,Tank,T-55,T-55,red,Early Cold War,yes,no,0,2500,,,,"T-55: Soviet main battle tank with a 100mm gun, widely used during the Cold War."
|
||||
T-72B,yes,Ground Unit,Tank,T-72B,T-72B,red,Mid Cold War,yes,no,0,4000,,,,"T-72B: Soviet main battle tank with various upgrades, including composite armor."
|
||||
T-72B3,yes,Ground Unit,Tank,Tank T-72B3,Tank T-72B3,,,yes,no,0,4000,,,,Tank T-72B3: Modernized version of the Soviet T-72B main battle tank.
|
||||
T-80UD,yes,Ground Unit,Tank,T-80UD,T-80UD,red,Mid Cold War,yes,no,0,5000,,,,T-80UD: Ukrainian main battle tank known for its mobility and firepower.
|
||||
T-90,yes,Ground Unit,Tank,T-90,T-90,red,Late Cold War,yes,no,0,5000,,,,"T-90: Russian main battle tank, an upgraded version of the T-72 series."
|
||||
T155_Firtina,yes,Ground Unit,Artillery,SPH T155 Firtina 155mm,SPH T155 Firtina 155mm,,,yes,no,0,41000,,,,SPH T155 Firtina 155mm: Turkish self-propelled howitzer with a 155mm gun.
|
||||
TACAN_beacon,yes,Ground Unit,Structure,Beacon TACAN Portable TTS 3030,Beacon TACAN Portable TTS 3030,,,no,no,0,0,,,,Beacon TACAN Portable TTS 3030: Portable TACAN (Tactical Air Navigation) beacon for navigation.
|
||||
tacr2a,yes,Ground Unit,Unarmed,RAF Rescue,RAF Rescue,,,no,no,0,0,,,,RAF Rescue: Search and rescue helicopter used by the Royal Air Force.
|
||||
Tankcartrinity,yes,Ground Unit,Carriage,Tank Cartrinity,Tank Cartrinity,,,no,no,0,0,,,,Tank Cartrinity: Railway tank car used for transporting liquids.
|
||||
Tetrarch,no,Ground Unit,Armoured Car,Tk Tetrach,Tk Tetrach,,,yes,no,0,2000,,,,Tk Tetrach: British light tank used during World War II.
|
||||
Tiger_I,no,Ground Unit,Tank,Tk Tiger 1,Tk Tiger 1,,,yes,no,0,3000,,,,Tk Tiger 1: German heavy tank used during World War II.
|
||||
Tiger_II_H,no,Ground Unit,Tank,Tk Tiger II,Tk Tiger II,,,yes,no,0,6000,,,,"Tk Tiger II: German heavy tank, also known as King Tiger."
|
||||
Tigr_233036,yes,Ground Unit,Unarmed,Tigr_233036,Tigr_233036,red,Late Cold War,no,no,0,0,,,,Tigr_233036: Russian 4x4 wheeled armored vehicle used for various purposes.
|
||||
Tor 9A331,yes,Ground Unit,SAM,SA-15 Tor 9A331,Tor 9A331,red,Late Cold War,no,no,25000,12000,,,,SA-15 Tor 9A331: Russian short-range air defense system with Radar guidance.
|
||||
TPZ,yes,Ground Unit,Armoured Personnel Carrier,TPz Fuchs,TPz Fuchs,blue,Late Cold War,yes,no,0,1000,,,,TPz Fuchs: German 6x6 wheeled armored personnel carrier.
|
||||
Trolley bus,yes,Ground Unit,Unarmed,Trolley bus,Trolley bus,blue,Late Cold War,no,no,0,0,,,,"Trolley bus: Electric bus powered by overhead wires, used for public transport."
|
||||
tt_B8M1,yes,Ground Unit,Artillery,MLRS LC with B8M1 80mm,MLRS LC with B8M1 80mm,,,yes,no,5000,5000,,,,MLRS LC with B8M1 80mm: Light multiple rocket launcher system with 80mm rockets.
|
||||
tt_DSHK,yes,Ground Unit,Armoured Car,Scout LC with DSHK 12.7mm,Scout LC with DSHK 12.7mm,,,yes,no,5000,1200,,,,Scout LC with DSHK 12.7mm: Armored reconnaissance vehicle with a mounted DShK heavy machine gun.
|
||||
tt_KORD,yes,Ground Unit,Armoured Car,Scout LC with KORD 12.7mm,Scout LC with KORD 12.7mm,,,yes,no,5000,1200,,,,Scout LC with KORD 12.7mm: Armored reconnaissance vehicle with a mounted KORD heavy machine gun.
|
||||
tt_ZU-23,yes,Ground Unit,AAA,SPAAA LC with ZU-23,SPAAA LC with ZU-23,,,yes,no,0,2500,,,,SPAAA LC with ZU-23: Light armored anti-aircraft vehicle with dual ZU-23 autocannons.
|
||||
TYPE-59,yes,Ground Unit,Tank,MT Type 59,MT Type 59,,,yes,no,0,2500,,,,MT Type 59: Chinese medium tank based on the Soviet T-54.
|
||||
TZ-22_KrAZ,yes,Ground Unit,Unarmed,Refueler TZ-22 Tractor (KrAZ-258B1),Refueler TZ-22 Tractor (KrAZ-258B1),,,no,no,0,0,,,,Refueler TZ-22 Tractor (KrAZ-258B1): Military refueling tractor used for aircraft refueling.
|
||||
UAZ-469,yes,Ground Unit,Unarmed,UAZ-469,UAZ-469,red,Mid Cold War,no,no,0,0,,,,UAZ-469: Soviet/Russian light utility vehicle used for reconnaissance and transport.
|
||||
Uragan_BM-27,yes,Ground Unit,Rocket Artillery,Uragan,Uragan,red,Late Cold War,yes,no,0,35800,,,,Uragan: Soviet multiple rocket launcher system with a 220mm caliber.
|
||||
Ural ATsP-6,yes,Ground Unit,Unarmed,Ural ATsP-6,Ural ATsP-6,red,Mid Cold War,no,no,0,0,,,,Ural ATsP-6: Mobile command post based on the Ural truck platform.
|
||||
Ural-375,yes,Ground Unit,Unarmed,Ural-375,Ural-375,red,Mid Cold War,no,yes,0,0,,,,Ural-375: Soviet/Russian military truck used for various logistical purposes.
|
||||
Ural-375 PBU,yes,Ground Unit,Unarmed,Ural-375 PBU,Ural-375 PBU,red,Mid Cold War,no,no,0,0,,,,Ural-375 PBU: Mobile communication vehicle based on the Ural truck platform.
|
||||
Ural-375 ZU-23,yes,Ground Unit,AAA,Ural-375 ZU-23,Ural-375 ZU-23,red,Early Cold War,yes,no,5000,2500,,,,Ural-375 ZU-23: Anti-aircraft vehicle based on the Ural truck platform.
|
||||
Ural-375 ZU-23 Insurgent,yes,Ground Unit,AAA,Ural-375 ZU-23 Insurgent,Ural-375 ZU-23 Insurgent,red,Early Cold War,yes,no,5000,2500,,,,Ural-375 ZU-23 Insurgent: Improvised anti-aircraft vehicle based on the Ural truck platform.
|
||||
Ural-4320 APA-5D,yes,Ground Unit,Unarmed,Ural-4320 APA-5D,Ural-4320 APA-5D,red,Early Cold War,no,yes,0,0,Lightly armoured,,"Rearms ground units of same coaltion,",Ural-4320 APA-5D: Mobile power station based on the Ural truck platform.
|
||||
Ural-4320-31,yes,Ground Unit,Unarmed,Ural-4320-31,Ural-4320-31,red,Late Cold War,no,yes,0,0,,,"Rearms ground units of same coaltion,",Ural-4320-31: Military truck used for various logistical purposes.
|
||||
Ural-4320T,yes,Ground Unit,Unarmed,Ural-4320T,Ural-4320T,red,Late Cold War,no,yes,0,0,,,"Rearms ground units of same coaltion,",Ural-4320T: Military truck used for troop transport and logistics.
|
||||
v1_launcher,no,Ground Unit,Missile System,V-1 Launch Ramp,V-1 Launch Ramp,,,yes,no,0,0,,,,V-1 Launch Ramp: Launch ramp for the German V-1 flying bomb.
|
||||
VAB_Mephisto,yes,Ground Unit,Armoured Car,ATGM VAB Mephisto,ATGM VAB Mephisto,,,yes,no,0,3800,,,,ATGM VAB Mephisto: French armored vehicle equipped with anti-tank guided missiles.
|
||||
VAZ Car,yes,Ground Unit,Unarmed,VAZ Car,VAZ Car,red,Early Cold War,no,no,0,0,,,,VAZ Car: Russian military staff car based on the Lada Niva platform.
|
||||
Vulcan,yes,Ground Unit,AAA,Vulcan,Vulcan,blue,Late Cold War,yes,no,5000,2000,,,,Vulcan: Self-propelled anti-aircraft gun system featuring the M61 Vulcan cannon.
|
||||
Wellcarnsc,yes,Ground Unit,Carriage,Well Car,Well Car,,,no,no,0,0,,,,Well Car: Railway car designed to transport intermodal containers.
|
||||
Wespe124,no,Ground Unit,Artillery,SPH Sd.Kfz.124 Wespe 105mm,SPH Sd.Kfz.124 Wespe 105mm,,,yes,no,0,10500,,,,SPH Sd.Kfz.124 Wespe 105mm: German self-propelled howitzer used during World War II.
|
||||
Willys_MB,no,Ground Unit,Unarmed,Car Willys Jeep,Car Willys Jeep,,,no,no,0,0,,,,Car Willys Jeep: Iconic American military jeep used for reconnaissance and transport.
|
||||
ZBD04A,yes,Ground Unit,Infantry Fighting Vehicle,ZBD-04A,ZBD-04A,,,yes,no,0,4800,,,,ZBD-04A: Chinese amphibious infantry fighting vehicle.
|
||||
ZiL-131 APA-80,yes,Ground Unit,Unarmed,ZiL-131 APA-80,ZiL-131 APA-80,red,Early Cold War,no,no,0,0,,,,ZiL-131 APA-80: Mobile power station based on the ZiL-131 truck platform.
|
||||
ZIL-131 KUNG,yes,Ground Unit,Unarmed,ZIL-131 KUNG,ZIL-131 KUNG,red,Early Cold War,no,no,0,0,,,,ZIL-131 KUNG: Military truck with a covered cargo area based on the ZIL-131 platform.
|
||||
ZIL-135,yes,Ground Unit,Unarmed,Truck ZIL-135,Truck ZIL-135,,,no,no,0,0,,,,Truck ZIL-135: Soviet military truck used for various logistical purposes.
|
||||
ZIL-4331,yes,Ground Unit,Unarmed,ZIL-4331,ZIL-4331,red,Early Cold War,no,no,0,0,,,,ZIL-4331: Soviet/Russian military truck used for various logistical purposes.
|
||||
ZSU_57_2,yes,Ground Unit,AAA,SPAAA ZSU-57-2,SPAAA ZSU-57-2,,,yes,no,5000,7000,,,,SPAAA ZSU-57-2: Self-propelled anti-aircraft gun with dual 57mm autocannons.
|
||||
ZSU-23-4 Shilka,yes,Ground Unit,AAA,ZSU-23-4 Shilka,ZSU-23-4 Shilka,red,Late Cold War,yes,no,5000,2500,Ship,,"2nm 7,000ft range",ZSU-23-4 Shilka: Soviet self-propelled anti-aircraft gun system with four 23mm autocannons.
|
||||
ZTZ96B,yes,Ground Unit,Tank,ZTZ-96B,ZTZ-96B,,,yes,no,0,5000,,,,ZTZ-96B: Chinese main battle tank with a 125mm smoothbore gun.
|
||||
ZU-23 Closed Insurgent,yes,Ground Unit,AAA,ZU-23 Closed Insurgent,ZU-23 Closed Insurgent,red,Early Cold War,yes,no,5000,2500,,,,ZU-23 Closed Insurgent: Improvised anti-aircraft vehicle with ZU-23 autocannons.
|
||||
ZU-23 Emplacement,yes,Ground Unit,AAA,ZU-23 Emplacement,ZU-23 Emplacement,red,Early Cold War,yes,no,5000,2500,,,,ZU-23 Emplacement: Fixed emplacement with ZU-23 autocannons.
|
||||
ZU-23 Emplacement Closed,yes,Ground Unit,AAA,ZU-23 Emplacement Closed,ZU-23 Emplacement Closed,red,Early Cold War,yes,no,5000,2500,,,,ZU-23 Emplacement Closed: Fixed emplacement with ZU-23 autocannons.
|
||||
ZU-23 Insurgent,yes,Ground Unit,AAA,ZU-23 Insurgent,ZU-23 Insurgent,red,Early Cold War,yes,no,5000,2500,,,,ZU-23 Insurgent: Improvised anti-aircraft vehicle with ZU-23 autocannons.
|
||||
AH-1W,yes,Helicopter,Helicopter,AH-1W Cobra,AH1,blue,Mid Cold War,yes,no,,,"2 engine, 2 crew attack helicopter. Cobra",,"Gun, rockets and ATGMs",
|
||||
AH-64D_BLK_II,yes,Helicopter,Helicopter,AH-64D Apache,AH64,blue,Modern,yes,no,,,"2 engine, 2 crew attack helicopter. Apache",,"Gun, rockets and ATGMs",
|
||||
Ka-50_3,yes,Helicopter,Helicopter,Ka-50 Hokum A,K50,red,Late Cold War,yes,no,,,"2 engine, 1 crew attack helicopter. Blackshark",,"Fox 2 and gun, Rockets and ATGMs",
|
||||
Mi-24P,yes,Helicopter,Helicopter,Mi-24P Hind,Mi24,red,Mid Cold War,yes,no,,,"2 engine, 2 crew attack helicopter. Hind",,"Fox 2 and gun, Rockets and ATGMs",
|
||||
Mi-26,yes,Helicopter,Helicopter,Mi-26 Halo,M26,red,Late Cold War,no,no,,,"2 engine, 5 crew transport helicopter. Halo",,,
|
||||
Mi-28N,yes,Helicopter,Helicopter,Mi-28N Havoc,M28,red,Modern,yes,no,,,"2 engine, 2 crew attack helicopter. Havoc",,"Gun, Rockets and ATGMs",
|
||||
Mi-8MT,yes,Helicopter,Helicopter,Mi-8MT Hip,Mi8,red,Mid Cold War,no,no,,,"2 engine, 3 crew transport helicopter. Hip",,"Gun and rockets,",
|
||||
SA342L,yes,Helicopter,Helicopter,SA342L Gazelle,342,blue,Mid Cold War,no,no,,,"1 engine, 2 crew scout helicopter. Gazelle",,"Fox 2 and gun, Rockets and ATGMs",
|
||||
SA342M,yes,Helicopter,Helicopter,SA342M Gazelle,342,blue,Mid Cold War,no,no,,,"1 engine, 2 crew scout helicopter. Gazelle",,ATGMs,
|
||||
SA342Mistral,yes,Helicopter,Helicopter,SA342Mistral Gazelle,342,blue,Mid Cold War,no,no,,,"1 engine, 2 crew scout helicopter. Gazelle",,Fox 2,
|
||||
SH-60B,yes,Helicopter,Helicopter,SH-60B Seahawk,S60,blue,Mid Cold War,no,no,,,"2 engine, 3 crew transport helicopter. Seahawk",,,
|
||||
UH-1H,yes,Helicopter,Helicopter,UH-1H Huey,UH1,blue,Early Cold War,no,no,,,"2 engine, 2 crew transport helicopter. Huey",,"Gun and rockets,",
|
||||
UH-60A,yes,Helicopter,Helicopter,UH-60A Blackhawk,U60,blue,Mid Cold War,no,no,,,"2 engine, 3 crew transport helicopter. Blackhawk",,,
|
||||
albatros,yes,Navy Unit,Frigate,Albatros (Grisha-5),Albatros,red,Early Cold War,yes,no,30000,16000,,,,"Albatros (Grisha-5): Grisha-class corvette Albatros, designed for anti-submarine warfare and patrol duties."
|
||||
ara_vdm,yes,Navy Unit,Aircraft Carrier,ARA Vienticinco de Mayo,ARA Vienticinco de Mayo,,Mid Cold War,yes,no,18000,5000,ARA Vienticinco de Mayo. Conventional CATOBAR carrier,,"24kn, 9x40mm AA gun, 3nm range 9,000ft","ARA Vienticinco de Mayo: Aircraft carrier Vienticinco de Mayo, serving the Argentine Navy."
|
||||
BDK-775,yes,Navy Unit,Landing Ship,LS Ropucha,LS Ropucha,blue,Mid Cold War,yes,no,25000,6000,Landing ship Ropucha,,"2 57mm gun, rockets, Strela SAM, 8nm, 9,000ft range",LS Ropucha: Ropucha-class landing ship designed for transporting and landing amphibious forces.
|
||||
CastleClass_01,yes,Navy Unit,Patrol,HMS Leeds Castle (P-258),HMS Leeds Castle (P-258),blue,Mid Cold War,yes,no,25000,3000,HMS Leeds Castle. Smaller. Patrol craft,,"20mm gun, 12,7mm gun x 4, 3nm 4,000ft range",HMS Leeds Castle (P-258): Castle-class patrol vessel used for offshore patrol duties and maritime security.
|
||||
CV_1143_5,yes,Navy Unit,Aircraft Carrier,CV Admiral Kuznetsov(2017),Admiral Kuznetsov(2017),red,Modern,no,no,25000,12000,Admiral Kuznetsov. Conventional STOBAR carrier,,"12 granit anti ship missiles, kynshal & Kashtan SAM, 30mm gun x 6, 9nm 20,000ft range, 29kn","CV Admiral Kuznetsov(2017): Russian aircraft carrier Admiral Kuznetsov, serving as a mobile airbase for naval aviation."
|
||||
CVN_71,yes,Navy Unit,Super Aircraft Carrier,CVN-71 Theodore Roosevelt,CVN-71,blue,Late Cold War,no,no,50000,25000,Ship,,"6nm 16,000ft range",CVN-71 Theodore Roosevelt: Nimitz-class aircraft carrier serving as a flagship with a focus on power projection.
|
||||
CVN_72,yes,Navy Unit,Super Aircraft Carrier,CVN-72 Abraham Lincoln,CVN-72,blue,Late Cold War,no,no,50000,25000,Ship,,"6nm 16,000ft range","CVN-72 Abraham Lincoln: Nimitz-class aircraft carrier, a key component of naval force projection and air superiority."
|
||||
CVN_73,yes,Navy Unit,Super Aircraft Carrier,CVN-73 George Washington,CVN-73,blue,Late Cold War,no,no,50000,25000,Ship,,"6nm 16,000ft range",CVN-73 George Washington: Nimitz-class aircraft carrier providing strategic naval capabilities and air support.
|
||||
CVN_75,yes,Navy Unit,Aircraft Carrier,CVN-75 Harry S. Truman,CVN-75,blue,Late Cold War,no,no,50000,25000,Ship,,"6nm 16,000ft range",CVN-75 Harry S. Truman: Nimitz-class aircraft carrier playing a crucial role in power projection and global security.
|
||||
Dry-cargo ship-1,yes,Navy Unit,Cargoship,Bulker Yakushev,Bulker Yakushev,,,no,no,0,0,,,,"Bulker Yakushev: Bulk carrier ship Yakushev, specializing in the transport of dry bulk cargo."
|
||||
Dry-cargo ship-2,yes,Navy Unit,Cargoship,Cargo Ivanov,Cargo Ivanov,,,no,no,0,0,,,,Cargo Ivanov: Ivanov-class cargo ship designed for transporting goods and equipment.
|
||||
elnya,yes,Navy Unit,Tanker,Elnya tanker,Elnya tanker,red,Late Cold War,no,no,0,0,,,,"Elnya tanker: Elnya-class tanker, supporting naval operations by providing fuel replenishment."
|
||||
Forrestal,yes,Navy Unit,Aircraft Carrier,CV-59 Forrestal,CV-59 Forrestal,,,no,no,50000,25000,,,,"CV-59 Forrestal: Forrestal-class aircraft carrier, a historic vessel with a significant role in naval aviation."
|
||||
HandyWind,yes,Navy Unit,Cargoship,Bulker Handy Wind,Bulker Handy Wind,blue,Late Cold War,no,no,0,0,,,,"Bulker Handy Wind: Bulk carrier ship designed for transporting unpackaged cargo, such as grains or minerals."
|
||||
HarborTug,yes,Navy Unit,Tug,Harbor Tug,Harbor Tug,,Mid Cold War,no,no,0,0,,,,"Harbor Tug: Tugboat specialized in maneuvering ships in harbors, assisting in docking and undocking."
|
||||
Higgins_boat,yes,Navy Unit,Landing Ship,Boat LCVP Higgins,Boat LCVP Higgins,,,yes,no,3000,1000,,,,"Boat LCVP Higgins: Landing Craft, Vehicle, Personnel (LCVP) Higgins, designed for troop and vehicle transport."
|
||||
hms_invincible,yes,Navy Unit,Aircraft Carrier,HMS Invincible (R05),HMS Invincible,blue,Mid Cold War,yes,no,100000,74000,Ship,,"46nm >50,000ft range","HMS Invincible (R05): Invincible-class aircraft carrier, a key asset in the Royal Navy's fleet."
|
||||
IMPROVED_KILO,yes,Navy Unit,Submarine,SSK 636 Improved Kilo,SSK 636 Improved Kilo,,,no,no,0,0,,,,"SSK 636 Improved Kilo: Upgraded version of the Kilo-class submarine, known for its stealth capabilities."
|
||||
kilo,yes,Navy Unit,Submarine,Project 636 Varshavyanka Basic,Varshavyanka Basic,red,Late Cold War,no,no,0,0,,,,Project 636 Varshavyanka Basic: Improved Kilo-class submarine designed for stealth and anti-submarine warfare.
|
||||
kuznecow,yes,Navy Unit,Aircraft Carrier,Admiral Kuznetsov,Admiral Kuznetsov,red,Late Cold War,no,no,25000,12000,,,,"Admiral Kuznetsov: Russian aircraft carrier Admiral Kuznetsov, a key element of Russia's naval aviation."
|
||||
La_Combattante_II,yes,Navy Unit,Fast Attack Craft,FAC La Combattante lla,FAC La Combattante,blue,Mid Cold War,yes,no,19000,4000,,,,FAC La Combattante lla: Fast Attack Craft designed for high-speed naval operations and coastal defense.
|
||||
leander-gun-achilles,yes,Navy Unit,Frigate,HMS Achilles (F12),HMS Achilles,blue,Mid Cold War,yes,no,180000,8000,Ship,,"7nm 6,000ft range","HMS Achilles (F12): Leander-class frigate HMS Achilles, providing anti-submarine and anti-air capabilities."
|
||||
leander-gun-andromeda,yes,Navy Unit,Frigate,HMS Andromeda (F57),HMS Andromeda,blue,Mid Cold War,yes,no,180000,140000,Ship,,"7nm 6,000ft range","HMS Andromeda (F57): Leander-class frigate HMS Andromeda, serving in anti-submarine and anti-air roles."
|
||||
leander-gun-ariadne,yes,Navy Unit,Frigate,HMS Ariadne (F72),HMS Ariadne,blue,Mid Cold War,yes,no,150000,100000,Ship,,"7nm 6,000ft range","HMS Ariadne (F72): Leander-class frigate HMS Ariadne, contributing to the Royal Navy's anti-submarine capabilities."
|
||||
leander-gun-condell,yes,Navy Unit,Frigate,Almirante Condell PFG-06,Almirante Condell,,Mid Cold War,yes,no,150000,100000,Ship,,"7nm 6,000ft range","Almirante Condell PFG-06: Condell-class frigate, part of the Chilean Navy, with anti-submarine and anti-ship capabilities."
|
||||
leander-gun-lynch,yes,Navy Unit,Frigate,CNS Almirante Lynch (PFG-07),CNS Almirante Lynch,,Mid Cold War,yes,no,180000,140000,Ship,,"7nm 6,000ft range","CNS Almirante Lynch (PFG-07): Lynch-class frigate, enhancing the naval capabilities of the Chilean Navy."
|
||||
LHA_Tarawa,yes,Navy Unit,Aircraft Carrier,LHA-1 Tarawa,LHA-1 Tarawa,blue,Mid Cold War,yes,no,150000,20000,Ship,,"8nm 13,000ft range",LHA-1 Tarawa: Wasp-class amphibious assault ship capable of deploying Marines and their equipment.
|
||||
LST_Mk2,yes,Navy Unit,Landing Ship,LST Mk.II,LST Mk.II,,,yes,no,0,4000,,,,"LST Mk.II: Landing Ship, Tank (LST) Mk.II, designed for transporting tanks and vehicles for amphibious assaults."
|
||||
molniya,yes,Navy Unit,Corvette,Molniya (Tarantul-3),Molniya,,Late Cold War,yes,no,21000,2000,,,,"Molniya (Tarantul-3): Tarantul-class corvette Molniya, a fast attack craft specializing in anti-ship warfare."
|
||||
moscow,yes,Navy Unit,Cruiser,Moscow,Moscow,red,Late Cold War,yes,no,160000,75000,,,,"Moscow: Slava-class cruiser Moscow, serving as a guided missile cruiser with anti-ship and anti-air capabilities."
|
||||
neustrash,yes,Navy Unit,Frigate,Neustrashimy,Neustrashimy,red,Late Cold War,yes,no,27000,12000,,,,"Neustrashimy: Neustrashimy-class frigate, designed for anti-submarine warfare and escort missions."
|
||||
perry,yes,Navy Unit,Frigate,Oliver H. Perry,Oliver H. Perry,blue,Mid Cold War,yes,no,150000,100000,,,,"Oliver H. Perry: Oliver Hazard Perry-class frigate, providing anti-submarine and anti-air capabilities."
|
||||
PIOTR,yes,Navy Unit,Cruiser,Battlecruiser 1144.2 Pyotr Velikiy,Battlecruiser 1144.2 Pyotr Velikiy,,,yes,no,250000,190000,,,,"Battlecruiser 1144.2 Pyotr Velikiy: Kirov-class battlecruiser Pyotr Velikiy, a heavily armed surface warfare vessel."
|
||||
Rezky (Krivak-2),yes,Navy Unit,Frigate,Rezky (Krivak-2),Rezky,red,Early Cold War,yes,no,30000,16000,,,,"Rezky (Krivak-2): Krivak II-class frigate Rezky, providing anti-submarine and anti-surface capabilities."
|
||||
santafe,yes,Navy Unit,Submarine,ARA Santa Fe S-21,ARA Santa,,Early Cold War,no,no,0,0,,,,"ARA Santa Fe S-21: Santa Fe-class submarine, a conventional diesel-electric submarine in service with the Argentine Navy."
|
||||
Schnellboot_type_S130,yes,Navy Unit,Torpedo Boat,Boat Schnellboot type S130,Boat Schnellboot type S130,,,yes,no,10000,4000,,,,"Boat Schnellboot type S130: Schnellboot-type torpedo boat S130, a high-speed vessel used for fast attack and patrol."
|
||||
Seawise_Giant,yes,Navy Unit,Tanker,Tanker Seawise Giant,Seawise Giant,blue,Late Cold War,no,no,0,0,,,,"Tanker Seawise Giant: Supertanker Seawise Giant, one of the largest ships ever built, used for transporting crude oil."
|
||||
Ship_Tilde_Supply,yes,Navy Unit,Transport,Supply Ship MV Tilde,Supply Ship Tilde,blue,Late Cold War,no,no,0,0,,,,"Supply Ship MV Tilde: Supply ship MV Tilde designed to replenish naval vessels at sea with fuel, ammunition, and supplies."
|
||||
SOM,yes,Navy Unit,Submarine,SSK 641B Tango,SSK 641B Tango,,,no,no,0,0,,,,"SSK 641B Tango: Tango-class submarine, a diesel-electric attack submarine used for various roles."
|
||||
speedboat,yes,Navy Unit,Speedboat,Boat Armed Hi-speed,Boat Armed Hi-speed,,,yes,no,5000,1000,,,,Boat Armed Hi-speed: High-speed armed patrol boat designed for coastal defense and interception.
|
||||
Stennis,yes,Navy Unit,Aircraft Carrier,CVN-74 John C. Stennis,CVN-74,blue,Late Cold War,yes,no,50000,25000,,,,"CVN-74 John C. Stennis: Nimitz-class aircraft carrier, a vital element in power projection and global stability."
|
||||
TICONDEROG,yes,Navy Unit,Cruiser,Ticonderoga,Ticonderoga,blue,Late Cold War,yes,no,150000,100000,Ship,,"55nm >50,000ft range",Ticonderoga: Ticonderoga-class cruiser equipped with advanced Radar and missile systems for air defense.
|
||||
Type_052B,yes,Navy Unit,Destroyer,052B DDG-168 Guangzhou,Type 52B,red,Modern,yes,no,100000,30000,Ship,,"27nm 48,000ft range","052B DDG-168 Guangzhou: Luyang I-class destroyer Guangzhou, part of the People's Liberation Army Navy."
|
||||
Type_052C,yes,Navy Unit,Destroyer,052C DDG-171 Haikou,Type 52C,red,Modern,yes,no,260000,100000,Ship,,"64nm >50,000ft range","052C DDG-171 Haikou: Luyang II-class destroyer Haikou, featuring advanced air defense and anti-submarine capabilities."
|
||||
Type_071,yes,Navy Unit,Transport,Type 071,Type 071,red,Modern,yes,no,300000,150000,Ship,,"4nm 9,000ft range","Type 071: Amphibious transport dock ship designed for carrying troops, vehicles, and helicopters."
|
||||
Type_093,yes,Navy Unit,Submarine,Type 093,Type 093,red,Modern,yes,no,40000,40000,,,,Type 093: Chinese nuclear-powered attack submarine providing strategic underwater capabilities.
|
||||
Uboat_VIIC,yes,Navy Unit,Submarine,U-boat VIIC U-flak,U-boat VIIC U-flak,,,yes,no,20000,4000,,,,"U-boat VIIC U-flak: German U-boat Type VIIC, a World War II submarine designed for naval warfare."
|
||||
USS_Arleigh_Burke_IIa,yes,Navy Unit,Destroyer,DDG Arleigh Burke lla,DDG Arleigh Burke,blue,Late Cold War,yes,no,150000,100000,,,"57nm >50,000ft range",DDG Arleigh Burke lla: Arleigh Burke-class destroyer equipped with advanced anti-aircraft and anti-submarine systems.
|
||||
USS_Samuel_Chase,yes,Navy Unit,Landing SHip,LS Samuel Chase,LS Samuel Chase,,,yes,no,0,7000,,,,"LS Samuel Chase: Samuel Chase-class landing ship, a vessel used for amphibious warfare and logistics support."
|
||||
VINSON,yes,Navy Unit,Aircraft Carrier,CVN-70 Carl Vinson,CVN-70 Carl Vinson,,,no,no,30000,15000,,,,CVN-70 Carl Vinson: Nimitz-class aircraft carrier with a focus on power projection and naval air operations.
|
||||
zwezdny,yes,Navy Unit,Civilian Boat,Zwezdny,Zwezdny,,Modern,no,no,0,0,,,,"Zwezdny: Zwezdny-class transport ship, supporting naval operations with cargo and troop transport."
|
||||
,yes,Navy Unit,Frigate,054A FFG-538 Yantai,Type 54A,red,Modern,yes,no,160000,45000,Ship,,"35nm >50,000ft range","054A FFG-538 Yantai: Jiangkai I-class frigate Yantai, serving as a versatile and multi-role surface combatant."
|
||||
|
@ -1,86 +0,0 @@
|
||||
import math
|
||||
import urllib.request
|
||||
import os
|
||||
import multiprocessing
|
||||
|
||||
try:
|
||||
os.mkdir("hgt")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
def download_file(latlng):
|
||||
lat = latlng[0]
|
||||
lng = latlng[1]
|
||||
|
||||
if lat < 0:
|
||||
lat = f"S{abs(lat):02}"
|
||||
else:
|
||||
lat = f"N{lat:02}"
|
||||
|
||||
if lng < 0:
|
||||
lng = f"W{abs(lng):03}"
|
||||
else:
|
||||
lng = f"E{lng:03}"
|
||||
|
||||
url = f"https://srtm.fasma.org/{lat}{lng}.SRTMGL3S.hgt.zip"
|
||||
urllib.request.urlretrieve(url, f"hgt/{lat}{lng}.SRTMGL3S.hgt.zip")
|
||||
print(f"{url} downloaded")
|
||||
|
||||
boundaries = [
|
||||
[ # NTTR
|
||||
39.7982463, -119.985425,
|
||||
34.4037128, -119.7806729,
|
||||
34.3483316, -112.4529351,
|
||||
39.7372411, -112.1130805,
|
||||
39.7982463, -119.985425
|
||||
],
|
||||
[ # Syria
|
||||
37.3630556, 29.2686111,
|
||||
31.8472222, 29.8975,
|
||||
32.1358333, 42.1502778,
|
||||
37.7177778, 42.3716667,
|
||||
37.3630556, 29.2686111
|
||||
],
|
||||
[ # Caucasus
|
||||
39.6170191, 27.634935,
|
||||
38.8735863, 47.1423108,
|
||||
47.3907982, 49.3101946,
|
||||
48.3955879, 26.7753625,
|
||||
39.6170191, 27.634935
|
||||
],
|
||||
[ # Persian Gulf
|
||||
32.9355285, 46.5623682,
|
||||
21.729393, 47.572675,
|
||||
21.8501348, 63.9734737,
|
||||
33.131584, 64.7313594,
|
||||
32.9355285, 46.5623682
|
||||
],
|
||||
[ # Marianas
|
||||
22.09, 135.0572222,
|
||||
10.5777778, 135.7477778,
|
||||
10.7725, 149.3918333,
|
||||
22.5127778, 149.5427778,
|
||||
22.09, 135.0572222
|
||||
]
|
||||
]
|
||||
|
||||
latlngs = []
|
||||
if __name__ == '__main__':
|
||||
pool = multiprocessing.Pool(32)
|
||||
for boundary_set in boundaries:
|
||||
lats = [boundary_set[i] for i in range(0, len(boundary_set), 2)]
|
||||
lngs = [boundary_set[i] for i in range(1, len(boundary_set), 2)]
|
||||
minLat = math.floor(min(lats))
|
||||
minLng = math.floor(min(lngs))
|
||||
maxLat = math.ceil(max(lats))
|
||||
maxLng = math.ceil(max(lngs))
|
||||
|
||||
index = 1
|
||||
for lat in range(minLat, maxLat + 1):
|
||||
for lng in range(minLng, maxLng + 1):
|
||||
latlngs.append((lat, lng))
|
||||
|
||||
print(len(latlngs))
|
||||
#pool.map(download_file, latlngs)
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ public:
|
||||
virtual void setOnOff(bool newOnOff, bool force = false);
|
||||
virtual void setFollowRoads(bool newFollowRoads, bool force = false);
|
||||
|
||||
void aimAtPoint(Coords aimTarget, double horizontalScatterMultiplier = 1, double verticalScatterMultiplier = 1);
|
||||
void aimAtPoint(Coords aimTarget);
|
||||
|
||||
protected:
|
||||
virtual void AIloop();
|
||||
|
||||
@ -180,7 +180,7 @@ void GroundUnit::AIloop()
|
||||
if (!getHasTask()) {
|
||||
std::ostringstream taskSS;
|
||||
taskSS.precision(10);
|
||||
taskSS << "{id = 'FireAtPoint', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << ", radius = 1000}";
|
||||
taskSS << "{id = 'FireAtPoint', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << ", radius = 100}";
|
||||
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
@ -191,10 +191,53 @@ void GroundUnit::AIloop()
|
||||
case State::SIMULATE_FIRE_FIGHT: {
|
||||
setTask("Simulating fire fight");
|
||||
|
||||
if (internalCounter == 0) {
|
||||
aimAtPoint(targetPosition, 3.0, 0.0);
|
||||
if (internalCounter == 0 && targetPosition != Coords(NULL)) {
|
||||
/* Get the distance and bearing to the target */
|
||||
Coords scatteredTargetPosition = targetPosition;
|
||||
double distance;
|
||||
double bearing1;
|
||||
double bearing2;
|
||||
Geodesic::WGS84().Inverse(getPosition().lat, getPosition().lng, scatteredTargetPosition.lat, scatteredTargetPosition.lng, distance, bearing1, bearing2);
|
||||
|
||||
/* Compute the scattered position applying a random scatter to the shot */
|
||||
double scatterDistance = distance * tan(10 /* degs */ * (ShotsScatter::LOW - shotsScatter) / 57.29577) * RANDOM_MINUS_ONE_TO_ONE;
|
||||
Geodesic::WGS84().Direct(scatteredTargetPosition.lat, scatteredTargetPosition.lng, bearing1 + 90, scatterDistance, scatteredTargetPosition.lat, scatteredTargetPosition.lng);
|
||||
|
||||
/* Recover the data from the database */
|
||||
bool indirectFire = false;
|
||||
double shotsBaseInterval = 15; /* s */
|
||||
if (database.has_object_field(to_wstring(name))) {
|
||||
json::value databaseEntry = database[to_wstring(name)];
|
||||
if (databaseEntry.has_boolean_field(L"indirectFire"))
|
||||
indirectFire = databaseEntry[L"indirectFire"].as_bool();
|
||||
if (databaseEntry.has_number_field(L"shotsBaseInterval"))
|
||||
shotsBaseInterval = databaseEntry[L"shotsBaseInterval"].as_number().to_double();
|
||||
}
|
||||
|
||||
/* If the unit is of the indirect fire type, like a mortar, simply shoot at the target */
|
||||
if (indirectFire) {
|
||||
log(unitName + "(" + name + ")" + " simulating fire fight with indirect fire");
|
||||
std::ostringstream taskSS;
|
||||
taskSS.precision(10);
|
||||
taskSS << "{id = 'FireAtPoint', lat = " << scatteredTargetPosition.lat << ", lng = " << scatteredTargetPosition.lng << ", radius = 100}";
|
||||
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
}
|
||||
/* Otherwise use the aim method */
|
||||
else {
|
||||
log(unitName + "(" + name + ")" + " simulating fire fight with aim at point method");
|
||||
aimAtPoint(scatteredTargetPosition);
|
||||
}
|
||||
|
||||
/* Wait an amout of time depending on the shots intensity */
|
||||
internalCounter = ((ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL;
|
||||
}
|
||||
|
||||
if (targetPosition == Coords(NULL))
|
||||
setState(State::IDLE);
|
||||
|
||||
/* Fallback if something went wrong */
|
||||
if (internalCounter == 0)
|
||||
internalCounter = 20 / FRAMERATE_TIME_INTERVAL;
|
||||
internalCounter--;
|
||||
@ -204,13 +247,14 @@ void GroundUnit::AIloop()
|
||||
case State::SCENIC_AAA: {
|
||||
setTask("Scenic AAA");
|
||||
|
||||
if ((!getHasTask() || internalCounter == 0) && getOperateAs() > 0) {
|
||||
if ((!getHasTask() || internalCounter == 0)) {
|
||||
double distance = 0;
|
||||
unsigned char targetCoalition = getOperateAs() == 2 ? 1 : 2;
|
||||
unsigned char unitCoalition = coalition == 0 ? getOperateAs() : coalition;
|
||||
unsigned char targetCoalition = unitCoalition == 2 ? 1 : 2;
|
||||
Unit* target = unitsManager->getClosestUnit(this, targetCoalition, { "Aircraft", "Helicopter" }, distance);
|
||||
|
||||
/* Only run if an enemy air unit is closer than 20km to avoid useless load */
|
||||
if (distance < 20000 /* m */) {
|
||||
if (target != nullptr && distance < 20000 /* m */) {
|
||||
double r = 15; /* m */
|
||||
double barrelElevation = r * tan(acos(((double)(rand()) / (double)(RAND_MAX))));
|
||||
|
||||
@ -247,9 +291,10 @@ void GroundUnit::AIloop()
|
||||
|
||||
if (canAAA) {
|
||||
/* Only run this when the internal counter reaches 0 to avoid excessive computations when no nearby target */
|
||||
if (internalCounter == 0 && getOperateAs() > 0) {
|
||||
if (internalCounter == 0) {
|
||||
double distance = 0;
|
||||
unsigned char targetCoalition = getOperateAs() == 2 ? 1 : 2;
|
||||
unsigned char unitCoalition = coalition == 0 ? getOperateAs() : coalition;
|
||||
unsigned char targetCoalition = unitCoalition == 2 ? 1 : 2;
|
||||
|
||||
/* Default gun values */
|
||||
double barrelHeight = 1.0; /* m */
|
||||
@ -261,6 +306,7 @@ void GroundUnit::AIloop()
|
||||
double engagementRange = 10000; /* m */
|
||||
double targetingRange = 0; /* m */
|
||||
double aimMethodRange = 0; /* m */
|
||||
double acquisitionRange = 0; /* m */
|
||||
|
||||
/* Load gun values from database */
|
||||
if (database.has_object_field(to_wstring(name))) {
|
||||
@ -283,11 +329,14 @@ void GroundUnit::AIloop()
|
||||
targetingRange = databaseEntry[L"targetingRange"].as_number().to_double();
|
||||
if (databaseEntry.has_number_field(L"aimMethodRange"))
|
||||
aimMethodRange = databaseEntry[L"aimMethodRange"].as_number().to_double();
|
||||
if (databaseEntry.has_number_field(L"acquisitionRange"))
|
||||
acquisitionRange = databaseEntry[L"acquisitionRange"].as_number().to_double();
|
||||
}
|
||||
|
||||
/* Get all the units in range and select one at random */
|
||||
double range = aimMethodRange > engagementRange ? aimMethodRange : engagementRange;
|
||||
double range = max(max(engagementRange, aimMethodRange), acquisitionRange);
|
||||
map<Unit*, double> targets = unitsManager->getUnitsInRange(this, targetCoalition, { "Aircraft", "Helicopter" }, range);
|
||||
|
||||
Unit* target = nullptr;
|
||||
unsigned int index = static_cast<unsigned int>((RANDOM_ZERO_TO_ONE * (targets.size() - 1)));
|
||||
for (auto const& p : targets) {
|
||||
@ -303,8 +352,6 @@ void GroundUnit::AIloop()
|
||||
if (muzzleVelocity != 0)
|
||||
aimTime += distance / muzzleVelocity;
|
||||
|
||||
internalCounter = (aimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL;
|
||||
|
||||
/* If the target is in targeting range and we are in highest precision mode, target it */
|
||||
if (distance < targetingRange && shotsScatter == ShotsScatter::LOW) {
|
||||
/* Send the command */
|
||||
@ -314,38 +361,51 @@ void GroundUnit::AIloop()
|
||||
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
|
||||
internalCounter = (aimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL;
|
||||
}
|
||||
/* Else, do miss on purpose */
|
||||
else {
|
||||
/* Compute where the target will be in aimTime seconds. */
|
||||
double aimDistance = target->getHorizontalVelocity() * aimTime;
|
||||
/* Compute where the target will be in aimTime seconds, plus the effect of scatter. */
|
||||
double scatterDistance = distance * tan(shotsBaseScatter * (ShotsScatter::LOW - shotsScatter) / 57.29577) * (RANDOM_ZERO_TO_ONE - 0.1);
|
||||
double aimDistance = target->getHorizontalVelocity() * aimTime + scatterDistance;
|
||||
double aimLat = 0;
|
||||
double aimLng = 0;
|
||||
Geodesic::WGS84().Direct(target->getPosition().lat, target->getPosition().lng, target->getHeading() * 57.29577, aimDistance, aimLat, aimLng); /* TODO make util to convert degrees and radians function */
|
||||
double aimAlt = target->getPosition().alt + target->getVerticalVelocity() * aimTime;
|
||||
double aimAlt = target->getPosition().alt + target->getVerticalVelocity() * aimTime + distance * tan(shotsBaseScatter * (ShotsScatter::LOW - shotsScatter) / 57.29577) * RANDOM_ZERO_TO_ONE; // Force to always miss high never low
|
||||
|
||||
/* Send the command */
|
||||
if (distance > engagementRange) {
|
||||
aimAtPoint(Coords(aimLat, aimLng, aimAlt));
|
||||
}
|
||||
else {
|
||||
/* Compute a random scattering depending on the distance and the selected shots scatter */
|
||||
double scatterDistance = distance * tan(shotsBaseScatter * (ShotsScatter::LOW - shotsScatter) / 57.29577) * RANDOM_MINUS_ONE_TO_ONE;
|
||||
double scatterAngle = 180 * RANDOM_MINUS_ONE_TO_ONE;
|
||||
Geodesic::WGS84().Direct(aimLat, aimLng, scatterAngle, scatterDistance, aimLat, aimLng); /* TODO make util function */
|
||||
aimAlt = aimAlt + distance * tan(shotsBaseScatter * (ShotsScatter::LOW - shotsScatter) / 57.29577) * RANDOM_MINUS_ONE_TO_ONE;
|
||||
|
||||
if (distance < engagementRange) {
|
||||
/* If the unit is closer than the engagement range, use the fire at point method */
|
||||
std::ostringstream taskSS;
|
||||
taskSS.precision(10);
|
||||
taskSS << "{id = 'FireAtPoint', lat = " << aimLat << ", lng = " << aimLng << ", alt = " << aimAlt << ", radius = 0.001, expendQty = " << shotsToFire << " }";
|
||||
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
setTargetPosition(Coords(aimLat, aimLng, target->getPosition().alt));
|
||||
internalCounter = (aimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL;
|
||||
}
|
||||
else if (distance < aimMethodRange) {
|
||||
/* If the unit is closer than the aim method range, use the aim method range */
|
||||
aimAtPoint(Coords(aimLat, aimLng, aimAlt));
|
||||
setTargetPosition(Coords(aimLat, aimLng, target->getPosition().alt));
|
||||
internalCounter = (aimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL;
|
||||
}
|
||||
else {
|
||||
/* Else just wake the unit up with an impossible command */
|
||||
std::ostringstream taskSS;
|
||||
taskSS.precision(10);
|
||||
taskSS << "{id = 'FireAtPoint', lat = " << 0 << ", lng = " << 0 << ", alt = " << 0 << ", radius = 0.001, expendQty = " << 0 << " }";
|
||||
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
setTargetPosition(Coords(NULL));
|
||||
|
||||
setTargetPosition(Coords(aimLat, aimLng, target->getPosition().alt));
|
||||
/* Don't wait too long before checking again */
|
||||
internalCounter = 5 / FRAMERATE_TIME_INTERVAL;
|
||||
}
|
||||
}
|
||||
|
||||
missOnPurposeTarget = target;
|
||||
}
|
||||
else {
|
||||
@ -362,7 +422,7 @@ void GroundUnit::AIloop()
|
||||
if (databaseEntry.has_number_field(L"alertnessTimeConstant"))
|
||||
alertnessTimeConstant = databaseEntry[L"alertnessTimeConstant"].as_number().to_double();
|
||||
}
|
||||
internalCounter = (5 + RANDOM_ZERO_TO_ONE * alertnessTimeConstant) / FRAMERATE_TIME_INTERVAL;
|
||||
internalCounter = (5 + RANDOM_ZERO_TO_ONE * alertnessTimeConstant * 0 /* TODO: remove to enable alertness again */) / FRAMERATE_TIME_INTERVAL;
|
||||
missOnPurposeTarget = nullptr;
|
||||
setTargetPosition(Coords(NULL));
|
||||
}
|
||||
@ -380,7 +440,7 @@ void GroundUnit::AIloop()
|
||||
}
|
||||
|
||||
|
||||
void GroundUnit::aimAtPoint(Coords aimTarget, double horizontalScatterMultiplier, double verticalScatterMultiplier) {
|
||||
void GroundUnit::aimAtPoint(Coords aimTarget) {
|
||||
double dist;
|
||||
double bearing1;
|
||||
double bearing2;
|
||||
@ -411,12 +471,13 @@ void GroundUnit::aimAtPoint(Coords aimTarget, double horizontalScatterMultiplier
|
||||
/* Check we can reach the target*/
|
||||
if (inner > 0) {
|
||||
/* Compute elevation and bearing */
|
||||
double barrelElevation = r * tan(atan((dist - sqrt(inner)) / (2 * alpha)) + RANDOM_MINUS_ONE_TO_ONE * (ShotsScatter::LOW - shotsScatter) * verticalScatterMultiplier);
|
||||
double barrelElevation = r * (dist - sqrt(inner)) / (2 * alpha);
|
||||
|
||||
double lat = 0;
|
||||
double lng = 0;
|
||||
double randomBearing = bearing1 + RANDOM_MINUS_ONE_TO_ONE * (ShotsScatter::LOW - shotsScatter) * horizontalScatterMultiplier;
|
||||
Geodesic::WGS84().Direct(position.lat, position.lng, randomBearing, r, lat, lng);
|
||||
Geodesic::WGS84().Direct(position.lat, position.lng, bearing1, r, lat, lng);
|
||||
|
||||
log(unitName + "(" + name + ")" + " shooting with aim at point method. Barrel elevation: " + to_string(barrelElevation * 57.29577) + "°, bearing: " + to_string(bearing1) + "°");
|
||||
|
||||
std::ostringstream taskSS;
|
||||
taskSS.precision(10);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#define VERSION "v0.4.6-alpha"
|
||||
#define VERSION "v0.4.8-alpha"
|
||||
#define LOG_NAME "Olympus_log.txt"
|
||||
#define REST_ADDRESS "http://localhost:30000"
|
||||
#define REST_URI "olympus"
|
||||
|
||||