mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge branch 'main' into 412-improve-importexport
This commit is contained in:
@@ -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
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
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
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();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user