mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
More work on AWACS mode
This commit is contained in:
@@ -42,6 +42,7 @@ import {
|
||||
ContextActions,
|
||||
ContextActionTarget,
|
||||
SHORT_PRESS_MILLISECONDS,
|
||||
TRAIL_LENGTH,
|
||||
} from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { Weapon } from "../weapon/weapon";
|
||||
@@ -62,6 +63,8 @@ import {
|
||||
UnitUpdatedEvent,
|
||||
} from "../events";
|
||||
|
||||
const bearingStrings = ["north", "north-east", "east", "south-east", "south", "south-west", "west", "north-west", "north"];
|
||||
|
||||
var pathIcon = new Icon({
|
||||
iconUrl: "/vite/images/markers/marker-icon.png",
|
||||
shadowUrl: "/vite/images/markers/marker-shadow.png",
|
||||
@@ -157,6 +160,8 @@ export abstract class Unit extends CustomMarker {
|
||||
#targetPositionPolyline: Polyline;
|
||||
#hotgroup: number | null = null;
|
||||
#detectionMethods: number[] = [];
|
||||
#trailPositions: LatLng[] = [];
|
||||
#trailPolylines: Polyline[] = [];
|
||||
|
||||
/* Inputs timers */
|
||||
#debounceTimeout: number | null = null;
|
||||
@@ -466,6 +471,8 @@ export abstract class Unit extends CustomMarker {
|
||||
this.#hasTask = dataExtractor.extractBool();
|
||||
break;
|
||||
case DataIndexes.position:
|
||||
this.#trailPositions.unshift(this.#position);
|
||||
this.#trailPositions.splice(TRAIL_LENGTH);
|
||||
this.#position = dataExtractor.extractLatLng();
|
||||
updateMarker = true;
|
||||
break;
|
||||
@@ -902,7 +909,7 @@ export abstract class Unit extends CustomMarker {
|
||||
if (this.belongsToCommandedCoalition() || this.getDetectionMethods().some((value) => [VISUAL, OPTIC].includes(value)))
|
||||
marker = this.getBlueprint()?.markerFile ?? this.getDefaultMarker();
|
||||
else marker = "aircraft";
|
||||
img.src = `/vite/images/units/map/${getApp().getMap().getOptions().AWACSMode? 'awacs': 'normal'}/${this.getCoalition()}/${marker}.svg`;
|
||||
img.src = `/vite/images/units/map/${getApp().getMap().getOptions().AWACSMode ? "awacs" : "normal"}/${this.getCoalition()}/${marker}.svg`;
|
||||
img.onload = () => SVGInjector(img);
|
||||
unitIcon.appendChild(img);
|
||||
|
||||
@@ -968,28 +975,15 @@ export abstract class Unit extends CustomMarker {
|
||||
summary.appendChild(altitude);
|
||||
summary.appendChild(speed);
|
||||
el.appendChild(summary);
|
||||
}
|
||||
|
||||
var tactical = document.createElement("div");
|
||||
tactical.classList.add("unit-tactical");
|
||||
if (iconOptions.showBullseyes) {
|
||||
var bullseyes = document.createElement("div");
|
||||
bullseyes.classList.add("unit-bullseyes");
|
||||
var blueBullseye = document.createElement("div");
|
||||
blueBullseye.classList.add("unit-blue-bullseye");
|
||||
var redBullseye = document.createElement("div");
|
||||
redBullseye.classList.add("unit-red-bullseye");
|
||||
bullseyes.appendChild(blueBullseye);
|
||||
bullseyes.appendChild(redBullseye);
|
||||
tactical.appendChild(bullseyes);
|
||||
}
|
||||
var bullseye = document.createElement("div");
|
||||
bullseye.classList.add("unit-bullseye");
|
||||
summary.appendChild(bullseye);
|
||||
|
||||
if (iconOptions.showBRAA) {
|
||||
var BRAA = document.createElement("div");
|
||||
BRAA.classList.add("unit-braa");
|
||||
tactical.appendChild(BRAA);
|
||||
summary.appendChild(BRAA);
|
||||
}
|
||||
el.appendChild(tactical);
|
||||
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
@@ -1337,7 +1331,10 @@ export abstract class Unit extends CustomMarker {
|
||||
this.#isLeftMouseDown = true;
|
||||
this.#leftMouseDownEpoch = Date.now();
|
||||
} else if (e.originalEvent?.button === 2) {
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && getApp().getMap().getContextAction()?.getTarget() !== ContextActionTarget.POINT) {
|
||||
if (
|
||||
getApp().getState() === OlympusState.IDLE ||
|
||||
(getApp().getState() === OlympusState.UNIT_CONTROL && getApp().getMap().getContextAction()?.getTarget() !== ContextActionTarget.POINT)
|
||||
) {
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
@@ -1375,6 +1372,19 @@ export abstract class Unit extends CustomMarker {
|
||||
#onRightLongClick(e: any) {
|
||||
console.log(`Right long click on ${this.getUnitName()}`);
|
||||
|
||||
if (getApp().getState() === OlympusState.IDLE) {
|
||||
this.setSelected(!this.getSelected());
|
||||
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
|
||||
window.setTimeout(() => {
|
||||
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.UNIT_CONTEXT_MENU);
|
||||
UnitContextMenuRequestEvent.dispatch(this);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && !getApp().getMap().getContextAction()) {
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
@@ -1402,6 +1412,10 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
#updateMarker() {
|
||||
/* Delete existing trails */
|
||||
this.#trailPolylines.forEach((polyline) => polyline.removeFrom(getApp().getMap()));
|
||||
this.#trailPolylines = [];
|
||||
|
||||
this.updateVisibility();
|
||||
|
||||
/* Draw the minimap marker */
|
||||
@@ -1516,11 +1530,8 @@ export abstract class Unit extends CustomMarker {
|
||||
/* Set bullseyes positions */
|
||||
const bullseyes = getApp().getMissionManager().getBullseyes();
|
||||
if (Object.keys(bullseyes).length > 0) {
|
||||
computeBearingRangeString
|
||||
const blueBullseye = `${computeBearingRangeString(bullseyes[2].getLatLng(), this.getPosition())}`;
|
||||
const redBullseye = `${computeBearingRangeString(bullseyes[1].getLatLng(), this.getPosition())}`;
|
||||
if (element.querySelector(".unit-blue-bullseye")) (<HTMLElement>element.querySelector(".unit-blue-bullseye")).innerText = `${blueBullseye}`;
|
||||
if (element.querySelector(".unit-red-bullseye")) (<HTMLElement>element.querySelector(".unit-red-bullseye")).innerText = `${redBullseye}`;
|
||||
const bullseye = `${computeBearingRangeString(bullseyes[coalitionToEnum(getApp().getMap().getOptions().AWACSCoalition)].getLatLng(), this.getPosition())}`;
|
||||
if (element.querySelector(".unit-bullseye")) (<HTMLElement>element.querySelector(".unit-bullseye")).innerText = `${bullseye}`;
|
||||
}
|
||||
|
||||
/* Set BRAA */
|
||||
@@ -1534,6 +1545,35 @@ export abstract class Unit extends CustomMarker {
|
||||
/* Set vertical offset for altitude stacking */
|
||||
var pos = getApp().getMap().latLngToLayerPoint(this.getLatLng()).round();
|
||||
this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0));
|
||||
|
||||
/* Get the cluster this unit is in to position the label correctly */
|
||||
let cluster = Object.values(getApp().getUnitsManager().getClusters()).find((cluster) => cluster.includes(this));
|
||||
if (cluster && cluster.length > 1) {
|
||||
let clusterMean = turf.centroid(turf.featureCollection(cluster.map((unit) => turf.point([unit.getPosition().lng, unit.getPosition().lat]))));
|
||||
let bearingFromCluster = bearing(
|
||||
clusterMean.geometry.coordinates[1],
|
||||
clusterMean.geometry.coordinates[0],
|
||||
this.getPosition().lat,
|
||||
this.getPosition().lng
|
||||
);
|
||||
|
||||
if (bearingFromCluster < 0) bearingFromCluster += 360;
|
||||
let trackIndex = Math.round(bearingFromCluster / 45);
|
||||
|
||||
for (let idx = 0; idx < bearingStrings.length; idx++) element?.querySelector(".unit-summary")?.classList.remove("cluster-" + bearingStrings[idx]);
|
||||
element?.querySelector(".unit-summary")?.classList.add("cluster-" + bearingStrings[trackIndex]);
|
||||
} else {
|
||||
for (let idx = 0; idx < bearingStrings.length; idx++) element?.querySelector(".unit-summary")?.classList.remove("cluster-" + bearingStrings[idx]);
|
||||
element?.querySelector(".unit-summary")?.classList.add("cluster-north-east");
|
||||
}
|
||||
|
||||
/* Draw the contact trail */
|
||||
if (getApp().getMap().getOptions().AWACSMode) {
|
||||
this.#trailPolylines = this.#trailPositions.map(
|
||||
(latlng, idx) => new Polyline([latlng, latlng], { color: "#FFFFFF", opacity: 1 - (idx + 1) / TRAIL_LENGTH })
|
||||
);
|
||||
this.#trailPolylines.forEach((polyline) => polyline.addTo(getApp().getMap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1807,10 +1847,8 @@ export abstract class AirUnit extends Unit {
|
||||
showFuel: belongsToCommandedCoalition,
|
||||
showAmmo: belongsToCommandedCoalition,
|
||||
showSummary: belongsToCommandedCoalition || this.getDetectionMethods().some((value) => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value)),
|
||||
showCallsign: belongsToCommandedCoalition,
|
||||
rotateToHeading: false,
|
||||
showBullseyes: true,
|
||||
showBRAA: true,
|
||||
showCallsign: belongsToCommandedCoalition && (!getApp().getMap().getOptions().AWACSMode || this.getHuman()),
|
||||
rotateToHeading: false
|
||||
} as ObjectIconOptions;
|
||||
}
|
||||
|
||||
@@ -1898,10 +1936,8 @@ export class GroundUnit extends Unit {
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: false,
|
||||
showCallsign: belongsToCommandedCoalition,
|
||||
showCallsign: belongsToCommandedCoalition && (!getApp().getMap().getOptions().AWACSMode || this.getHuman()),
|
||||
rotateToHeading: false,
|
||||
showBullseyes: false,
|
||||
showBRAA: false,
|
||||
} as ObjectIconOptions;
|
||||
}
|
||||
|
||||
@@ -1969,10 +2005,8 @@ export class NavyUnit extends Unit {
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: false,
|
||||
showCallsign: belongsToCommandedCoalition,
|
||||
showCallsign: belongsToCommandedCoalition && (!getApp().getMap().getOptions().AWACSMode || this.getHuman()),
|
||||
rotateToHeading: false,
|
||||
showBullseyes: false,
|
||||
showBRAA: false,
|
||||
} as ObjectIconOptions;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { Unit } from "./unit";
|
||||
import {
|
||||
areaContains,
|
||||
bearingAndDistanceToLatLng,
|
||||
deg2rad,
|
||||
getGroundElevation,
|
||||
latLngToMercator,
|
||||
mToFt,
|
||||
mercatorToLatLng,
|
||||
msToKnots,
|
||||
} from "../other/utils";
|
||||
import { AirUnit, Unit } from "./unit";
|
||||
import { areaContains, bearingAndDistanceToLatLng, deg2rad, getGroundElevation, latLngToMercator, mToFt, mercatorToLatLng, msToKnots } from "../other/utils";
|
||||
import { CoalitionPolygon } from "../map/coalitionarea/coalitionpolygon";
|
||||
import { DELETE_CYCLE_TIME, DELETE_SLOW_THRESHOLD, DataIndexes, GAME_MASTER, IADSDensities, OlympusState, UnitControlSubState } from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
@@ -33,6 +24,8 @@ import {
|
||||
UnitSelectedEvent,
|
||||
} from "../events";
|
||||
import { UnitDatabase } from "./databases/unitdatabase";
|
||||
import * as turf from "@turf/turf";
|
||||
import * as turfC from "@turf/clusters";
|
||||
|
||||
/** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only
|
||||
* result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows
|
||||
@@ -51,6 +44,7 @@ export class UnitsManager {
|
||||
#unitDatabase: UnitDatabase;
|
||||
#protectionCallback: (units: Unit[]) => void = (units) => {};
|
||||
#AWACSReference: Unit | null = null;
|
||||
#clusters: {[key: number]: Unit[]} = {};
|
||||
|
||||
constructor() {
|
||||
this.#unitDatabase = new UnitDatabase();
|
||||
@@ -82,7 +76,7 @@ export class UnitsManager {
|
||||
code: "KeyA",
|
||||
ctrlKey: true,
|
||||
shiftKey: false,
|
||||
altKey: false
|
||||
altKey: false,
|
||||
})
|
||||
.addShortcut("copyUnits", {
|
||||
label: "Copy units",
|
||||
@@ -90,7 +84,7 @@ export class UnitsManager {
|
||||
code: "KeyC",
|
||||
ctrlKey: true,
|
||||
shiftKey: false,
|
||||
altKey: false
|
||||
altKey: false,
|
||||
})
|
||||
.addShortcut("pasteUnits", {
|
||||
label: "Paste units",
|
||||
@@ -98,7 +92,7 @@ export class UnitsManager {
|
||||
code: "KeyV",
|
||||
ctrlKey: true,
|
||||
shiftKey: false,
|
||||
altKey: false
|
||||
altKey: false,
|
||||
});
|
||||
|
||||
const digits = ["Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9"];
|
||||
@@ -113,8 +107,9 @@ export class UnitsManager {
|
||||
code: code,
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
ctrlKey: false
|
||||
}).addShortcut(`hotgroup${idx + 1}add`, {
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut(`hotgroup${idx + 1}add`, {
|
||||
label: `Hotgroup ${idx + 1} (Add to)`,
|
||||
keyUpCallback: (ev: KeyboardEvent) => {
|
||||
this.addToHotgroup(parseInt(ev.code.substring(5)));
|
||||
@@ -122,8 +117,9 @@ export class UnitsManager {
|
||||
code: code,
|
||||
shiftKey: true,
|
||||
altKey: false,
|
||||
ctrlKey: false
|
||||
}).addShortcut(`hotgroup${idx + 1}set`, {
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut(`hotgroup${idx + 1}set`, {
|
||||
label: `Hotgroup ${idx + 1} (Set)`,
|
||||
keyUpCallback: (ev: KeyboardEvent) => {
|
||||
this.setHotgroup(parseInt(ev.code.substring(5)));
|
||||
@@ -131,8 +127,9 @@ export class UnitsManager {
|
||||
code: code,
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false
|
||||
}).addShortcut(`hotgroup${idx + 1}also`, {
|
||||
shiftKey: false,
|
||||
})
|
||||
.addShortcut(`hotgroup${idx + 1}also`, {
|
||||
label: `Hotgroup ${idx + 1} (Select also)`,
|
||||
keyUpCallback: (ev: KeyboardEvent) => {
|
||||
this.selectUnitsByHotgroup(parseInt(ev.code.substring(5)), false);
|
||||
@@ -140,7 +137,7 @@ export class UnitsManager {
|
||||
code: code,
|
||||
ctrlKey: true,
|
||||
shiftKey: true,
|
||||
altKey: false
|
||||
altKey: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -280,6 +277,9 @@ export class UnitsManager {
|
||||
if (this.#units[ID].getSelected()) this.#units[ID].drawLines();
|
||||
}
|
||||
|
||||
/* Compute the base clusters */
|
||||
this.#clusters = this.computeClusters();
|
||||
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
@@ -1548,13 +1548,37 @@ export class UnitsManager {
|
||||
|
||||
setAWACSReference(ID) {
|
||||
this.#AWACSReference = this.#units[ID] ?? null;
|
||||
AWACSReferenceChangedEvent.dispatch(this.#AWACSReference)
|
||||
AWACSReferenceChangedEvent.dispatch(this.#AWACSReference);
|
||||
}
|
||||
|
||||
getAWACSReference() {
|
||||
return this.#AWACSReference;
|
||||
}
|
||||
|
||||
computeClusters(filter: (unit: Unit) => boolean = (unit) => true, distance: number = 5 /* km */) {
|
||||
let units = Object.values(this.#units)
|
||||
.filter((unit) => unit.getAlive() && unit instanceof AirUnit)
|
||||
.filter(filter);
|
||||
|
||||
var geojson = turf.featureCollection(units.map((unit) => turf.point([unit.getPosition().lng, unit.getPosition().lat])));
|
||||
|
||||
//@ts-ignore
|
||||
var clustered = turf.clustersDbscan(geojson, distance, { minPoints: 1 });
|
||||
|
||||
let clusters: {[key: number]: Unit[]} = {};
|
||||
clustered.features.forEach((feature, idx) => {
|
||||
if (clusters[feature.properties.cluster] === undefined)
|
||||
clusters[feature.properties.cluster] = [] as Unit[];
|
||||
clusters[feature.properties.cluster].push(units[idx]);
|
||||
})
|
||||
|
||||
return clusters;
|
||||
}
|
||||
|
||||
getClusters() {
|
||||
return this.#clusters;
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
#onUnitSelection(unit: Unit) {
|
||||
if (this.getSelectedUnits().length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user