Completed frontend controls

This commit is contained in:
Pax1601
2023-06-01 17:18:14 +02:00
parent 4087dbde21
commit 1dd4014e61
29 changed files with 1297 additions and 541 deletions

View File

@@ -37,9 +37,13 @@ interface TaskData {
currentTask: string;
activePath: any;
targetSpeed: number;
targetSpeedType: string;
targetAltitude: number;
targetAltitudeType: string;
isTanker: boolean;
isAWACS: boolean;
onOff: boolean;
followRoads: boolean;
}
interface OptionsData {
@@ -51,15 +55,6 @@ interface OptionsData {
generalSettings: GeneralSettings;
}
interface UnitData {
baseData: BaseData;
flightData: FlightData;
missionData: MissionData;
formationData: FormationData;
taskData: TaskData;
optionsData: OptionsData;
}
interface TACAN {
isOn: boolean;
channel: number;
@@ -91,4 +86,13 @@ interface UnitIconOptions {
showAmmo: boolean,
showSummary: boolean,
rotateToHeading: boolean
}
interface UnitData {
baseData: BaseData;
flightData: FlightData;
missionData: MissionData;
formationData: FormationData;
taskData: TaskData;
optionsData: OptionsData;
}

View File

@@ -0,0 +1,102 @@
import { LatLng, LatLngBounds, TileLayer, tileLayer } from "leaflet";
export const ROEs: string[] = ["Hold", "Return", "Designated", "Free"];
export const reactionsToThreat: string[] = ["None", "Manoeuvre", "Passive", "Evade"];
export const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"];
export const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"];
export const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"];
export const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar OFF, no ECM)", "Attack (Radar only for targeting, ECM only if locked)", "Defend (Radar for searching, ECM if locked)", "Always on (Radar and ECM always on)"];
export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
export const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 };
export const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helicopter: 0 };
export const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 };
export const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 };
export const minimapBoundaries = [
[ // NTTR
new LatLng(39.7982463, -119.985425),
new LatLng(34.4037128, -119.7806729),
new LatLng(34.3483316, -112.4529351),
new LatLng(39.7372411, -112.1130805),
new LatLng(39.7982463, -119.985425)
],
[ // Syria
new LatLng(37.3630556, 29.2686111),
new LatLng(31.8472222, 29.8975),
new LatLng(32.1358333, 42.1502778),
new LatLng(37.7177778, 42.3716667),
new LatLng(37.3630556, 29.2686111)
],
[ // Caucasus
new LatLng(39.6170191, 27.634935),
new LatLng(38.8735863, 47.1423108),
new LatLng(47.3907982, 49.3101946),
new LatLng(48.3955879, 26.7753625),
new LatLng(39.6170191, 27.634935)
],
[ // Persian Gulf
new LatLng(32.9355285, 46.5623682),
new LatLng(21.729393, 47.572675),
new LatLng(21.8501348, 63.9734737),
new LatLng(33.131584, 64.7313594),
new LatLng(32.9355285, 46.5623682)
],
[ // Marianas
new LatLng(22.09, 135.0572222),
new LatLng(10.5777778, 135.7477778),
new LatLng(10.7725, 149.3918333),
new LatLng(22.5127778, 149.5427778),
new LatLng(22.09, 135.0572222)
]
];
export const mapBounds = {
"Syria": { bounds: new LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]), zoom: 5 },
"MarianaIslands": { bounds: new LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]), zoom: 5 },
"Nevada": { bounds: new LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805]), zoom: 5 },
"PersianGulf": { bounds: new LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]), zoom: 5 },
"Caucasus": { bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]), zoom: 4 },
// TODO "Falklands"
}
export const layers = {
"ArcGIS Satellite": {
urlTemplate: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
maxZoom: 20,
minZoom: 1,
attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
},
"USGS Topo": {
urlTemplate: 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}',
minZoom: 1,
maxZoom: 20,
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: 19,
attribution: '&copy; <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,
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 &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
},
"Esri.DeLorme": {
urlTemplate: 'https://server.arcgisonline.com/ArcGIS/rest/services/Specialty/DeLorme_World_Base_Map/MapServer/tile/{z}/{y}/{x}',
minZoom: 1,
maxZoom: 11,
attribution: 'Tiles &copy; Esri &mdash; Copyright: &copy;2012 DeLorme',
},
"CyclOSM": {
urlTemplate: 'https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png',
minZoom: 1,
maxZoom: 20,
attribution: '<a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> | Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}
}

View File

@@ -1,5 +1,6 @@
export class Control {
#container: HTMLElement | null;
expectedValue: any = null;
constructor(ID: string) {
this.#container = document.getElementById(ID);
@@ -18,4 +19,16 @@ export class Control {
getContainer() {
return this.#container;
}
setExpectedValue(expectedValue: any) {
this.expectedValue = expectedValue;
}
resetExpectedValue() {
this.expectedValue = null;
}
checkExpectedValue(value: any) {
return this.expectedValue === null || value === this.expectedValue;
}
}

View File

@@ -19,14 +19,12 @@ export class Dropdown {
}
this.#value.addEventListener("click", (ev) => {
this.#element.classList.toggle("is-open");
this.#options.classList.toggle("scrollbar-visible", this.#options.scrollHeight > this.#options.clientHeight);
this.#clip();
this.#toggle();
});
document.addEventListener("click", (ev) => {
if (!(this.#value.contains(ev.target as Node) || this.#options.contains(ev.target as Node) || this.#element.contains(ev.target as Node))) {
this.#element.classList.remove("is-open");
this.#close();
}
});
@@ -46,12 +44,7 @@ export class Dropdown {
button.addEventListener("click", (e: MouseEvent) => {
e.stopPropagation();
this.#value = document.createElement("div");
this.#value.classList.add("ol-ellipsed");
this.#value.innerText = option;
this.#close();
this.#callback(option, e);
this.#index = idx;
this.selectValue(idx);
});
return div;
}));
@@ -113,6 +106,8 @@ export class Dropdown {
#open() {
this.#element.classList.add("is-open");
this.#options.classList.toggle("scrollbar-visible", this.#options.scrollHeight > this.#options.clientHeight);
this.#clip();
}
#toggle() {

View File

