mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
1343 lines
47 KiB
TypeScript
1343 lines
47 KiB
TypeScript
import * as L from "leaflet";
|
|
import { getApp } from "../olympusapp";
|
|
import { BoxSelect } from "./boxselect";
|
|
import { Airbase } from "../mission/airbase";
|
|
import { Unit } from "../unit/unit";
|
|
import { areaContains, deg2rad, getGroundElevation } from "../other/utils";
|
|
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
|
|
import { ClickableMiniMap } from "./clickableminimap";
|
|
import {
|
|
defaultMapLayers,
|
|
mapBounds,
|
|
minimapBoundaries,
|
|
defaultMapMirrors,
|
|
MAP_OPTIONS_DEFAULTS,
|
|
MAP_HIDDEN_TYPES_DEFAULTS,
|
|
OlympusState,
|
|
OlympusSubState,
|
|
NO_SUBSTATE,
|
|
SpawnSubState,
|
|
DrawSubState,
|
|
JTACSubState,
|
|
UnitControlSubState,
|
|
ContextActionTarget,
|
|
ContextActionType,
|
|
ContextActions,
|
|
} from "../constants/constants";
|
|
import { CoalitionPolygon } from "./coalitionarea/coalitionpolygon";
|
|
import { MapHiddenTypes, MapOptions } from "../types/types";
|
|
import { EffectRequestTable, OlympusConfig, SpawnRequestTable } from "../interfaces";
|
|
import { ContextAction } from "../unit/contextaction";
|
|
|
|
/* Stylesheets */
|
|
import "./markers/stylesheets/airbase.css";
|
|
import "./markers/stylesheets/bullseye.css";
|
|
import "./markers/stylesheets/units.css";
|
|
import "./stylesheets/map.css";
|
|
import { CoalitionCircle } from "./coalitionarea/coalitioncircle";
|
|
|
|
import { initDraggablePath } from "./coalitionarea/draggablepath";
|
|
import { ExplosionMarker } from "./markers/explosionmarker";
|
|
import { TextMarker } from "./markers/textmarker";
|
|
import { TargetMarker } from "./markers/targetmarker";
|
|
import {
|
|
AirbaseSelectedEvent,
|
|
AppStateChangedEvent,
|
|
CoalitionAreaSelectedEvent,
|
|
ConfigLoadedEvent,
|
|
ContextActionChangedEvent,
|
|
ContextActionSetChangedEvent,
|
|
HiddenTypesChangedEvent,
|
|
MapContextMenuRequestEvent,
|
|
MapOptionsChangedEvent,
|
|
MapSourceChangedEvent,
|
|
MouseMovedEvent,
|
|
SelectionClearedEvent,
|
|
StarredSpawnContextMenuRequestEvent,
|
|
StarredSpawnsChangedEvent,
|
|
UnitDeselectedEvent,
|
|
UnitSelectedEvent,
|
|
UnitUpdatedEvent,
|
|
} from "../events";
|
|
import { ContextActionSet } from "../unit/contextactionset";
|
|
import { SmokeMarker } from "./markers/smokemarker";
|
|
|
|
/* Register the handler for the box selection */
|
|
L.Map.addInitHook("addHandler", "boxSelect", BoxSelect);
|
|
|
|
initDraggablePath(L);
|
|
|
|
export class Map extends L.Map {
|
|
/* Options */
|
|
#options: MapOptions = MAP_OPTIONS_DEFAULTS;
|
|
#hiddenTypes: MapHiddenTypes = MAP_HIDDEN_TYPES_DEFAULTS;
|
|
|
|
/* Map layers */
|
|
#theatre: string = "";
|
|
#layer: L.TileLayer | L.LayerGroup | null = null;
|
|
#layerName: string = "";
|
|
#mapLayers: any = defaultMapLayers;
|
|
#mapMirrors: any = defaultMapMirrors;
|
|
|
|
/* Inputs timers */
|
|
#mouseCooldownTimer: number = 0;
|
|
#shortPressTimer: number = 0;
|
|
#longPressTimer: number = 0;
|
|
#selecting: boolean = false;
|
|
|
|
/* Camera keyboard panning control */
|
|
defaultPanDelta: number = 100;
|
|
#panInterval: number | null = null;
|
|
#panLeft: boolean = false;
|
|
#panRight: boolean = false;
|
|
#panUp: boolean = false;
|
|
#panDown: boolean = false;
|
|
#panFast: boolean = false;
|
|
|
|
/* Center on unit target */
|
|
#centeredUnit: Unit | null = null;
|
|
|
|
/* Minimap */
|
|
#miniMap: ClickableMiniMap | null = null;
|
|
#miniMapLayerGroup: L.LayerGroup;
|
|
#miniMapPolyline: L.Polyline;
|
|
|
|
/* Other state controls */
|
|
#isMouseOnCooldown: boolean = false;
|
|
#isZooming: boolean = false;
|
|
#isDragging: boolean = false;
|
|
#isMouseDown: boolean = false;
|
|
#lastMousePosition: L.Point = new L.Point(0, 0);
|
|
#lastMouseCoordinates: L.LatLng = new L.LatLng(0, 0);
|
|
#previousZoom: number = 0;
|
|
|
|
/* Camera control plugin */
|
|
#slaveDCSCamera: boolean = false;
|
|
#slaveDCSCameraAvailable: boolean = false;
|
|
#cameraControlTimer: number = 0;
|
|
#cameraControlPort: number = 3003;
|
|
#cameraControlMode: string = "map";
|
|
#cameraOptionsXmlHttp: XMLHttpRequest | null = null;
|
|
#bradcastPositionXmlHttp: XMLHttpRequest | null = null;
|
|
#cameraZoomRatio: number = 1.0;
|
|
|
|
/* Coalition areas drawing */
|
|
#coalitionAreas: (CoalitionPolygon | CoalitionCircle)[] = [];
|
|
|
|
/* Units movement */
|
|
#destinationPreviewMarkers: { [key: number]: TemporaryUnitMarker | TargetMarker } = {};
|
|
#destinationRotation: number = 0;
|
|
#isRotatingDestination: boolean = false;
|
|
|
|
/* Unit context actions */
|
|
#contextActionSet: null | ContextActionSet = null;
|
|
#contextAction: null | ContextAction = null;
|
|
|
|
/* Unit spawning */
|
|
#spawnRequestTable: SpawnRequestTable | null = null;
|
|
#starredSpawnRequestTables: { [key: string]: SpawnRequestTable } = {};
|
|
#effectRequestTable: EffectRequestTable | null = null;
|
|
#temporaryMarkers: TemporaryUnitMarker[] = [];
|
|
#currentSpawnMarker: TemporaryUnitMarker | null = null;
|
|
#currentEffectMarker: ExplosionMarker | SmokeMarker | null = null;
|
|
|
|
/* JTAC tools */
|
|
#ECHOPoint: TextMarker | null = null;
|
|
#IPPoint: TextMarker | null = null;
|
|
#targetPoint: TargetMarker | null = null;
|
|
#IPToTargetLine: L.Polygon | null = null;
|
|
|
|
/**
|
|
*
|
|
* @param ID - the ID of the HTML element which will contain the map
|
|
*/
|
|
constructor(ID: string) {
|
|
/* Init the leaflet map */
|
|
super(ID, {
|
|
preferCanvas: true,
|
|
doubleClickZoom: false,
|
|
zoomControl: false,
|
|
boxZoom: false,
|
|
//@ts-ignore Needed because the boxSelect option is non-standard and unsuppoerted
|
|
boxSelect: true,
|
|
zoomAnimation: true,
|
|
maxBoundsViscosity: 1.0,
|
|
minZoom: 7,
|
|
keyboard: true,
|
|
keyboardPanDelta: 0,
|
|
});
|
|
this.setView([37.23, -115.8], 10);
|
|
|
|
/* Minimap */
|
|
var minimapLayer = new L.TileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", {
|
|
minZoom: 0,
|
|
maxZoom: 13,
|
|
});
|
|
this.#miniMapLayerGroup = new L.LayerGroup([minimapLayer]);
|
|
this.#miniMapPolyline = new L.Polyline([], { color: "#202831" });
|
|
this.#miniMapPolyline.addTo(this.#miniMapLayerGroup);
|
|
|
|
/* Register event handles */
|
|
this.on("zoomstart", (e: any) => this.#onZoomStart(e));
|
|
this.on("zoom", (e: any) => this.#onZoom(e));
|
|
this.on("zoomend", (e: any) => this.#onZoomEnd(e));
|
|
|
|
this.on("dragstart", (e: any) => this.#onDragStart(e));
|
|
this.on("drag", (e: any) => this.centerOnUnit(null));
|
|
this.on("dragend", (e: any) => this.#onDragEnd(e));
|
|
|
|
this.on("selectionstart", (e: any) => this.#onSelectionStart(e));
|
|
this.on("selectionend", (e: any) => this.#onSelectionEnd(e));
|
|
|
|
this.on("dblclick", (e: any) => this.#onDoubleClick(e));
|
|
this.on("mouseup", (e: any) => this.#onMouseUp(e));
|
|
this.on("mousedown", (e: any) => this.#onMouseDown(e));
|
|
this.on("contextmenu", (e: any) => e.originalEvent.preventDefault());
|
|
|
|
this.on("mousemove", (e: any) => this.#onMouseMove(e));
|
|
|
|
this.on("move", (e: any) => this.#onMapMove(e));
|
|
|
|
/* Custom touch events for touchscreen support */
|
|
L.DomEvent.on(this.getContainer(), "touchstart", this.#onMouseDown, this);
|
|
L.DomEvent.on(this.getContainer(), "touchend", this.#onMouseUp, this);
|
|
|
|
/* Event listeners */
|
|
AppStateChangedEvent.on((state, subState) => this.#onStateChanged(state, subState));
|
|
|
|
HiddenTypesChangedEvent.on((hiddenTypes) => {
|
|
Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
|
Object.values(getApp().getMissionManager().getAirbases()).forEach((airbase: Airbase) => {
|
|
if (this.getHiddenTypes().airbase) airbase.removeFrom(this);
|
|
else airbase.addTo(this);
|
|
});
|
|
});
|
|
|
|
UnitUpdatedEvent.on((unit) => {
|
|
if (this.#centeredUnit != null && unit == this.#centeredUnit) this.#panToUnit(this.#centeredUnit);
|
|
if (unit.getSelected()) this.#moveDestinationPreviewMarkers();
|
|
});
|
|
|
|
MapOptionsChangedEvent.on((options: MapOptions) => {
|
|
this.getContainer().toggleAttribute("data-hide-labels", !options.showUnitLabels);
|
|
this.#cameraControlPort = options.cameraPluginPort;
|
|
this.#cameraZoomRatio = 50 / (20 + options.cameraPluginRatio);
|
|
this.#slaveDCSCamera = options.cameraPluginEnabled;
|
|
this.#cameraControlMode = options.cameraPluginMode;
|
|
|
|
if (this.#slaveDCSCamera) {
|
|
this.#broadcastPosition();
|
|
window.setTimeout(() => {
|
|
this.#broadcastPosition();
|
|
}, 500); // DCS does not always apply the altitude correctly at the first set when changing map type
|
|
}
|
|
|
|
this.updateMinimap();
|
|
});
|
|
|
|
ConfigLoadedEvent.on((config: OlympusConfig) => {
|
|
let layerSet = false;
|
|
|
|
/* First load the map mirrors */
|
|
if (config.frontend.mapMirrors) {
|
|
let mapMirrors = config.frontend.mapMirrors;
|
|
this.#mapMirrors = {
|
|
...this.#mapMirrors,
|
|
...mapMirrors,
|
|
};
|
|
this.setLayerName(Object.keys(mapMirrors)[0]);
|
|
}
|
|
|
|
/* Set the options, and if at least one mirror is available, select the first */
|
|
if (Object.keys(this.#mapMirrors).length > 0) {
|
|
this.setLayerName(Object.keys(this.#mapMirrors)[0]);
|
|
layerSet = true; // Needed because this is async
|
|
}
|
|
|
|
/* Then load the map layers */
|
|
if (config.frontend.mapLayers) {
|
|
let mapLayers = config.frontend.mapLayers;
|
|
this.#mapLayers = {
|
|
...this.#mapLayers,
|
|
...mapLayers,
|
|
};
|
|
}
|
|
|
|
/* Append this options, and if no mirror was selected, select the first on (if available). Mirrors have the precedence */
|
|
if (!layerSet && Object.keys(this.#mapLayers).length > 0) {
|
|
this.setLayerName(Object.keys(this.#mapLayers)[0]);
|
|
}
|
|
});
|
|
|
|
UnitSelectedEvent.on((unit) => {
|
|
this.#updateDestinationPreviewMarkers();
|
|
this.#destinationRotation = 0;
|
|
});
|
|
UnitDeselectedEvent.on(() => {
|
|
this.#updateDestinationPreviewMarkers();
|
|
this.#destinationRotation = 0;
|
|
});
|
|
SelectionClearedEvent.on(() => {
|
|
this.#updateDestinationPreviewMarkers();
|
|
this.#destinationRotation = 0;
|
|
});
|
|
ContextActionChangedEvent.on((contextAction) => this.#updateDestinationPreviewMarkers());
|
|
MapOptionsChangedEvent.on((mapOptions) => this.#moveDestinationPreviewMarkers());
|
|
|
|
/* 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.defaultPanDelta * (this.#panFast ? 3 : 1),
|
|
((this.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.defaultPanDelta * (this.#panFast ? 3 : 1)
|
|
)
|
|
);
|
|
}, 20);
|
|
|
|
getApp()
|
|
.getShortcutManager()
|
|
.addShortcut("toggleUnitLabels", {
|
|
label: "Hide/show labels",
|
|
keyUpCallback: () => this.setOption("showUnitLabels", !this.getOptions().showUnitLabels),
|
|
code: "KeyL",
|
|
})
|
|
.addShortcut("toggleAcquisitionRings", {
|
|
label: "Hide/show acquisition rings",
|
|
keyUpCallback: () => this.setOption("showUnitsAcquisitionRings", !this.getOptions().showUnitsAcquisitionRings),
|
|
code: "KeyE",
|
|
})
|
|
.addShortcut("toggleEngagementRings", {
|
|
label: "Hide/show engagement rings",
|
|
keyUpCallback: () => this.setOption("showUnitsEngagementRings", !this.getOptions().showUnitsEngagementRings),
|
|
code: "KeyQ",
|
|
})
|
|
.addShortcut("toggleHideShortEngagementRings", {
|
|
label: "Hide/show short range rings",
|
|
keyUpCallback: () => this.setOption("hideUnitsShortRangeRings", !this.getOptions().hideUnitsShortRangeRings),
|
|
code: "KeyR",
|
|
})
|
|
.addShortcut("toggleDetectionLines", {
|
|
label: "Hide/show detection lines",
|
|
keyUpCallback: () => this.setOption("showUnitTargets", !this.getOptions().showUnitTargets),
|
|
code: "KeyF",
|
|
})
|
|
.addShortcut("toggleGroupMembers", {
|
|
label: "Hide/show group members",
|
|
keyUpCallback: () => this.setOption("hideGroupMembers", !this.getOptions().hideGroupMembers),
|
|
code: "KeyG",
|
|
})
|
|
.addShortcut("toggleRelativePositions", {
|
|
label: "Toggle group movement mode",
|
|
keyUpCallback: () => this.setOption("keepRelativePositions", !this.getOptions().keepRelativePositions),
|
|
code: "KeyP",
|
|
})
|
|
.addShortcut("increaseCameraZoom", {
|
|
label: "Increase camera zoom",
|
|
altKey: true,
|
|
keyUpCallback: () => this.increaseCameraZoom(),
|
|
code: "Equal",
|
|
})
|
|
.addShortcut("decreaseCameraZoom", {
|
|
label: "Decrease camera zoom",
|
|
altKey: true,
|
|
keyUpCallback: () => this.decreaseCameraZoom(),
|
|
code: "Minus",
|
|
});
|
|
|
|
for (let contextActionName in ContextActions) {
|
|
if (ContextActions[contextActionName].getOptions().hotkey) {
|
|
getApp()
|
|
.getShortcutManager()
|
|
.addShortcut(`${contextActionName}Hotkey`, {
|
|
label: ContextActions[contextActionName].getLabel(),
|
|
code: ContextActions[contextActionName].getOptions().hotkey,
|
|
shiftKey: true,
|
|
keyUpCallback: () => {
|
|
const contextActionSet = this.getContextActionSet();
|
|
if (
|
|
getApp().getState() === OlympusState.UNIT_CONTROL &&
|
|
contextActionSet &&
|
|
ContextActions[contextActionName].getId() in contextActionSet.getContextActions()
|
|
) {
|
|
if (ContextActions[contextActionName].getOptions().executeImmediately) ContextActions[contextActionName].executeCallback();
|
|
else this.setContextAction(ContextActions[contextActionName]);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
/* Map panning shortcuts */
|
|
getApp()
|
|
.getShortcutManager()
|
|
.addShortcut(`panUp`, {
|
|
label: "Pan map up",
|
|
keyUpCallback: (ev: KeyboardEvent) => (this.#panUp = false),
|
|
keyDownCallback: (ev: KeyboardEvent) => (this.#panUp = true),
|
|
code: "KeyW",
|
|
})
|
|
.addShortcut(`panDown`, {
|
|
label: "Pan map down",
|
|
keyUpCallback: (ev: KeyboardEvent) => (this.#panDown = false),
|
|
keyDownCallback: (ev: KeyboardEvent) => (this.#panDown = true),
|
|
code: "KeyS",
|
|
})
|
|
.addShortcut(`panLeft`, {
|
|
label: "Pan map left",
|
|
keyUpCallback: (ev: KeyboardEvent) => (this.#panLeft = false),
|
|
keyDownCallback: (ev: KeyboardEvent) => (this.#panLeft = true),
|
|
code: "KeyA",
|
|
})
|
|
.addShortcut(`panRight`, {
|
|
label: "Pan map right",
|
|
keyUpCallback: (ev: KeyboardEvent) => (this.#panRight = false),
|
|
keyDownCallback: (ev: KeyboardEvent) => (this.#panRight = true),
|
|
code: "KeyD",
|
|
})
|
|
.addShortcut(`panFast`, {
|
|
label: "Pan map fast",
|
|
keyUpCallback: (ev: KeyboardEvent) => (this.#panFast = false),
|
|
keyDownCallback: (ev: KeyboardEvent) => (this.#panFast = true),
|
|
code: "ShiftLeft",
|
|
});
|
|
|
|
/* Periodically check if the camera control endpoint is available */
|
|
this.#cameraControlTimer = window.setInterval(() => {
|
|
this.#checkCameraPort();
|
|
}, 1000);
|
|
}
|
|
|
|
setLayerName(layerName: string) {
|
|
if (this.#layer) this.removeLayer(this.#layer);
|
|
|
|
let theatre = getApp().getMissionManager()?.getTheatre();
|
|
|
|
/* Normal or custom layers are handled here */
|
|
if (layerName in this.#mapLayers) {
|
|
const layerData = this.#mapLayers[layerName];
|
|
if (layerData instanceof Array) {
|
|
let layers = layerData.map((layer: any) => {
|
|
return new L.TileLayer(layer.urlTemplate.replace("{theatre}", theatre.toLowerCase()), layer);
|
|
});
|
|
this.#layer = new L.LayerGroup(layers);
|
|
this.#layer?.addTo(this);
|
|
} else {
|
|
this.#layer = new L.TileLayer(layerData.urlTemplate, layerData);
|
|
this.#layer?.addTo(this);
|
|
}
|
|
|
|
/* Mirrored layers are handled here */
|
|
} else if (Object.keys(this.#mapMirrors).includes(layerName)) {
|
|
let layers: L.TileLayer[] = [];
|
|
|
|
layers.push(
|
|
new L.TileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", {
|
|
minZoom: 1,
|
|
maxZoom: 19,
|
|
})
|
|
);
|
|
|
|
/* Load the configuration file */
|
|
const mirror = this.#mapMirrors[layerName as any];
|
|
const request = new Request(mirror + "/config.json");
|
|
fetch(request)
|
|
.then((response) => {
|
|
if (response.status === 200) {
|
|
return response.json();
|
|
} else {
|
|
return {};
|
|
}
|
|
})
|
|
.then((res: any) => {
|
|
if ("alt-" + theatre.toLowerCase() in res) {
|
|
let template = `${mirror}/alt-${theatre.toLowerCase()}/{z}/{x}/{y}.png`;
|
|
layers.push(
|
|
...res["alt-" + theatre.toLowerCase()].map((layerConfig: any) => {
|
|
return new L.TileLayer(template, {
|
|
...layerConfig,
|
|
crossOrigin: "",
|
|
});
|
|
})
|
|
);
|
|
}
|
|
this.#layer = new L.LayerGroup(layers);
|
|
this.#layer?.addTo(this);
|
|
})
|
|
.catch(() => {
|
|
this.#layer = new L.LayerGroup(layers);
|
|
this.#layer?.addTo(this);
|
|
});
|
|
}
|
|
this.#layerName = layerName;
|
|
|
|
MapSourceChangedEvent.dispatch(layerName);
|
|
}
|
|
|
|
getLayerName() {
|
|
return this.#layerName;
|
|
}
|
|
|
|
getLayers() {
|
|
return Object.keys(this.#mapLayers);
|
|
}
|
|
|
|
setSpawnRequestTable(spawnRequestTable: SpawnRequestTable) {
|
|
this.#spawnRequestTable = spawnRequestTable;
|
|
}
|
|
|
|
addStarredSpawnRequestTable(key, spawnRequestTable: SpawnRequestTable) {
|
|
this.#starredSpawnRequestTables[key] = spawnRequestTable;
|
|
StarredSpawnsChangedEvent.dispatch(this.#starredSpawnRequestTables);
|
|
}
|
|
|
|
removeStarredSpawnRequestTable(key) {
|
|
if (key in this.#starredSpawnRequestTables) delete this.#starredSpawnRequestTables[key];
|
|
StarredSpawnsChangedEvent.dispatch(this.#starredSpawnRequestTables);
|
|
}
|
|
|
|
setEffectRequestTable(effectRequestTable: EffectRequestTable) {
|
|
this.#effectRequestTable = effectRequestTable;
|
|
if (getApp().getState() === OlympusState.SPAWN && getApp().getSubState() === SpawnSubState.SPAWN_EFFECT) {
|
|
this.#currentEffectMarker?.removeFrom(this);
|
|
this.#currentEffectMarker = null;
|
|
if (this.#effectRequestTable?.type === "smoke")
|
|
this.#currentEffectMarker = new SmokeMarker(new L.LatLng(0, 0), this.#effectRequestTable.smokeColor ?? "white");
|
|
this.#currentEffectMarker?.addTo(this);
|
|
}
|
|
}
|
|
|
|
setContextActionSet(contextActionSet: ContextActionSet | null) {
|
|
this.#contextActionSet = contextActionSet;
|
|
ContextActionSetChangedEvent.dispatch(this.#contextActionSet);
|
|
}
|
|
|
|
setContextAction(contextAction: ContextAction | null) {
|
|
this.#contextAction = contextAction;
|
|
ContextActionChangedEvent.dispatch(this.#contextAction);
|
|
}
|
|
|
|
getCurrentControls() {
|
|
//const touch = matchMedia("(hover: none)").matches;
|
|
//if (getApp().getState() === IDLE) {
|
|
// return [
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB"],
|
|
// target: faJetFighter,
|
|
// text: "Select unit",
|
|
// },
|
|
// touch
|
|
// ? {
|
|
// actions: [faHandPointer, "Drag"],
|
|
// target: faMap,
|
|
// text: "Box selection",
|
|
// }
|
|
// : {
|
|
// actions: ["Shift", "LMB", "Drag"],
|
|
// target: faMap,
|
|
// text: "Box selection",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
|
// target: faMap,
|
|
// text: "Move map location",
|
|
// },
|
|
// ];
|
|
//} else if (getApp().getState() === OlympusState.SPAWN_UNIT) {
|
|
// return [
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB"],
|
|
// target: faMap,
|
|
// text: "Spawn unit",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", 2],
|
|
// target: faMap,
|
|
// text: "Exit spawn mode",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
|
// target: faMap,
|
|
// text: "Move map location",
|
|
// },
|
|
// ];
|
|
//} else if (getApp().getState() === SPAWN_EFFECT) {
|
|
// return [
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB"],
|
|
// target: faMap,
|
|
// text: "Spawn effect",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", 2],
|
|
// target: faMap,
|
|
// text: "Exit spawn mode",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
|
// target: faMap,
|
|
// text: "Move map location",
|
|
// },
|
|
// ];
|
|
//} else if (getApp().getState() === CONTEXT_ACTION) {
|
|
// let controls = [
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB"],
|
|
// target: faMap,
|
|
// text: "Deselect units",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
|
// target: faMap,
|
|
// text: "Move map location",
|
|
// },
|
|
// ];
|
|
//
|
|
// if (this.#contextAction) {
|
|
// controls.push({
|
|
// actions: [touch ? faHandPointer : "LMB"],
|
|
// target: this.#contextAction.getTarget() === "unit" ? faJetFighter : faMap,
|
|
// text: this.#contextAction?.getLabel() ?? "",
|
|
// });
|
|
// }
|
|
//
|
|
// if (!touch && this.#defaultContextAction) {
|
|
// controls.push({
|
|
// actions: ["RMB"],
|
|
// target: faMap,
|
|
// text: this.#defaultContextAction?.getLabel() ?? "",
|
|
// });
|
|
// controls.push({
|
|
// actions: ["RMB", "hold"],
|
|
// target: faMap,
|
|
// text: "Open context menu",
|
|
// });
|
|
// }
|
|
//
|
|
// return controls;
|
|
//} else if (getApp().getState() === COALITIONAREA_EDIT) {
|
|
// return [
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB"],
|
|
// target: faDrawPolygon,
|
|
// text: "Select shape",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", 2],
|
|
// target: faMap,
|
|
// text: "Exit drawing mode",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
|
// target: faMap,
|
|
// text: "Move map location",
|
|
// },
|
|
// ];
|
|
//} else if (getApp().getState() === COALITIONAREA_DRAW_POLYGON) {
|
|
// return [
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB"],
|
|
// target: faMap,
|
|
// text: "Add vertex to polygon",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", 2],
|
|
// target: faMap,
|
|
// text: "Finalize polygon",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
|
// target: faMap,
|
|
// text: "Move map location",
|
|
// },
|
|
// ];
|
|
//} else if (getApp().getState() === COALITIONAREA_DRAW_CIRCLE) {
|
|
// return [
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB"],
|
|
// target: faMap,
|
|
// text: "Add circle",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
|
// target: faMap,
|
|
// text: "Move map location",
|
|
// },
|
|
// ];
|
|
//} else if (getApp().getState() === SELECT_JTAC_TARGET) {
|
|
// return [
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB"],
|
|
// target: faMap,
|
|
// text: "Set unit/location as target",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", 2],
|
|
// target: faMap,
|
|
// text: "Exit selection mode",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
|
// target: faMap,
|
|
// text: "Move map location",
|
|
// },
|
|
// ];
|
|
//} else if (getApp().getState() === SELECT_JTAC_ECHO) {
|
|
// return [
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB"],
|
|
// target: faMap,
|
|
// text: "Set location as ECHO point",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", 2],
|
|
// target: faMap,
|
|
// text: "Exit selection mode",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
|
// target: faMap,
|
|
// text: "Move map location",
|
|
// },
|
|
// ];
|
|
//} else if (getApp().getState() === SELECT_JTAC_IP) {
|
|
// return [
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB"],
|
|
// target: faMap,
|
|
// text: "Set location as IP point",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", 2],
|
|
// target: faMap,
|
|
// text: "Exit selection mode",
|
|
// },
|
|
// {
|
|
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
|
// target: faMap,
|
|
// text: "Move map location",
|
|
// },
|
|
// ];
|
|
//} else {
|
|
// return [];
|
|
//}
|
|
}
|
|
|
|
deselectAllCoalitionAreas() {
|
|
if (this.getSelectedCoalitionArea() !== null) {
|
|
CoalitionAreaSelectedEvent.dispatch(null);
|
|
this.#coalitionAreas.forEach((coalitionArea: CoalitionPolygon | CoalitionCircle) => coalitionArea.setSelected(false));
|
|
}
|
|
}
|
|
|
|
deleteCoalitionArea(coalitionArea: CoalitionPolygon | CoalitionCircle) {
|
|
if (this.#coalitionAreas.includes(coalitionArea)) this.#coalitionAreas.splice(this.#coalitionAreas.indexOf(coalitionArea), 1);
|
|
|
|
if (this.hasLayer(coalitionArea)) this.removeLayer(coalitionArea);
|
|
}
|
|
|
|
getSelectedCoalitionArea() {
|
|
const coalitionArea = this.#coalitionAreas.find((coalitionArea: CoalitionPolygon | CoalitionCircle) => {
|
|
return coalitionArea.getSelected();
|
|
});
|
|
|
|
return coalitionArea ?? null;
|
|
}
|
|
|
|
setHiddenType(key: string, value: boolean) {
|
|
this.#hiddenTypes[key] = value;
|
|
HiddenTypesChangedEvent.dispatch(this.#hiddenTypes);
|
|
}
|
|
|
|
getHiddenTypes() {
|
|
return this.#hiddenTypes;
|
|
}
|
|
|
|
getMousePosition() {
|
|
return this.#lastMousePosition;
|
|
}
|
|
|
|
getMouseCoordinates() {
|
|
return this.#lastMouseCoordinates;
|
|
}
|
|
|
|
getDestinationRotation() {
|
|
return this.#destinationRotation;
|
|
}
|
|
|
|
centerOnUnit(unit: Unit | null) {
|
|
if (unit !== null) {
|
|
this.options.scrollWheelZoom = "center";
|
|
this.#centeredUnit = unit;
|
|
this.#panToUnit(unit);
|
|
} else {
|
|
this.options.scrollWheelZoom = undefined;
|
|
this.#centeredUnit = null;
|
|
}
|
|
}
|
|
|
|
getCenteredOnUnit() {
|
|
return this.#centeredUnit;
|
|
}
|
|
|
|
isSelecting() {
|
|
return this.#selecting;
|
|
}
|
|
|
|
setTheatre(theatre: string) {
|
|
this.#theatre = theatre;
|
|
|
|
var bounds = new L.LatLngBounds([-90, -180], [90, 180]);
|
|
if (theatre in mapBounds) {
|
|
bounds = mapBounds[theatre as keyof typeof mapBounds].bounds;
|
|
}
|
|
|
|
this.setView(bounds.getCenter(), 8);
|
|
this.updateMinimap();
|
|
const boundaries = this.#getMinimapBoundaries();
|
|
this.#miniMapPolyline.setLatLngs(boundaries[theatre as keyof typeof boundaries]);
|
|
this.setLayerName(this.#layerName);
|
|
}
|
|
|
|
updateMinimap() {
|
|
if (this.#miniMap) this.#miniMap.remove();
|
|
|
|
if (this.#options.showMinimap) {
|
|
var bounds = new L.LatLngBounds([-90, -180], [90, 180]);
|
|
var miniMapZoom = 5;
|
|
if (this.#theatre in mapBounds) {
|
|
bounds = mapBounds[this.#theatre as keyof typeof mapBounds].bounds;
|
|
miniMapZoom = mapBounds[this.#theatre as keyof typeof mapBounds].zoom;
|
|
}
|
|
|
|
this.#miniMap = new ClickableMiniMap(this.#miniMapLayerGroup, {
|
|
position: "bottomright",
|
|
width: 192 * 1.5,
|
|
height: 108 * 1.5,
|
|
//@ts-ignore Needed because some of the inputs are wrong in the original module interface
|
|
zoomLevelFixed: miniMapZoom,
|
|
//@ts-ignore Needed because some of the inputs are wrong in the original module interface
|
|
centerFixed: bounds.getCenter(),
|
|
}).addTo(this);
|
|
this.#miniMap.disableInteractivity();
|
|
this.#miniMap.getMap().on("click", (e: any) => {
|
|
if (this.#miniMap) this.setView(e.latlng);
|
|
});
|
|
}
|
|
}
|
|
|
|
getMiniMapLayerGroup() {
|
|
return this.#miniMapLayerGroup;
|
|
}
|
|
|
|
addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string, commandHash?: string) {
|
|
var marker = new TemporaryUnitMarker(latlng, name, coalition, commandHash);
|
|
marker.addTo(this);
|
|
this.#temporaryMarkers.push(marker);
|
|
return marker;
|
|
}
|
|
|
|
addExplosionMarker(latlng: L.LatLng) {
|
|
const explosionMarker = new ExplosionMarker(latlng, 5);
|
|
explosionMarker.addTo(this);
|
|
return explosionMarker;
|
|
}
|
|
|
|
addSmokeMarker(latlng: L.LatLng, color: string) {
|
|
const smokeMarker = new SmokeMarker(latlng, color);
|
|
smokeMarker.addTo(this);
|
|
return smokeMarker;
|
|
}
|
|
|
|
setOption(key, value) {
|
|
this.#options[key] = value;
|
|
MapOptionsChangedEvent.dispatch(this.#options);
|
|
}
|
|
|
|
setOptions(options) {
|
|
this.#options = { ...options };
|
|
MapOptionsChangedEvent.dispatch(this.#options);
|
|
}
|
|
|
|
getOptions() {
|
|
return this.#options;
|
|
}
|
|
|
|
isZooming() {
|
|
return this.#isZooming;
|
|
}
|
|
|
|
getPreviousZoom() {
|
|
return this.#previousZoom;
|
|
}
|
|
|
|
increaseCameraZoom() {
|
|
//const slider = document.querySelector(`label[title="${DCS_LINK_RATIO}"] input`);
|
|
//if (slider instanceof HTMLInputElement) {
|
|
// slider.value = String(Math.min(Number(slider.max), Number(slider.value) + 10));
|
|
// slider.dispatchEvent(new Event('input'));
|
|
// slider.dispatchEvent(new Event('mouseup'));
|
|
//}
|
|
}
|
|
|
|
decreaseCameraZoom() {
|
|
//const slider = document.querySelector(`label[title="${DCS_LINK_RATIO}"] input`);
|
|
//if (slider instanceof HTMLInputElement) {
|
|
// slider.value = String(Math.max(Number(slider.min), Number(slider.value) - 10));
|
|
// slider.dispatchEvent(new Event('input'));
|
|
// slider.dispatchEvent(new Event('mouseup'));
|
|
//}
|
|
}
|
|
|
|
executeContextAction(targetUnit: Unit | null, targetPosition: L.LatLng | null, originalEvent?: MouseEvent) {
|
|
this.#contextAction?.executeCallback(targetUnit, targetPosition, originalEvent);
|
|
}
|
|
|
|
getContextActionSet() {
|
|
return this.#contextActionSet;
|
|
}
|
|
|
|
getContextAction() {
|
|
return this.#contextAction;
|
|
}
|
|
|
|
executeDefaultContextAction(targetUnit: Unit | null, targetPosition: L.LatLng | null, originalEvent?: MouseEvent) {
|
|
this.#contextActionSet?.getDefaultContextAction()?.executeCallback(targetUnit, targetPosition, originalEvent);
|
|
}
|
|
|
|
preventClicks() {
|
|
console.log("Preventing clicks on map");
|
|
window.clearTimeout(this.#shortPressTimer);
|
|
window.clearTimeout(this.#longPressTimer);
|
|
}
|
|
|
|
/* Event handlers */
|
|
#onStateChanged(state: OlympusState, subState: OlympusSubState) {
|
|
/* Operations to perform when leaving a state */
|
|
this.getSelectedCoalitionArea()?.setEditing(false);
|
|
this.#currentSpawnMarker?.removeFrom(this);
|
|
this.#currentSpawnMarker = null;
|
|
this.#currentEffectMarker?.removeFrom(this);
|
|
this.#currentEffectMarker = null;
|
|
if (state !== OlympusState.UNIT_CONTROL) getApp().getUnitsManager().deselectAllUnits();
|
|
if (state !== OlympusState.DRAW || (state === OlympusState.DRAW && subState !== DrawSubState.EDIT)) this.deselectAllCoalitionAreas();
|
|
AirbaseSelectedEvent.dispatch(null);
|
|
|
|
/* Operations to perform when entering a state */
|
|
if (state === OlympusState.IDLE) {
|
|
getApp().getUnitsManager()?.deselectAllUnits();
|
|
} else if (state === OlympusState.SPAWN) {
|
|
if (subState === SpawnSubState.SPAWN_UNIT) {
|
|
console.log(`Spawn request table:`);
|
|
console.log(this.#spawnRequestTable);
|
|
this.#currentSpawnMarker = new TemporaryUnitMarker(
|
|
new L.LatLng(0, 0),
|
|
this.#spawnRequestTable?.unit.unitType ?? "",
|
|
this.#spawnRequestTable?.coalition ?? "neutral"
|
|
);
|
|
this.#currentSpawnMarker.addTo(this);
|
|
} else if (subState === SpawnSubState.SPAWN_EFFECT) {
|
|
console.log(`Effect request table:`);
|
|
console.log(this.#effectRequestTable);
|
|
if (this.#effectRequestTable?.type === "explosion") this.#currentEffectMarker = new ExplosionMarker(new L.LatLng(0, 0));
|
|
else if (this.#effectRequestTable?.type === "smoke")
|
|
this.#currentEffectMarker = new SmokeMarker(new L.LatLng(0, 0), this.#effectRequestTable.smokeColor ?? "white");
|
|
this.#currentEffectMarker?.addTo(this);
|
|
}
|
|
} else if (state === OlympusState.UNIT_CONTROL) {
|
|
console.log(`Context action:`);
|
|
console.log(this.#contextAction);
|
|
} else if (state === OlympusState.DRAW) {
|
|
if (subState == DrawSubState.DRAW_POLYGON) {
|
|
this.#coalitionAreas.push(new CoalitionPolygon([]));
|
|
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
|
|
this.#coalitionAreas[this.#coalitionAreas.length - 1].setSelected(true);
|
|
} else if (subState === DrawSubState.DRAW_CIRCLE) {
|
|
this.#coalitionAreas.push(new CoalitionCircle(new L.LatLng(0, 0), { radius: 1000 }));
|
|
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
|
|
this.#coalitionAreas[this.#coalitionAreas.length - 1].setSelected(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
#onDragStart(e: any) {
|
|
this.#isDragging = true;
|
|
}
|
|
|
|
#onDragEnd(e: any) {
|
|
this.#isDragging = false;
|
|
}
|
|
|
|
#onSelectionStart(e: any) {
|
|
this.#selecting = true;
|
|
}
|
|
|
|
#onSelectionEnd(e: any) {
|
|
getApp().getUnitsManager().selectFromBounds(e.selectionBounds);
|
|
this.#selecting = false;
|
|
}
|
|
|
|
#onMouseUp(e: any) {
|
|
this.#isMouseDown = false;
|
|
window.clearTimeout(this.#longPressTimer);
|
|
|
|
this.scrollWheelZoom.enable();
|
|
this.dragging.enable();
|
|
|
|
this.#isRotatingDestination = false;
|
|
this.#isMouseOnCooldown = true;
|
|
this.#mouseCooldownTimer = window.setTimeout(() => {
|
|
this.#isMouseOnCooldown = false;
|
|
}, 200);
|
|
}
|
|
|
|
#onMouseDown(e: any) {
|
|
this.#isMouseDown = true;
|
|
|
|
if (this.#isMouseOnCooldown) {
|
|
return;
|
|
}
|
|
|
|
if (this.#contextAction?.getTarget() === ContextActionTarget.POINT && e.originalEvent?.button === 2) this.#isRotatingDestination = true;
|
|
this.scrollWheelZoom.disable();
|
|
|
|
this.#shortPressTimer = window.setTimeout(() => {
|
|
/* If the mouse is no longer being pressed, execute the short press action */
|
|
if (!this.#isMouseDown) this.#onShortPress(e);
|
|
}, 200);
|
|
|
|
this.#longPressTimer = window.setTimeout(() => {
|
|
/* If the mouse is still being pressed, execute the long press action */
|
|
if (this.#isMouseDown && !this.#isDragging && !this.#isZooming) this.#onLongPress(e);
|
|
}, 350);
|
|
}
|
|
|
|
#onDoubleClick(e: any) {
|
|
console.log(`Double click at ${e.latlng}`);
|
|
|
|
window.clearTimeout(this.#shortPressTimer);
|
|
window.clearTimeout(this.#longPressTimer);
|
|
|
|
if (getApp().getState() === OlympusState.IDLE) {
|
|
StarredSpawnContextMenuRequestEvent.dispatch(e.latlng);
|
|
getApp().setState(OlympusState.STARRED_SPAWN);
|
|
} else {
|
|
if (getApp().getSubState() !== NO_SUBSTATE) {
|
|
getApp().setState(getApp().getState(), NO_SUBSTATE);
|
|
} else {
|
|
getApp().setState(OlympusState.IDLE);
|
|
}
|
|
}
|
|
}
|
|
|
|
#onShortPress(e: any) {
|
|
let pressLocation: L.LatLng;
|
|
if (e.type === "touchstart") pressLocation = this.containerPointToLatLng(this.mouseEventToContainerPoint(e.touches[0]));
|
|
else pressLocation = new L.LatLng(e.latlng.lat, e.latlng.lng);
|
|
|
|
console.log(`Short press at ${pressLocation}`);
|
|
|
|
/* Execute the short click action */
|
|
if (getApp().getState() === OlympusState.IDLE) {
|
|
/* Do nothing */
|
|
} else if (getApp().getState() === OlympusState.SPAWN) {
|
|
if (getApp().getSubState() === SpawnSubState.SPAWN_UNIT) {
|
|
if (e.originalEvent?.button != 2 && this.#spawnRequestTable !== null) {
|
|
this.#spawnRequestTable.unit.location = pressLocation;
|
|
getApp()
|
|
.getUnitsManager()
|
|
.spawnUnits(
|
|
this.#spawnRequestTable.category,
|
|
[this.#spawnRequestTable.unit],
|
|
this.#spawnRequestTable.coalition,
|
|
false,
|
|
undefined,
|
|
undefined,
|
|
(hash) => {
|
|
this.addTemporaryMarker(pressLocation, this.#spawnRequestTable?.unit.unitType ?? "unknown", this.#spawnRequestTable?.coalition ?? "blue", hash);
|
|
}
|
|
);
|
|
}
|
|
} else if (getApp().getSubState() === SpawnSubState.SPAWN_EFFECT) {
|
|
if (e.originalEvent?.button != 2 && this.#effectRequestTable !== null) {
|
|
if (this.#effectRequestTable.type === "explosion") {
|
|
if (this.#effectRequestTable.explosionType === "High explosive") getApp().getServerManager().spawnExplosion(50, "normal", pressLocation);
|
|
else if (this.#effectRequestTable.explosionType === "Napalm") getApp().getServerManager().spawnExplosion(50, "napalm", pressLocation);
|
|
else if (this.#effectRequestTable.explosionType === "White phosphorous")
|
|
getApp().getServerManager().spawnExplosion(50, "phosphorous", pressLocation);
|
|
|
|
this.addExplosionMarker(pressLocation);
|
|
} else if (this.#effectRequestTable.type === "smoke") {
|
|
getApp()
|
|
.getServerManager()
|
|
.spawnSmoke(this.#effectRequestTable.smokeColor ?? "white", pressLocation);
|
|
this.addSmokeMarker(pressLocation, this.#effectRequestTable.smokeColor ?? "white");
|
|
}
|
|
}
|
|
}
|
|
} else if (getApp().getState() === OlympusState.DRAW) {
|
|
if (getApp().getSubState() === DrawSubState.DRAW_POLYGON) {
|
|
const selectedArea = this.getSelectedCoalitionArea();
|
|
if (selectedArea && selectedArea instanceof CoalitionPolygon) {
|
|
selectedArea.addTemporaryLatLng(pressLocation);
|
|
}
|
|
} else if (getApp().getSubState() === DrawSubState.DRAW_CIRCLE) {
|
|
const selectedArea = this.getSelectedCoalitionArea();
|
|
if (selectedArea && selectedArea instanceof CoalitionCircle) {
|
|
if (selectedArea.getLatLng().lat == 0 && selectedArea.getLatLng().lng == 0) selectedArea.setLatLng(pressLocation);
|
|
getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
|
}
|
|
} else if (getApp().getSubState() == DrawSubState.NO_SUBSTATE) {
|
|
this.deselectAllCoalitionAreas();
|
|
for (let idx = 0; idx < this.#coalitionAreas.length; idx++) {
|
|
if (areaContains(pressLocation, this.#coalitionAreas[idx])) {
|
|
this.#coalitionAreas[idx].setSelected(true);
|
|
getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
|
|
if (e.type === "touchstart" || e.originalEvent?.buttons === 1) {
|
|
if (this.#contextAction !== null) this.executeContextAction(null, pressLocation, e.originalEvent);
|
|
else getApp().setState(OlympusState.IDLE);
|
|
} else if (e.originalEvent?.buttons === 2) {
|
|
this.executeDefaultContextAction(null, pressLocation, e.originalEvent);
|
|
}
|
|
} else if (getApp().getState() === OlympusState.JTAC) {
|
|
// TODO less redundant way to do this
|
|
if (getApp().getSubState() === JTACSubState.SELECT_TARGET) {
|
|
if (!this.#targetPoint) {
|
|
this.#targetPoint = new TextMarker(pressLocation, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
|
this.#targetPoint.addTo(this);
|
|
this.#targetPoint.on("dragstart", (event) => {
|
|
event.target.options["freeze"] = true;
|
|
});
|
|
this.#targetPoint.on("dragend", (event) => {
|
|
getApp().setState(OlympusState.JTAC);
|
|
event.target.options["freeze"] = false;
|
|
});
|
|
this.#targetPoint.on("click", (event) => {
|
|
getApp().setState(OlympusState.JTAC);
|
|
});
|
|
} else this.#targetPoint.setLatLng(pressLocation);
|
|
} else if (getApp().getSubState() === JTACSubState.SELECT_ECHO_POINT) {
|
|
if (!this.#ECHOPoint) {
|
|
this.#ECHOPoint = new TextMarker(pressLocation, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
|
this.#ECHOPoint.addTo(this);
|
|
this.#ECHOPoint.on("dragstart", (event) => {
|
|
event.target.options["freeze"] = true;
|
|
});
|
|
this.#ECHOPoint.on("dragend", (event) => {
|
|
getApp().setState(OlympusState.JTAC);
|
|
event.target.options["freeze"] = false;
|
|
});
|
|
this.#ECHOPoint.on("click", (event) => {
|
|
getApp().setState(OlympusState.JTAC);
|
|
});
|
|
} else this.#ECHOPoint.setLatLng(pressLocation);
|
|
} else if (getApp().getSubState() === JTACSubState.SELECT_IP) {
|
|
if (!this.#IPPoint) {
|
|
this.#IPPoint = new TextMarker(pressLocation, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
|
this.#IPPoint.addTo(this);
|
|
this.#IPPoint.on("dragstart", (event) => {
|
|
event.target.options["freeze"] = true;
|
|
});
|
|
this.#IPPoint.on("dragend", (event) => {
|
|
getApp().setState(OlympusState.JTAC);
|
|
event.target.options["freeze"] = false;
|
|
});
|
|
this.#IPPoint.on("click", (event) => {
|
|
getApp().setState(OlympusState.JTAC);
|
|
});
|
|
} else this.#IPPoint.setLatLng(pressLocation);
|
|
}
|
|
getApp().setState(OlympusState.JTAC);
|
|
this.#drawIPToTargetLine();
|
|
} else {
|
|
}
|
|
}
|
|
|
|
#onLongPress(e: any) {
|
|
let pressLocation: L.LatLng;
|
|
if (e.type === "touchstart") pressLocation = this.containerPointToLatLng(this.mouseEventToContainerPoint(e.touches[0]));
|
|
else pressLocation = new L.LatLng(e.latlng.lat, e.latlng.lng);
|
|
|
|
console.log(`Long press at ${pressLocation}`);
|
|
|
|
if (!this.#isDragging && !this.#isZooming) {
|
|
this.deselectAllCoalitionAreas();
|
|
if (getApp().getState() === OlympusState.IDLE) {
|
|
if (e.type === "touchstart") document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e }));
|
|
else document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e.originalEvent }));
|
|
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
|
|
if (e.originalEvent?.button === 2) {
|
|
if (!this.getContextAction()) {
|
|
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.MAP_CONTEXT_MENU);
|
|
MapContextMenuRequestEvent.dispatch(pressLocation);
|
|
}
|
|
} else {
|
|
if (this.#contextAction?.getTarget() === ContextActionTarget.POINT) {
|
|
this.dragging.disable();
|
|
this.#isRotatingDestination = true;
|
|
} else {
|
|
if (e.type === "touchstart") document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e }));
|
|
else document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e.originalEvent }));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#onMouseMove(e: any) {
|
|
window.clearTimeout(this.#longPressTimer);
|
|
|
|
if (!this.#isRotatingDestination) {
|
|
this.#lastMousePosition.x = e.originalEvent.x;
|
|
this.#lastMousePosition.y = e.originalEvent.y;
|
|
this.#lastMouseCoordinates = e.latlng;
|
|
|
|
MouseMovedEvent.dispatch(e.latlng);
|
|
getGroundElevation(e.latlng, (elevation) => {
|
|
MouseMovedEvent.dispatch(e.latlng, elevation);
|
|
})
|
|
|
|
if (this.#currentSpawnMarker) this.#currentSpawnMarker.setLatLng(e.latlng);
|
|
if (this.#currentEffectMarker) this.#currentEffectMarker.setLatLng(e.latlng);
|
|
} else {
|
|
this.#destinationRotation -= e.originalEvent.movementX;
|
|
}
|
|
|
|
this.#moveDestinationPreviewMarkers();
|
|
}
|
|
|
|
#onMapMove(e: any) {
|
|
if (this.#slaveDCSCamera) this.#broadcastPosition();
|
|
}
|
|
|
|
#onZoomStart(e: any) {
|
|
this.#previousZoom = this.getZoom();
|
|
if (this.#centeredUnit != null) this.#panToUnit(this.#centeredUnit);
|
|
this.#isZooming = true;
|
|
}
|
|
|
|
#onZoom(e: any) {
|
|
if (this.#centeredUnit != null) this.#panToUnit(this.#centeredUnit);
|
|
}
|
|
|
|
#onZoomEnd(e: any) {
|
|
this.#isZooming = false;
|
|
}
|
|
|
|
#broadcastPosition() {
|
|
if (this.#bradcastPositionXmlHttp?.readyState !== 4 && this.#bradcastPositionXmlHttp !== null) return;
|
|
|
|
getGroundElevation(this.getCenter(), (response: string) => {
|
|
var groundElevation: number | null = null;
|
|
try {
|
|
groundElevation = parseFloat(response);
|
|
this.#bradcastPositionXmlHttp = new XMLHttpRequest();
|
|
/* Using 127.0.0.1 instead of localhost because the LuaSocket version used in DCS only listens to IPv4. This avoids the lag caused by the
|
|
browser if it first tries to send the request on the IPv6 address for localhost */
|
|
this.#bradcastPositionXmlHttp.open("POST", `http://127.0.0.1:${this.#cameraControlPort}`);
|
|
|
|
const C = 40075016.686;
|
|
let mpp = (C * Math.cos(deg2rad(this.getCenter().lat))) / Math.pow(2, this.getZoom() + 8);
|
|
let d = mpp * 1920;
|
|
let alt = (((d / 2) * 1) / Math.tan(deg2rad(40))) * this.#cameraZoomRatio;
|
|
alt = Math.min(alt, 50000);
|
|
this.#bradcastPositionXmlHttp.send(
|
|
JSON.stringify({
|
|
lat: this.getCenter().lat,
|
|
lng: this.getCenter().lng,
|
|
alt: alt + groundElevation,
|
|
mode: this.#cameraControlMode,
|
|
})
|
|
);
|
|
} catch {
|
|
console.warn("broadcastPosition: could not retrieve ground elevation");
|
|
}
|
|
});
|
|
}
|
|
|
|
/* */
|
|
#panToUnit(unit: Unit) {
|
|
var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng);
|
|
this.setView(unitPosition, this.getZoom(), { animate: false });
|
|
}
|
|
|
|
#getMinimapBoundaries() {
|
|
/* Draw the limits of the maps in the minimap*/
|
|
return minimapBoundaries;
|
|
}
|
|
|
|
#setSlaveDCSCameraAvailable(newSlaveDCSCameraAvailable: boolean) {
|
|
this.#slaveDCSCameraAvailable = newSlaveDCSCameraAvailable;
|
|
}
|
|
|
|
/* Check if the camera control plugin is available. Right now this will only change the color of the button, no changes in functionality */
|
|
#checkCameraPort() {
|
|
if (this.#cameraOptionsXmlHttp?.readyState !== 4) this.#cameraOptionsXmlHttp?.abort();
|
|
|
|
this.#cameraOptionsXmlHttp = new XMLHttpRequest();
|
|
|
|
/* Using 127.0.0.1 instead of localhost because the LuaSocket version used in DCS only listens to IPv4. This avoids the lag caused by the
|
|
browser if it first tries to send the request on the IPv6 address for localhost */
|
|
this.#cameraOptionsXmlHttp.open("OPTIONS", `http://127.0.0.1:${this.#cameraControlPort}`);
|
|
this.#cameraOptionsXmlHttp.onload = (res: any) => {
|
|
if (this.#cameraOptionsXmlHttp !== null && this.#cameraOptionsXmlHttp.status == 204) this.#setSlaveDCSCameraAvailable(true);
|
|
else this.#setSlaveDCSCameraAvailable(false);
|
|
};
|
|
this.#cameraOptionsXmlHttp.onerror = (res: any) => {
|
|
this.#setSlaveDCSCameraAvailable(false);
|
|
};
|
|
this.#cameraOptionsXmlHttp.ontimeout = (res: any) => {
|
|
this.#setSlaveDCSCameraAvailable(false);
|
|
};
|
|
this.#cameraOptionsXmlHttp.timeout = 500;
|
|
this.#cameraOptionsXmlHttp.send("");
|
|
}
|
|
|
|
#drawIPToTargetLine() {
|
|
if (this.#targetPoint && this.#IPPoint) {
|
|
if (!this.#IPToTargetLine) {
|
|
this.#IPToTargetLine = new L.Polygon([this.#targetPoint.getLatLng(), this.#IPPoint.getLatLng()]);
|
|
this.#IPToTargetLine.addTo(this);
|
|
} else this.#IPToTargetLine.setLatLngs([this.#targetPoint.getLatLng(), this.#IPPoint.getLatLng()]);
|
|
}
|
|
}
|
|
|
|
#updateDestinationPreviewMarkers() {
|
|
const selectedUnits = getApp()
|
|
.getUnitsManager()
|
|
.getSelectedUnits()
|
|
.filter((unit) => !unit.getHuman());
|
|
|
|
Object.keys(this.#destinationPreviewMarkers).forEach((ID) => {
|
|
this.#destinationPreviewMarkers[ID].removeFrom(this);
|
|
delete this.#destinationPreviewMarkers[ID];
|
|
});
|
|
|
|
selectedUnits.forEach((unit) => {
|
|
if (this.#contextAction?.getOptions().type === ContextActionType.MOVE) {
|
|
this.#destinationPreviewMarkers[unit.ID] = new TemporaryUnitMarker(new L.LatLng(0, 0), unit.getName(), unit.getCoalition());
|
|
} else if (this.#contextAction?.getTarget() === ContextActionTarget.POINT) {
|
|
this.#destinationPreviewMarkers[unit.ID] = new TargetMarker(new L.LatLng(0, 0));
|
|
}
|
|
this.#destinationPreviewMarkers[unit.ID]?.addTo(this);
|
|
});
|
|
}
|
|
|
|
#moveDestinationPreviewMarkers() {
|
|
if (this.#options.keepRelativePositions) {
|
|
Object.entries(getApp().getUnitsManager().computeGroupDestination(this.#lastMouseCoordinates, this.#destinationRotation)).forEach(([ID, latlng]) => {
|
|
this.#destinationPreviewMarkers[ID]?.setLatLng(latlng);
|
|
});
|
|
} else {
|
|
Object.values(this.#destinationPreviewMarkers).forEach((marker) => {
|
|
marker.setLatLng(this.#lastMouseCoordinates);
|
|
});
|
|
}
|
|
}
|
|
}
|