@@ -5,6 +5,8 @@ import { aircraftDatabase } from "../units/aircraftdatabase";
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
import { ContextMenu } from "./contextmenu";
import { Dropdown } from "./dropdown";
import { Switch } from "./switch";
import { Slider } from "./slider";
export interface SpawnOptions {
role: string;
@@ -13,24 +15,29 @@ export interface SpawnOptions {
coalition: string;
loadout: string | null;
airbaseName: string | null;
altitude: number | null;
}
export class MapContextMenu extends ContextMenu {
#coalitionSwitch: Switch;
#aircraftRoleDropdown: Dropdown;
#aircraftTypeDropdown: Dropdown;
#aircraftLoadoutDropdown: Dropdown;
#aircrafSpawnAltitudeSlider: Slider;
#groundUnitRoleDropdown: Dropdown;
#groundUnitTypeDropdown: Dropdown;
#spawnOptions: SpawnOptions = { role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null };
#spawnOptions: SpawnOptions = { role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null, altitude: 20000 };
constructor(id: string) {
super(id);
this.getContainer()?.querySelector("#context-menu-switch")?.addEventListener('click', (e) => this.#onToggleLeftClick(e));
this.getContainer()?.querySelector("#context-menu-switch")?.addEventListener('contextmenu', (e) => this.#onToggleRightClick(e));
this.#coalitionSwitch = new Switch("coalition-switch", this.#onSwitchClick);
this.#coalitionSwitch.setValue(false);
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick(e));
this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role));
this.#aircraftTypeDropdown = new Dropdown("aircraft-type-options", (type: string) => this.#setAircraftType(type));
this.#aircraftLoadoutDropdown = new Dropdown("loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout));
this.#aircrafSpawnAltitudeSlider = new Slider("aircraft-spawn-altitude-slider", 0, 50000, "ft", (value: number) => {this.#spawnOptions.altitude = value;});
this.#groundUnitRoleDropdown = new Dropdown("ground-unit-role-options", (role: string) => this.#setGroundUnitRole(role));
this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type));
@@ -61,6 +68,10 @@ export class MapContextMenu extends ContextMenu {
spawnSmoke(e.detail.color, this.getLatLng());
});
this.#aircrafSpawnAltitudeSlider.setIncrement(500);
this.#aircrafSpawnAltitudeSlider.setValue(20000);
this.#aircrafSpawnAltitudeSlider.setActive(true);
this.hide();
}
@@ -102,26 +113,13 @@ export class MapContextMenu extends ContextMenu {
this.#spawnOptions.latlng = latlng;
}
#onToggleLeftClick(e: any) {
if (this.getContainer() != null) {
if (e.srcElement.dataset.activeCoalition == "blue")
setActiveCoalition("neutral");
else if (e.srcElement.dataset.activeCoalition == "neutral")
setActiveCoalition("red");
else
setActiveCoalition("blue");
}
#onSwitchClick(value: boolean) {
value? setActiveCoalition("red"): setActiveCoalition("blue");
}
#onToggleRightClick(e: any) {
if (this.getContainer() != null) {
if (e.srcElement.dataset.activeCoalition == "red")
setActiveCoalition("neutral");
else if (e.srcElement.dataset.activeCoalition == "neutral")
setActiveCoalition("blue");
else
setActiveCoalition("red");
}
#onSwitchRightClick(e: any) {
this.#coalitionSwitch.setValue(undefined);
setActiveCoalition("neutral");
}
/********* Aircraft spawn menu *********/

View File

@@ -2,38 +2,38 @@ import { zeroPad } from "../other/utils";
import { Control } from "./control";
export class Slider extends Control {
#callback: CallableFunction;
#callback: CallableFunction | null = null;
#slider: HTMLInputElement | null = null;
#valueText: HTMLElement | null = null;
#minValue: number;
#maxValue: number;
#increment: number;
#minValue: number = 0;
#maxValue: number = 0;
#increment: number = 0;
#minMaxValueDiv: HTMLElement | null = null;
#unit: string;
#unitOfMeasure: string;
#dragged: boolean = false;
#value: number = 0;
constructor(ID: string, minValue: number, maxValue: number, unit: string, callback: CallableFunction) {
constructor(ID: string, minValue: number, maxValue: number, unitOfMeasure: string, callback: CallableFunction) {
super(ID);
this.#callback = callback;
this.#minValue = minValue;
this.#maxValue = maxValue;
this.#increment = 1;
this.#unit = unit;
this.#callback = callback;
this.#unitOfMeasure = unitOfMeasure;
this.#slider = this.getContainer()?.querySelector("input") as HTMLInputElement;
if (this.#slider != null) {
this.#slider.addEventListener("input", (e: any) => this.#onInput());
this.#slider.addEventListener("input", (e: any) => this.#update());
this.#slider.addEventListener("mousedown", (e: any) => this.#onStart());
this.#slider.addEventListener("mouseup", (e: any) => this.#onFinalize());
}
this.#valueText = this.getContainer()?.querySelector(".ol-slider-value") as HTMLElement;
this.#minMaxValueDiv = this.getContainer()?.querySelector(".ol-slider-min-max") as HTMLElement;
this.setIncrement(1);
this.setMinMax(minValue, maxValue);
}
setActive(newActive: boolean) {
if (!this.#dragged) {
if (!this.getDragged()) {
this.getContainer()?.classList.toggle("active", newActive);
if (!newActive && this.#valueText != null)
this.#valueText.innerText = "Mixed values";
@@ -41,27 +41,31 @@ export class Slider extends Control {
}
setMinMax(newMinValue: number, newMaxValue: number) {
this.#minValue = newMinValue;
this.#maxValue = newMaxValue;
this.#updateMax();
if (this.#minMaxValueDiv != null) {
this.#minMaxValueDiv.setAttribute('data-min-value', `${this.#minValue}${this.#unit}`);
this.#minMaxValueDiv.setAttribute('data-max-value', `${this.#maxValue}${this.#unit}`);
if (this.#minValue != newMinValue || this.#maxValue != newMaxValue) {
this.#minValue = newMinValue;
this.#maxValue = newMaxValue;
this.#updateMaxValue();
if (this.#minMaxValueDiv != null) {
this.#minMaxValueDiv.setAttribute('data-min-value', `${this.#minValue}${this.#unitOfMeasure}`);
this.#minMaxValueDiv.setAttribute('data-max-value', `${this.#maxValue}${this.#unitOfMeasure}`);
}
}
}
setIncrement(newIncrement: number) {
this.#increment = newIncrement;
this.#updateMax();
if (this.#increment != newIncrement) {
this.#increment = newIncrement;
this.#updateMaxValue();
}
}
setValue(newValue: number) {
// Disable value setting if the user is dragging the element
if (!this.#dragged) {
setValue(newValue: number, ignoreExpectedValue: boolean = true) {
if (!this.getDragged() && (ignoreExpectedValue || this.checkExpectedValue(newValue))) {
this.#value = newValue;
if (this.#slider != null)
this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max));
this.#onValue()
this.#update();
}
}
@@ -69,45 +73,51 @@ export class Slider extends Control {
return this.#value;
}
setDragged(newDragged: boolean) {
this.#dragged = newDragged;
}
getDragged() {
return this.#dragged;
}
#updateMax() {
#updateMaxValue() {
var oldValue = this.getValue();
if (this.#slider != null)
this.#slider.max = String((this.#maxValue - this.#minValue) / this.#increment);
this.setValue(oldValue);
}
#onValue() {
#update() {
if (this.#valueText != null && this.#slider != null)
{
/* Update the text value */
var value = this.#minValue + Math.round(parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue));
var strValue = String(value);
if (value > 1000)
strValue = String(Math.floor(value / 1000)) + "," + zeroPad(value - Math.floor(value / 1000) * 1000, 3);
this.#valueText.innerText = strValue + " " + this.#unit.toUpperCase();
this.#valueText.innerText = `${strValue} ${this.#unitOfMeasure.toUpperCase()}`;
/* Update the position of the slider */
var percentValue = parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * 90 + 5;
this.#slider.style.background = 'linear-gradient(to right, var(--accent-light-blue) 5%, var(--accent-light-blue) ' + percentValue + '%, var(--background-grey) ' + percentValue + '%, var(--background-grey) 100%)'
this.#slider.style.background = `linear-gradient(to right, var(--accent-light-blue) 5%, var(--accent-light-blue) ${percentValue}%, var(--background-grey) ${percentValue}%, var(--background-grey) 100%)`
}
this.setActive(true);
}
#onInput() {
this.#onValue();
}
#onStart() {
this.#dragged = true;
this.setDragged(true);
}
#onFinalize() {
this.#dragged = false;
this.setDragged(false);
if (this.#slider != null) {
this.#value = this.#minValue + parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue);
this.#callback(this.getValue());
this.resetExpectedValue();
this.setValue(this.#minValue + parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue));
if (this.#callback) {
this.#callback(this.getValue());
this.setExpectedValue(this.getValue());
}
}
}
}

View File

@@ -1,12 +1,16 @@
import { Control } from "./control";
export class Switch extends Control {
#value: boolean = false;
constructor(ID: string, initialValue?: boolean) {
#value: boolean | undefined = false;
#callback: CallableFunction | null = null;
constructor(ID: string, callback: CallableFunction, initialValue?: boolean) {
super(ID);
this.getContainer()?.addEventListener('click', (e) => this.#onToggle());
this.setValue(initialValue !== undefined? initialValue: true);
this.#callback = callback;
/* Add the toggle itself to the document */
const container = this.getContainer();
if (container != undefined){
@@ -14,14 +18,17 @@ export class Switch extends Control {
const height = getComputedStyle(container).height;
var el = document.createElement("div");
el.classList.add("ol-switch-fill");
el.style.setProperty("--width", width? width: "0px");
el.style.setProperty("--height", height? height: "0px");
el.style.setProperty("--width", width? width: "0");
el.style.setProperty("--height", height? height: "0");
this.getContainer()?.appendChild(el);
}
}
setValue(value: boolean) {
this.#value = value;
this.getContainer()?.setAttribute("data-value", String(value));
setValue(newValue: boolean | undefined, ignoreExpectedValue: boolean = true) {
if (ignoreExpectedValue || this.checkExpectedValue(newValue)) {
this.#value = newValue;
this.getContainer()?.setAttribute("data-value", String(newValue));
}
}
getValue() {
@@ -29,6 +36,11 @@ export class Switch extends Control {
}
#onToggle() {
this.resetExpectedValue();
this.setValue(!this.getValue());
if (this.#callback) {
this.#callback(this.getValue());
this.setExpectedValue(this.getValue());
}
}
}

View File

@@ -98,8 +98,6 @@ function readConfig(config: any) {
}
function setupEvents() {
window.onanimationiteration = console.log;
/* Generic clicks */
document.addEventListener("click", (ev) => {
if (ev instanceof MouseEvent && ev.target instanceof HTMLElement) {

View File

@@ -6,7 +6,7 @@ export class DestinationPreviewMarker extends CustomMarker {
this.setIcon(new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 26],
className: "leaflet-destination-preview"
className: "leaflet-destination-preview",
}));
var el = document.createElement("div");
el.classList.add("ol-destination-preview-icon");

View File

@@ -12,6 +12,7 @@ import { DestinationPreviewMarker } from "./destinationpreviewmarker";
import { TemporaryUnitMarker } from "./temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector'
import { layers as mapLayers, mapBounds, minimapBoundaries } from "../constants/constants";
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
@@ -58,10 +59,10 @@ export class Map extends L.Map {
super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
this.setView([37.23, -115.8], 10);
this.setLayer("ArcGIS Satellite");
this.setLayer(Object.keys(mapLayers)[0]);
/* Minimap */
var minimapLayer = new L.TileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { minZoom: 0, maxZoom: 13 });
var minimapLayer = new L.TileLayer(mapLayers[Object.keys(mapLayers)[0] as keyof typeof mapLayers].urlTemplate, { minZoom: 0, maxZoom: 13 });
this.#miniMapLayerGroup = new L.LayerGroup([minimapLayer]);
var miniMapPolyline = new L.Polyline(this.#getMinimapBoundaries(), { color: '#202831' });
miniMapPolyline.addTo(this.#miniMapLayerGroup);
@@ -124,59 +125,30 @@ export class Map extends L.Map {
}
setLayer(layerName: string) {
if (this.#layer != null) {
if (this.#layer != null)
this.removeLayer(this.#layer)
if (layerName in mapLayers){
const layerData = mapLayers[layerName as keyof typeof mapLayers];
var options: L.TileLayerOptions = {
attribution: layerData.attribution,
minZoom: layerData.minZoom,
maxZoom: layerData.maxZoom
};
this.#layer = new L.TileLayer(layerData.urlTemplate, options);
}
if (layerName == "ArcGIS Satellite") {
this.#layer = L.tileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", {
attribution: "Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
});
}
else if (layerName == "USGS Topo") {
this.#layer = L.tileLayer('https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}', {
maxZoom: 20,
attribution: 'Tiles courtesy of the <a href="https://usgs.gov/">U.S. Geological Survey</a>'
});
}
else if (layerName == "OpenStreetMap Mapnik") {
this.#layer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
}
else if (layerName == "OPENVKarte") {
this.#layer = L.tileLayer('https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png', {
maxZoom: 18,
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 &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
}
else if (layerName == "Esri.DeLorme") {
this.#layer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Specialty/DeLorme_World_Base_Map/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Copyright: &copy;2012 DeLorme',
minZoom: 1,
maxZoom: 11
});
}
else if (layerName == "CyclOSM") {
this.#layer = L.tileLayer('https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', {
maxZoom: 20,
attribution: '<a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> | Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
}
this.#layer?.addTo(this);
}
getLayers() {
return ["ArcGIS Satellite", "USGS Topo", "OpenStreetMap Mapnik", "OPENVKarte", "Esri.DeLorme", "CyclOSM"]
return Object.keys(mapLayers);
}
/* State machine */
setState(state: string) {
this.#state = state;
if (this.#state === IDLE) {
L.DomUtil.removeClass(this.getContainer(), 'crosshair-cursor-enabled');
/* Remove all the destination preview markers */
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => {
this.removeLayer(marker);
@@ -188,8 +160,6 @@ export class Map extends L.Map {
this.#destinationRotationCenter = null;
}
else if (this.#state === MOVE_UNIT) {
L.DomUtil.addClass(this.getContainer(), 'crosshair-cursor-enabled');
/* Remove all the exising destination preview markers */
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => {
this.removeLayer(marker);
@@ -199,7 +169,7 @@ export class Map extends L.Map {
if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 1 && getUnitsManager().getSelectedUnits({ excludeHumans: true }).length < 20) {
/* Create the unit destination preview markers */
this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true }).map((unit: Unit) => {
var marker = new DestinationPreviewMarker(this.getMouseCoordinates());
var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false});
marker.addTo(this);
return marker;
})
@@ -295,20 +265,9 @@ export class Map extends L.Map {
setTheatre(theatre: string) {
var bounds = new L.LatLngBounds([-90, -180], [90, 180]);
var miniMapZoom = 5;
if (theatre == "Syria")
bounds = new L.LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]);
else if (theatre == "MarianaIslands")
bounds = new L.LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]);
else if (theatre == "Nevada")
bounds = new L.LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805])
else if (theatre == "PersianGulf")
bounds = new L.LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594])
else if (theatre == "Falklands") {
// TODO
}
else if (theatre == "Caucasus") {
bounds = new L.LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946])
miniMapZoom = 4;
if (theatre in mapBounds) {
bounds = mapBounds[theatre as keyof typeof mapBounds].bounds;
miniMapZoom = mapBounds[theatre as keyof typeof mapBounds].zoom;
}
this.setView(bounds.getCenter(), 8);
@@ -426,7 +385,7 @@ export class Map extends L.Map {
if (!e.originalEvent.ctrlKey) {
getUnitsManager().selectedUnitsClearDestinations();
}
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, !e.originalEvent.shiftKey, this.#destinationGroupRotation)
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, e.originalEvent.shiftKey, this.#destinationGroupRotation)
this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
@@ -481,48 +440,13 @@ export class Map extends L.Map {
#getMinimapBoundaries() {
/* Draw the limits of the maps in the minimap*/
return [[ // NTTR
new L.LatLng(39.7982463, -119.985425),
new L.LatLng(34.4037128, -119.7806729),
new L.LatLng(34.3483316, -112.4529351),
new L.LatLng(39.7372411, -112.1130805),
new L.LatLng(39.7982463, -119.985425)
],
[ // Syria
new L.LatLng(37.3630556, 29.2686111),
new L.LatLng(31.8472222, 29.8975),
new L.LatLng(32.1358333, 42.1502778),
new L.LatLng(37.7177778, 42.3716667),
new L.LatLng(37.3630556, 29.2686111)
],
[ // Caucasus
new L.LatLng(39.6170191, 27.634935),
new L.LatLng(38.8735863, 47.1423108),
new L.LatLng(47.3907982, 49.3101946),
new L.LatLng(48.3955879, 26.7753625),
new L.LatLng(39.6170191, 27.634935)
],
[ // Persian Gulf
new L.LatLng(32.9355285, 46.5623682),
new L.LatLng(21.729393, 47.572675),
new L.LatLng(21.8501348, 63.9734737),
new L.LatLng(33.131584, 64.7313594),
new L.LatLng(32.9355285, 46.5623682)
],
[ // Marianas
new L.LatLng(22.09, 135.0572222),
new L.LatLng(10.5777778, 135.7477778),
new L.LatLng(10.7725, 149.3918333),
new L.LatLng(22.5127778, 149.5427778),
new L.LatLng(22.09, 135.0572222)
]
];
return minimapBoundaries;
}
#updateDestinationPreview(e: any) {
Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(), this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
if (idx < this.#destinationPreviewMarkers.length)
this.#destinationPreviewMarkers[idx].setLatLng(!e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates());
this.#destinationPreviewMarkers[idx].setLatLng(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates());
})
}

View File

@@ -37,7 +37,7 @@ export class MissionHandler
for (let idx in data.airbases)
{
var airbase = data.airbases[idx]
if (this.#airbases[idx] === undefined)
if (this.#airbases[idx] === undefined && airbase.callsign != '')
{
this.#airbases[idx] = new Airbase({
position: new LatLng(airbase.latitude, airbase.longitude),
@@ -45,7 +45,8 @@ export class MissionHandler
}).addTo(getMap());
this.#airbases[idx].on('contextmenu', (e) => this.#onAirbaseClick(e));
}
if (airbase.latitude && airbase.longitude && airbase.coalition)
if (this.#airbases[idx] != undefined && airbase.latitude && airbase.longitude && airbase.coalition)
{
this.#airbases[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude));
this.#airbases[idx].setCoalition(airbase.coalition);

View File

@@ -3,38 +3,21 @@ import { getUnitsManager } from "..";
import { Dropdown } from "../controls/dropdown";
import { Slider } from "../controls/slider";
import { aircraftDatabase } from "../units/aircraftdatabase";
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
import { Aircraft, GroundUnit, Unit } from "../units/unit";
import { UnitDatabase } from "../units/unitdatabase";
import { Unit } from "../units/unit";
import { Panel } from "./panel";
import { Switch } from "../controls/switch";
const ROEs: string[] = ["Hold", "Return", "Designated", "Free"];
const reactionsToThreat: string[] = ["None", "Manoeuvre", "Passive", "Evade"];
const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"];
const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"];
const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"];
const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar OFF, no ECM)", "Attack (Radar only for targeting, ECM only if locked)", "Defend (Radar for searching, ECM if locked)", "Always on (Radar and ECM always on)"];
const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 };
const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helicopter: 0 };
const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 };
const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 };
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants";
export class UnitControlPanel extends Panel {
#altitudeSlider: Slider;
#altitudeTypeSwitch: Switch;
#airspeedSlider: Slider;
#airspeedTypeSwitch: Switch;
#speedSlider: Slider;
#speedTypeSwitch: Switch;
#onOffSwitch: Switch;
#followRoadsSwitch: Switch;
#TACANXYDropdown: Dropdown;
#radioDecimalsDropdown: Dropdown;
#radioCallsignDropdown: Dropdown;
#expectedAltitude: number = -1;
#expectedSpeed: number = -1;
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
#advancedSettingsDialog: HTMLElement;
@@ -42,17 +25,11 @@ export class UnitControlPanel extends Panel {
super(ID);
/* Unit control sliders */
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => {
this.#expectedAltitude = value;
getUnitsManager().selectedUnitsSetAltitude(value * 0.3048);
});
this.#altitudeTypeSwitch = new Switch("altitude-type-switch");
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getUnitsManager().selectedUnitsSetAltitude(value * 0.3048); });
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetAltitudeType(value? "AGL": "ASL"); });
this.#airspeedSlider = new Slider("airspeed-slider", 0, 100, "kts", (value: number) => {
this.#expectedSpeed = value;
getUnitsManager().selectedUnitsSetSpeed(value / 1.94384);
});
this.#airspeedTypeSwitch = new Switch("airspeed-type-switch");
this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getUnitsManager().selectedUnitsSetSpeed(value / 1.94384); });
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "GS": "CAS"); });
/* Option buttons */
this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => {
@@ -72,7 +49,14 @@ export class UnitControlPanel extends Panel {
this.getElement().querySelector("#emissions-countermeasures-buttons-container")?.append(...this.#optionButtons["emissionsCountermeasures"]);
/* On off switch */
this.#onOffSwitch = new Switch("on-off-switch");
this.#onOffSwitch = new Switch("on-off-switch", (value: boolean) => {
getUnitsManager().selectedUnitsSetOnOff(value);
});
/* Follow roads switch */
this.#followRoadsSwitch = new Switch("follow-roads-switch", (value: boolean) => {
getUnitsManager().selectedUnitsSetFollowRoads(value);
});
/* Advanced settings dialog */
this.#advancedSettingsDialog = <HTMLElement> document.querySelector("#advanced-settings-dialog");
@@ -98,26 +82,18 @@ export class UnitControlPanel extends Panel {
this.hide();
}
// Do this after panel is hidden (make sure there's a reset)
hide() {
super.hide();
this.#expectedAltitude = -1;
this.#expectedSpeed = -1;
show() {
super.show();
this.#speedTypeSwitch.resetExpectedValue();
this.#altitudeTypeSwitch.resetExpectedValue();
this.#onOffSwitch.resetExpectedValue();
this.#followRoadsSwitch.resetExpectedValue();
}
addButtons() {
var units = getUnitsManager().getSelectedUnits();
if (units.length < 20) {
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => {
let database: UnitDatabase | null;
if (unit instanceof Aircraft)
database = aircraftDatabase;
else if (unit instanceof GroundUnit)
database = groundUnitsDatabase;
else
database = null; // TODO add databases for other unit types
var button = document.createElement("button");
var callsign = unit.getBaseData().unitName || "";
var label = unit.getDatabase()?.getByName(unit.getBaseData().name)?.label || unit.getBaseData().name;
@@ -150,42 +126,42 @@ export class UnitControlPanel extends Panel {
if (element != null && units.length > 0) {
/* Toggle visibility of control elements */
element.toggleAttribute("data-show-categories-tooltip", selectedUnitsTypes.length > 1);
element.toggleAttribute("data-show-airspeed-slider", selectedUnitsTypes.length == 1);
element.toggleAttribute("data-show-speed-slider", selectedUnitsTypes.length == 1);
element.toggleAttribute("data-show-altitude-slider", selectedUnitsTypes.length == 1 && (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")));
element.toggleAttribute("data-show-roe", true);
element.toggleAttribute("data-show-threat", (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")) && !(selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-emissions-countermeasures", (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")) && !(selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-on-off", (selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit")) && !(selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")));
element.toggleAttribute("data-show-follow-roads", (selectedUnitsTypes.length == 1 && selectedUnitsTypes.includes("GroundUnit")));
element.toggleAttribute("data-show-advanced-settings-button", units.length == 1);
/* Flight controls */
var targetAltitude = getUnitsManager().getSelectedUnitsTargetAltitude();
var targetSpeed = getUnitsManager().getSelectedUnitsTargetSpeed();
var targetAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().targetAltitude});
var targetAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().targetAltitudeType});
var targetSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().targetSpeed});
var targetSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().targetSpeedType});
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().onOff});
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().followRoads});
if (selectedUnitsTypes.length == 1) {
this.#airspeedSlider.setMinMax(minSpeedValues[selectedUnitsTypes[0]], maxSpeedValues[selectedUnitsTypes[0]]);
this.#altitudeTypeSwitch.setValue(targetAltitudeType != undefined? targetAltitudeType == "AGL": undefined, false);
this.#speedTypeSwitch.setValue(targetSpeedType != undefined? targetSpeedType == "GS": undefined, false);
this.#speedSlider.setMinMax(minSpeedValues[selectedUnitsTypes[0]], maxSpeedValues[selectedUnitsTypes[0]]);
this.#altitudeSlider.setMinMax(minAltitudeValues[selectedUnitsTypes[0]], maxAltitudeValues[selectedUnitsTypes[0]]);
this.#airspeedSlider.setIncrement(speedIncrements[selectedUnitsTypes[0]]);
this.#speedSlider.setIncrement(speedIncrements[selectedUnitsTypes[0]]);
this.#altitudeSlider.setIncrement(altitudeIncrements[selectedUnitsTypes[0]]);
this.#airspeedSlider.setActive(targetSpeed != undefined);
if (targetSpeed != undefined) {
targetSpeed *= 1.94384;
if (this.#updateCanSetSpeedSlider(targetSpeed)) {
this.#airspeedSlider.setValue(targetSpeed);
}
}
this.#speedSlider.setActive(targetSpeed != undefined);
if (targetSpeed != undefined)
this.#speedSlider.setValue(targetSpeed * 1.94384, false);
this.#altitudeSlider.setActive(targetAltitude != undefined);
if (targetAltitude != undefined) {
targetAltitude /= 0.3048;
if (this.#updateCanSetAltitudeSlider(targetAltitude)) {
this.#altitudeSlider.setValue(targetAltitude);
}
}
if (targetAltitude != undefined)
this.#altitudeSlider.setValue(targetAltitude / 0.3048, false);
}
else {
this.#airspeedSlider.setActive(false);
this.#speedSlider.setActive(false);
this.#altitudeSlider.setActive(false);
}
@@ -201,27 +177,13 @@ export class UnitControlPanel extends Panel {
this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().emissionsCountermeasures === button.value))
});
this.#onOffSwitch.setValue(onOff, false);
this.#followRoadsSwitch.setValue(followRoads, false);
}
}
}
/* Update function will only be allowed to update the sliders once it's matched the expected value for the first time (due to lag of Ajax request) */
#updateCanSetAltitudeSlider(altitude: number) {
if (this.#expectedAltitude < 0 || altitude === this.#expectedAltitude) {
this.#expectedAltitude = -1;
return true;
}
return false;
}
#updateCanSetSpeedSlider(altitude: number) {
if (this.#expectedSpeed < 0 || altitude === this.#expectedSpeed) {
this.#expectedSpeed = -1;
return true;
}
return false;
}
#updateAdvancedSettingsDialog(units: Unit[])
{
if (units.length == 1)

View File

@@ -16,7 +16,7 @@ export class UnitInfoPanel extends Panel {
#latitude: HTMLElement;
#longitude: HTMLElement;
#loadoutContainer: HTMLElement;
#silhouette: HTMLElement;
#silhouette: HTMLImageElement;
#unitControl: HTMLElement;
#unitLabel: HTMLElement;
#unitName: HTMLElement;
@@ -24,21 +24,21 @@ export class UnitInfoPanel extends Panel {
constructor(ID: string) {
super(ID);
this.#altitude = <HTMLElement>(this.getElement().querySelector("#altitude"));
this.#currentTask = <HTMLElement>(this.getElement().querySelector("#current-task"));
this.#groundSpeed = <HTMLElement>(this.getElement().querySelector("#ground-speed"));
this.#fuelBar = <HTMLElement>(this.getElement().querySelector("#fuel-bar"));
this.#fuelPercentage = <HTMLElement>(this.getElement().querySelector("#fuel-percentage"));
this.#groupName = <HTMLElement>(this.getElement().querySelector("#group-name"));
this.#heading = <HTMLElement>(this.getElement().querySelector("#heading"));
this.#name = <HTMLElement>(this.getElement().querySelector("#name"));
this.#latitude = <HTMLElement>(this.getElement().querySelector("#latitude"));
this.#loadoutContainer = <HTMLElement>(this.getElement().querySelector("#loadout-container"));
this.#longitude = <HTMLElement>(this.getElement().querySelector("#longitude"));
this.#silhouette = <HTMLElement>(this.getElement().querySelector("#loadout-silhouette"));
this.#unitControl = <HTMLElement>(this.getElement().querySelector("#unit-control"));
this.#unitLabel = <HTMLElement>(this.getElement().querySelector("#unit-label"));
this.#unitName = <HTMLElement>(this.getElement().querySelector("#unit-name"));
this.#altitude = (this.getElement().querySelector("#altitude")) as HTMLElement;
this.#currentTask = (this.getElement().querySelector("#current-task")) as HTMLElement;
this.#groundSpeed = (this.getElement().querySelector("#ground-speed")) as HTMLElement;
this.#fuelBar = (this.getElement().querySelector("#fuel-bar")) as HTMLElement;
this.#fuelPercentage = (this.getElement().querySelector("#fuel-percentage")) as HTMLElement;
this.#groupName = (this.getElement().querySelector("#group-name")) as HTMLElement;
this.#heading = (this.getElement().querySelector("#heading")) as HTMLElement;
this.#name = (this.getElement().querySelector("#name")) as HTMLElement;
this.#latitude = (this.getElement().querySelector("#latitude")) as HTMLElement;
this.#loadoutContainer = (this.getElement().querySelector("#loadout-container")) as HTMLElement;
this.#longitude = (this.getElement().querySelector("#longitude")) as HTMLElement;
this.#silhouette = (this.getElement().querySelector("#loadout-silhouette")) as HTMLImageElement;
this.#unitControl = (this.getElement().querySelector("#unit-control")) as HTMLElement;
this.#unitLabel = (this.getElement().querySelector("#unit-label")) as HTMLElement;
this.#unitName = (this.getElement().querySelector("#unit-name")) as HTMLElement;
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => this.#onUnitsSelection(e.detail));
document.addEventListener("unitsDeselection", (e: CustomEvent<Unit[]>) => this.#onUnitsDeselection(e.detail));
@@ -69,18 +69,15 @@ export class UnitInfoPanel extends Panel {
this.#currentTask.dataset.currentTask = unit.getTaskData().currentTask !== ""? unit.getTaskData().currentTask: "No task";
this.#currentTask.dataset.coalition = unit.getMissionData().coalition;
this.#silhouette.setAttribute( "style", `--loadout-background-image:url('/images/units/${aircraftDatabase.getByName( baseData.name )?.filename}');` );;
this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(baseData.name)?.filename}`;
this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(baseData.name)?.filename == undefined || unit.getDatabase()?.getByName(baseData.name)?.filename == '');
/* Add the loadout elements */
const items = <HTMLElement>this.#loadoutContainer.querySelector( "#loadout-items" );
if ( items ) {
const ammo = Object.values( unit.getMissionData().ammo );
if ( ammo.length > 0 ) {
items.replaceChildren(...Object.values(unit.getMissionData().ammo).map(
(ammo: any) => {
var el = document.createElement("div");
@@ -91,25 +88,28 @@ export class UnitInfoPanel extends Panel {
));
} else {
items.innerText = "No loadout";
items.innerText = "No loadout";
}
}
}
}
#onUnitsSelection(units: Unit[]){
if (units.length == 1)
{
this.show();
this.#onUnitUpdate(units[0]);
}
else
this.hide();
}
#onUnitsDeselection(units: Unit[]){
if (units.length == 1)
{
this.show();
this.#onUnitUpdate(units[0]);
}
else
this.hide();
}

View File

@@ -29,7 +29,7 @@ export function setCredentials(newUsername: string, newCredentials: string) {
credentials = newCredentials;
}
export function GET(callback: CallableFunction, uri: string, options?: {time?: number}) {
export function GET(callback: CallableFunction, uri: string, options?: { time?: number }) {
var xmlHttp = new XMLHttpRequest();
/* Assemble the request options string */
@@ -37,15 +37,14 @@ export function GET(callback: CallableFunction, uri: string, options?: {time?: n
if (options?.time != undefined)
optionsString = `time=${options.time}`;
xmlHttp.open("GET", `${demoEnabled? DEMO_ADDRESS: REST_ADDRESS}/${uri}${optionsString? `?${optionsString}`: ''}`, true);
xmlHttp.open("GET", `${demoEnabled ? DEMO_ADDRESS : REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true);
if (credentials)
xmlHttp.setRequestHeader("Authorization", "Basic " + credentials);
xmlHttp.onload = function (e) {
if (xmlHttp.status == 200) {
var data = JSON.parse(xmlHttp.responseText);
if (uri !== UNITS_URI || (options?.time == 0) || parseInt(data.time) > lastUpdateTime)
{
if (uri !== UNITS_URI || (options?.time == 0) || parseInt(data.time) > lastUpdateTime) {
callback(data);
lastUpdateTime = parseInt(data.time);
if (isNaN(lastUpdateTime))
@@ -66,14 +65,14 @@ export function GET(callback: CallableFunction, uri: string, options?: {time?: n
xmlHttp.send(null);
}
export function POST(request: object, callback: CallableFunction){
export function POST(request: object, callback: CallableFunction) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("PUT", demoEnabled? DEMO_ADDRESS: REST_ADDRESS);
xmlHttp.open("PUT", demoEnabled ? DEMO_ADDRESS : REST_ADDRESS);
xmlHttp.setRequestHeader("Content-Type", "application/json");
if (credentials)
xmlHttp.setRequestHeader("Authorization", "Basic " + credentials);
xmlHttp.onreadystatechange = () => {
callback();
xmlHttp.onreadystatechange = () => {
callback();
};
xmlHttp.send(JSON.stringify(request));
}
@@ -113,7 +112,7 @@ export function getMission(callback: CallableFunction) {
}
export function getUnits(callback: CallableFunction, refresh: boolean = false) {
GET(callback, `${UNITS_URI}`, {time: refresh? 0: lastUpdateTime});
GET(callback, `${UNITS_URI}`, { time: refresh ? 0 : lastUpdateTime });
}
export function addDestination(ID: number, path: any) {
@@ -135,7 +134,7 @@ export function spawnGroundUnit(spawnOptions: SpawnOptions) {
}
export function spawnAircraft(spawnOptions: SpawnOptions) {
var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "payloadName": spawnOptions.loadout != null? spawnOptions.loadout: "", "airbaseName": spawnOptions.airbaseName != null? spawnOptions.airbaseName: ""};
var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "altitude": spawnOptions.altitude, "payloadName": spawnOptions.loadout != null ? spawnOptions.loadout : "", "airbaseName": spawnOptions.airbaseName != null ? spawnOptions.airbaseName : "" };
var data = { "spawnAir": command }
POST(data, () => { });
}
@@ -146,12 +145,12 @@ export function attackUnit(ID: number, targetID: number) {
POST(data, () => { });
}
export function followUnit(ID: number, targetID: number, offset: {"x": number, "y": number, "z": number}) {
export function followUnit(ID: number, targetID: number, offset: { "x": number, "y": number, "z": number }) {
// X: front-rear, positive front
// Y: top-bottom, positive bottom
// Z: left-right, positive right
var command = { "ID": ID, "targetID": targetID, "offsetX": offset["x"], "offsetY": offset["y"], "offsetZ": offset["z"]};
var command = { "ID": ID, "targetID": targetID, "offsetX": offset["x"], "offsetY": offset["y"], "offsetZ": offset["z"] };
var data = { "followUnit": command }
POST(data, () => { });
}
@@ -162,8 +161,8 @@ export function cloneUnit(ID: number, latlng: L.LatLng) {
POST(data, () => { });
}
export function deleteUnit(ID: number) {
var command = { "ID": ID};
export function deleteUnit(ID: number, explosion: boolean) {
var command = { "ID": ID, "explosion": explosion };
var data = { "deleteUnit": command }
POST(data, () => { });
}
@@ -175,50 +174,74 @@ export function landAt(ID: number, latlng: L.LatLng) {
}
export function changeSpeed(ID: number, speedChange: string) {
var command = {"ID": ID, "change": speedChange}
var data = {"changeSpeed": command}
var command = { "ID": ID, "change": speedChange }
var data = { "changeSpeed": command }
POST(data, () => { });
}
export function setSpeed(ID: number, speed: number) {
var command = {"ID": ID, "speed": speed}
var data = {"setSpeed": command}
var command = { "ID": ID, "speed": speed }
var data = { "setSpeed": command }
POST(data, () => { });
}
export function setSpeedType(ID: number, speedType: string) {
var command = { "ID": ID, "speedType": speedType }
var data = { "setSpeedType": command }
POST(data, () => { });
}
export function changeAltitude(ID: number, altitudeChange: string) {
var command = {"ID": ID, "change": altitudeChange}
var data = {"changeAltitude": command}
var command = { "ID": ID, "change": altitudeChange }
var data = { "changeAltitude": command }
POST(data, () => { });
}
export function setAltitudeType(ID: number, altitudeType: string) {
var command = { "ID": ID, "altitudeType": altitudeType }
var data = { "setAltitudeType": command }
POST(data, () => { });
}
export function setAltitude(ID: number, altitude: number) {
var command = {"ID": ID, "altitude": altitude}
var data = {"setAltitude": command}
var command = { "ID": ID, "altitude": altitude }
var data = { "setAltitude": command }
POST(data, () => { });
}
export function createFormation(ID: number, isLeader: boolean, wingmenIDs: number[]) {
var command = {"ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader}
var data = {"setLeader": command}
var command = { "ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader }
var data = { "setLeader": command }
POST(data, () => { });
}
export function setROE(ID: number, ROE: string) {
var command = {"ID": ID, "ROE": ROE}
var data = {"setROE": command}
var command = { "ID": ID, "ROE": ROE }
var data = { "setROE": command }
POST(data, () => { });
}
export function setReactionToThreat(ID: number, reactionToThreat: string) {
var command = {"ID": ID, "reactionToThreat": reactionToThreat}
var data = {"setReactionToThreat": command}
var command = { "ID": ID, "reactionToThreat": reactionToThreat }
var data = { "setReactionToThreat": command }
POST(data, () => { });
}
export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string) {
var command = {"ID": ID, "emissionsCountermeasures": emissionCountermeasure}
var data = {"setEmissionsCountermeasures": command}
var command = { "ID": ID, "emissionsCountermeasures": emissionCountermeasure }
var data = { "setEmissionsCountermeasures": command }
POST(data, () => { });
}
export function setOnOff(ID: number, onOff: boolean) {
var command = { "ID": ID, "onOff": onOff }
var data = { "setOnOff": command }
POST(data, () => { });
}
export function setFollowRoads(ID: number, followRoads: boolean) {
var command = { "ID": ID, "followRoads": followRoads }
var data = { "setFollowRoads": command }
POST(data, () => { });
}
@@ -228,14 +251,14 @@ export function refuel(ID: number) {
POST(data, () => { });
}
export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings)
{
var command = { "ID": ID,
"isTanker": isTanker,
"isAWACS": isAWACS,
"TACAN": TACAN,
"radio": radio,
"generalSettings": generalSettings
export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) {
var command = {
"ID": ID,
"isTanker": isTanker,
"isAWACS": isAWACS,
"TACAN": TACAN,
"radio": radio,
"generalSettings": generalSettings
};
var data = { "setAdvancedOptions": command };

View File

@@ -1,7 +1,7 @@
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map } from 'leaflet';
import { getMap, getUnitsManager } from '..';
import { rad2deg } from '../other/utils';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures } from '../server/server';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads } from '../server/server';
import { aircraftDatabase } from './aircraftdatabase';
import { groundUnitsDatabase } from './groundunitsdatabase';
import { CustomMarker } from '../map/custommarker';
@@ -49,9 +49,13 @@ export class Unit extends CustomMarker {
currentTask: "",
activePath: {},
targetSpeed: 0,
targetSpeedType: "GS",
targetAltitude: 0,
targetAltitudeType: "AGL",
isTanker: false,
isAWACS: false,
onOff: true,
followRoads: false
},
optionsData: {
ROE: "",
@@ -116,8 +120,6 @@ export class Unit extends CustomMarker {
/* Set the unit data */
this.setData(data);
}
getMarkerCategory() {
@@ -203,48 +205,19 @@ export class Unit extends CustomMarker {
const aliveChanged = (data.baseData != undefined && data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive);
var updateMarker = (positionChanged || headingChanged || aliveChanged || !getMap().hasLayer(this));
if (data.baseData != undefined) {
for (let key in this.#data.baseData)
if (key in data.baseData)
//@ts-ignore
this.#data.baseData[key] = data.baseData[key];
}
if (data.flightData != undefined) {
for (let key in this.#data.flightData)
if (key in data.flightData)
//@ts-ignore
this.#data.flightData[key] = data.flightData[key];
}
if (data.missionData != undefined) {
for (let key in this.#data.missionData)
if (key in data.missionData)
//@ts-ignore
this.#data.missionData[key] = data.missionData[key];
}
if (data.formationData != undefined) {
for (let key in this.#data.formationData)
if (key in data.formationData)
//@ts-ignore
this.#data.formationData[key] = data.formationData[key];
}
if (data.taskData != undefined) {
for (let key in this.#data.taskData)
if (key in data.taskData)
//@ts-ignore
this.#data.taskData[key] = data.taskData[key];
}
if (data.optionsData != undefined) {
for (let key in this.#data.optionsData)
if (key in data.optionsData)
//@ts-ignore
this.#data.optionsData[key] = data.optionsData[key];
}
/* Load the data from the received json */
Object.keys(this.#data).forEach((key1: string) => {
Object.keys(this.#data[key1 as keyof(UnitData)]).forEach((key2: string) => {
if (key1 in data && key2 in data[key1]) {
var value1 = this.#data[key1 as keyof(UnitData)];
var value2 = value1[key2 as keyof typeof value1];
if (typeof data[key1][key2] === typeof value2)
//@ts-ignore
this.#data[key1 as keyof(UnitData)][key2 as keyof typeof struct] = data[key1][key2];
}
});
});
/* Fire an event when a unit dies */
if (aliveChanged && this.getBaseData().alive == false)
document.dispatchEvent(new CustomEvent("unitDeath", { detail: this }));
@@ -485,11 +458,21 @@ export class Unit extends CustomMarker {
setSpeed(this.ID, speed);
}
setSpeedType(speedType: string) {
if (!this.getMissionData().flags.Human)
setSpeedType(this.ID, speedType);
}
setAltitude(altitude: number) {
if (!this.getMissionData().flags.Human)
setAltitude(this.ID, altitude);
}
setAltitudeType(altitudeType: string) {
if (!this.getMissionData().flags.Human)
setAltitudeType(this.ID, altitudeType);
}
setROE(ROE: string) {
if (!this.getMissionData().flags.Human)
setROE(this.ID, ROE);
@@ -510,9 +493,19 @@ export class Unit extends CustomMarker {
setLeader(this.ID, isLeader, wingmenIDs);
}
delete() {
setOnOff(onOff: boolean) {
if (!this.getMissionData().flags.Human)
setOnOff(this.ID, onOff);
}
setFollowRoads(followRoads: boolean) {
if (!this.getMissionData().flags.Human)
setFollowRoads(this.ID, followRoads);
}
delete(explosion: boolean) {
// TODO: add confirmation popup
deleteUnit(this.ID);
deleteUnit(this.ID, explosion);
}
refuel() {

View File

@@ -21,7 +21,8 @@ export class UnitsManager {
document.addEventListener('unitSelection', (e: CustomEvent) => this.#onUnitSelection(e.detail));
document.addEventListener('unitDeselection', (e: CustomEvent) => this.#onUnitDeselection(e.detail));
document.addEventListener('keydown', (event) => this.#onKeyDown(event));
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete())
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete());
document.addEventListener('explodeSelectedUnits', () => this.selectedUnitsDelete(true));
}
getSelectableAircraft() {
@@ -155,25 +156,16 @@ export class UnitsManager {
});
};
getSelectedUnitsTargetSpeed() {
getSelectedUnitsVariable(variableGetter: CallableFunction) {
if (this.getSelectedUnits().length == 0)
return undefined;
return this.getSelectedUnits().map((unit: Unit) => {
return unit.getTaskData().targetSpeed
return variableGetter(unit);
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
};
getSelectedUnitsTargetAltitude() {
if (this.getSelectedUnits().length == 0)
return undefined;
return this.getSelectedUnits().map((unit: Unit) => {
return unit.getTaskData().targetAltitude
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
};
getSelectedUnitsCoalition() {
if (this.getSelectedUnits().length == 0)
@@ -261,6 +253,14 @@ export class UnitsManager {
this.#showActionMessage(selectedUnits, `setting speed to ${speed * 1.94384} kts`);
}
selectedUnitsSetSpeedType(speedType: string) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
for (let idx in selectedUnits) {
selectedUnits[idx].setSpeedType(speedType);
}
this.#showActionMessage(selectedUnits, `setting speed type to ${speedType}`);
}
selectedUnitsSetAltitude(altitude: number) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
for (let idx in selectedUnits) {
@@ -269,6 +269,14 @@ export class UnitsManager {
this.#showActionMessage(selectedUnits, `setting altitude to ${altitude / 0.3048} ft`);
}
selectedUnitsSetAltitudeType(altitudeType: string) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
for (let idx in selectedUnits) {
selectedUnits[idx].setAltitudeType(altitudeType);
}
this.#showActionMessage(selectedUnits, `setting altitude type to ${altitudeType}`);
}
selectedUnitsSetROE(ROE: string) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
for (let idx in selectedUnits) {
@@ -290,7 +298,23 @@ export class UnitsManager {
for (let idx in selectedUnits) {
selectedUnits[idx].setEmissionsCountermeasures(emissionCountermeasure);
}
this.#showActionMessage(selectedUnits, `reaction to threat set to ${emissionCountermeasure}`);
this.#showActionMessage(selectedUnits, `emissions & countermeasures set to ${emissionCountermeasure}`);
}
selectedUnitsSetOnOff(onOff: boolean) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
for (let idx in selectedUnits) {
selectedUnits[idx].setOnOff(onOff);
}
this.#showActionMessage(selectedUnits, `unit acitve set to ${onOff}`);
}
selectedUnitsSetFollowRoads(followRoads: boolean) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
for (let idx in selectedUnits) {
selectedUnits[idx].setFollowRoads(followRoads);
}
this.#showActionMessage(selectedUnits, `follow roads set to ${followRoads}`);
}
@@ -302,10 +326,10 @@ export class UnitsManager {
this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
}
selectedUnitsDelete() {
selectedUnitsDelete(explosion: boolean = false) {
var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
for (let idx in selectedUnits) {
selectedUnits[idx].delete();
selectedUnits[idx].delete(explosion);
}
this.#showActionMessage(selectedUnits, `deleted`);
}