mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge branch 'release-candidate' into features/redgreen-unit
This commit is contained in:
commit
f7e9fc5cbc
@ -13,7 +13,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill="#3BB9FF"
|
||||
fill="#none"
|
||||
stroke="none"
|
||||
stroke-width="2"
|
||||
d="M45.7733 41.3423L25.9481 7.63951C25.5228 6.91648 24.4772 6.91646 24.0519 7.63951L4.22671 41.3423C3.79536 42.0756 4.32409 43 5.17484 43H44.8252C45.6759 43 46.2046 42.0756 45.7733 41.3423Z"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@ -13,7 +13,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill="#3BB9FF"
|
||||
fill="#none"
|
||||
stroke="none"
|
||||
stroke-width="2"
|
||||
d="M45.7733 41.3423L25.9481 7.63951C25.5228 6.91648 24.4772 6.91646 24.0519 7.63951L4.22671 41.3423C3.79536 42.0756 4.32409 43 5.17484 43H44.8252C45.6759 43 46.2046 42.0756 45.7733 41.3423Z"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@ -13,7 +13,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill="#3BB9FF"
|
||||
fill="#none"
|
||||
stroke="none"
|
||||
stroke-width="2"
|
||||
d="M45.7733 41.3423L25.9481 7.63951C25.5228 6.91648 24.4772 6.91646 24.0519 7.63951L4.22671 41.3423C3.79536 42.0756 4.32409 43 5.17484 43H44.8252C45.6759 43 46.2046 42.0756 45.7733 41.3423Z"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@ -420,6 +420,7 @@ export const MAP_OPTIONS_DEFAULTS: MapOptions = {
|
||||
hideChromeWarning: false,
|
||||
hideSecureWarning: false,
|
||||
showMissionDrawings: false,
|
||||
clusterGroundUnits: true
|
||||
};
|
||||
|
||||
export const MAP_HIDDEN_TYPES_DEFAULTS = {
|
||||
@ -517,7 +518,8 @@ export const DELETE_CYCLE_TIME = 0.05;
|
||||
export const DELETE_SLOW_THRESHOLD = 50;
|
||||
|
||||
export const GROUPING_ZOOM_TRANSITION = 13;
|
||||
export const SPOTS_EDIT_ZOOM_TRANSITION = 14;
|
||||
export const CLUSTERING_ZOOM_TRANSITION = 13;
|
||||
export const SPOTS_EDIT_ZOOM_TRANSITION = 13;
|
||||
|
||||
export const MAX_SHOTS_SCATTER = 3;
|
||||
export const MAX_SHOTS_INTENSITY = 3;
|
||||
|
||||
@ -14,6 +14,8 @@ import { Unit } from "./unit/unit";
|
||||
import { LatLng } from "leaflet";
|
||||
import { Weapon } from "./weapon/weapon";
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
export class BaseOlympusEvent {
|
||||
static on(callback: () => void, singleShot = false) {
|
||||
document.addEventListener(
|
||||
@ -27,7 +29,7 @@ export class BaseOlympusEvent {
|
||||
|
||||
static dispatch() {
|
||||
document.dispatchEvent(new CustomEvent(this.name));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,8 +46,8 @@ export class BaseUnitEvent {
|
||||
|
||||
static dispatch(unit: Unit) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { unit } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
console.log(unit);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(unit);
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +64,7 @@ export class BaseUnitsEvent {
|
||||
|
||||
static dispatch(units: Unit[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: units }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,8 +83,8 @@ export class AppStateChangedEvent {
|
||||
static dispatch(state: OlympusState, subState: OlympusSubState) {
|
||||
const detail = { state, subState };
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
console.log(`State: ${state} Substate: ${subState}`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`State: ${state} Substate: ${subState}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,8 +101,8 @@ export class ConfigLoadedEvent {
|
||||
|
||||
static dispatch(config: OlympusConfig) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: config }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
console.log(config);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(config);
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,7 +138,7 @@ export class InfoPopupEvent {
|
||||
|
||||
static dispatch(messages: string[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { messages } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,7 +157,7 @@ export class ShortcutsChangedEvent {
|
||||
|
||||
static dispatch(shortcuts: { [key: string]: Shortcut }) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { shortcuts } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,7 +174,7 @@ export class ShortcutChangedEvent {
|
||||
|
||||
static dispatch(shortcut: Shortcut) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { shortcut } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,7 +191,7 @@ export class BindShortcutRequestEvent {
|
||||
|
||||
static dispatch(shortcut: Shortcut) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { shortcut } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,7 +208,7 @@ export class ModalEvent {
|
||||
|
||||
static dispatch(modal: boolean) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { modal } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +225,7 @@ export class SessionDataChangedEvent {
|
||||
|
||||
static dispatch(sessionData: SessionData) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { sessionData } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,7 +245,7 @@ export class AdminPasswordChangedEvent {
|
||||
|
||||
static dispatch(password: string) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { password } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,24 +280,24 @@ export class HiddenTypesChangedEvent {
|
||||
|
||||
static dispatch(hiddenTypes: MapHiddenTypes) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { hiddenTypes } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class MapOptionsChangedEvent {
|
||||
static on(callback: (mapOptions: MapOptions) => void, singleShot = false) {
|
||||
static on(callback: (mapOptions: MapOptions, key: keyof MapOptions | undefined) => void, singleShot = false) {
|
||||
document.addEventListener(
|
||||
this.name,
|
||||
(ev: CustomEventInit) => {
|
||||
callback(ev.detail.mapOptions);
|
||||
callback(ev.detail.mapOptions, ev.detail.key);
|
||||
},
|
||||
{ once: singleShot }
|
||||
);
|
||||
}
|
||||
|
||||
static dispatch(mapOptions: MapOptions) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { mapOptions } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
static dispatch(mapOptions: MapOptions, key?: (keyof MapOptions) | undefined) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { mapOptions, key: key } }));
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,7 +314,7 @@ export class MapSourceChangedEvent {
|
||||
|
||||
static dispatch(source: string) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { source } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -329,7 +331,7 @@ export class CoalitionAreaSelectedEvent {
|
||||
|
||||
static dispatch(coalitionArea: CoalitionCircle | CoalitionPolygon | null) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { coalitionArea } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -348,7 +350,7 @@ export class CoalitionAreasChangedEvent {
|
||||
|
||||
static dispatch(coalitionAreas: (CoalitionCircle | CoalitionPolygon)[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { coalitionAreas } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,7 +367,7 @@ export class AirbaseSelectedEvent {
|
||||
|
||||
static dispatch(airbase: Airbase | null) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { airbase } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,7 +384,7 @@ export class SelectionEnabledChangedEvent {
|
||||
|
||||
static dispatch(enabled: boolean) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { enabled } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -399,7 +401,7 @@ export class PasteEnabledChangedEvent {
|
||||
|
||||
static dispatch(enabled: boolean) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { enabled } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,7 +435,7 @@ export class ContextActionSetChangedEvent {
|
||||
|
||||
static dispatch(contextActionSet: ContextActionSet | null) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { contextActionSet } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -450,7 +452,7 @@ export class ContextActionChangedEvent {
|
||||
|
||||
static dispatch(contextAction: ContextAction | null) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { contextAction } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -467,7 +469,7 @@ export class CopiedUnitsEvents {
|
||||
|
||||
static dispatch(unitsData: UnitData[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { unitsData } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -505,7 +507,7 @@ export class UnitExplosionRequestEvent {
|
||||
|
||||
static dispatch(units: Unit[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { units } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -522,7 +524,7 @@ export class FormationCreationRequestEvent {
|
||||
|
||||
static dispatch(leader: Unit, wingmen: Unit[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { leader, wingmen } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -539,7 +541,7 @@ export class MapContextMenuRequestEvent {
|
||||
|
||||
static dispatch(latlng: L.LatLng) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { latlng } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -556,7 +558,7 @@ export class UnitContextMenuRequestEvent {
|
||||
|
||||
static dispatch(unit: Unit) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { unit } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -573,7 +575,7 @@ export class SpawnContextMenuRequestEvent {
|
||||
|
||||
static dispatch(latlng: L.LatLng) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { latlng } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -590,7 +592,7 @@ export class SpawnHeadingChangedEvent {
|
||||
|
||||
static dispatch(heading: number) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { heading } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -607,7 +609,7 @@ export class HotgroupsChangedEvent {
|
||||
|
||||
static dispatch(hotgroups: { [key: number]: Unit[] }) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { hotgroups } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -624,7 +626,7 @@ export class StarredSpawnsChangedEvent {
|
||||
|
||||
static dispatch(starredSpawns: { [key: number]: SpawnRequestTable }) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { starredSpawns } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -658,7 +660,7 @@ export class DrawingsInitEvent {
|
||||
|
||||
static dispatch(drawingsData: any /*TODO*/) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: drawingsData}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -678,7 +680,7 @@ export class CommandModeOptionsChangedEvent {
|
||||
|
||||
static dispatch(options: CommandModeOptions) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: options }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -696,8 +698,8 @@ export class AudioSourcesChangedEvent {
|
||||
|
||||
static dispatch(audioSources: AudioSource[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { audioSources } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
console.log(audioSources);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(audioSources);
|
||||
}
|
||||
}
|
||||
|
||||
@ -714,8 +716,8 @@ export class AudioSinksChangedEvent {
|
||||
|
||||
static dispatch(audioSinks: AudioSink[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { audioSinks } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
console.log(audioSinks);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(audioSinks);
|
||||
}
|
||||
}
|
||||
|
||||
@ -749,7 +751,7 @@ export class AudioManagerStateChangedEvent {
|
||||
|
||||
static dispatch(state: boolean) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { state } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -766,7 +768,7 @@ export class AudioManagerDevicesChangedEvent {
|
||||
|
||||
static dispatch(devices: MediaDeviceInfo[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { devices } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -783,7 +785,7 @@ export class AudioManagerInputChangedEvent {
|
||||
|
||||
static dispatch(input: MediaDeviceInfo) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { input } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -800,7 +802,7 @@ export class AudioManagerOutputChangedEvent {
|
||||
|
||||
static dispatch(output: MediaDeviceInfo) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { output } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -817,7 +819,7 @@ export class AudioManagerCoalitionChangedEvent {
|
||||
|
||||
static dispatch(coalition: Coalition) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { coalition } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -887,6 +889,6 @@ export class WeaponsRefreshedEvent {
|
||||
|
||||
static dispatch(weapons: Weapon[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: weapons }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,6 +172,7 @@ export interface ObjectIconOptions {
|
||||
showSummary: boolean;
|
||||
showCallsign: boolean;
|
||||
rotateToHeading: boolean;
|
||||
showCluster: boolean;
|
||||
}
|
||||
|
||||
export interface GeneralSettings {
|
||||
|
||||
@ -779,7 +779,7 @@ export class Map extends L.Map {
|
||||
|
||||
setOption(key, value) {
|
||||
this.#options[key] = value;
|
||||
MapOptionsChangedEvent.dispatch(this.#options);
|
||||
MapOptionsChangedEvent.dispatch(this.#options, key);
|
||||
}
|
||||
|
||||
setOptions(options) {
|
||||
|
||||
33
frontend/react/src/map/markers/clustermarker.ts
Normal file
33
frontend/react/src/map/markers/clustermarker.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { Coalition } from "../../types/types";
|
||||
|
||||
export class ClusterMarker extends CustomMarker {
|
||||
#coalition: Coalition;
|
||||
#numberOfUnits: number;
|
||||
|
||||
constructor(latlng: LatLngExpression, coalition: Coalition, numberOfUnits:number, options?: MarkerOptions) {
|
||||
super(latlng, options);
|
||||
this.setZIndexOffset(9999);
|
||||
this.#coalition = coalition;
|
||||
this.#numberOfUnits = numberOfUnits;
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
this.setIcon(
|
||||
new DivIcon({
|
||||
iconSize: [52, 52],
|
||||
iconAnchor: [26, 26],
|
||||
className: "leaflet-cluster-marker",
|
||||
})
|
||||
);
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("ol-cluster-icon");
|
||||
el.classList.add(`${this.#coalition}`);
|
||||
this.getElement()?.appendChild(el);
|
||||
var span = document.createElement("span");
|
||||
span.classList.add("ol-cluster-number");
|
||||
span.textContent = `${this.#numberOfUnits}`;
|
||||
el.appendChild(span);
|
||||
}
|
||||
}
|
||||
@ -93,6 +93,42 @@
|
||||
translate: -1px 1px;
|
||||
}
|
||||
|
||||
.unit-cluster {
|
||||
border: 2px solid #272727;
|
||||
border-radius: var(--border-radius-xs);
|
||||
display: none;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
translate: 70% -70%;
|
||||
width: 30px;
|
||||
|
||||
}
|
||||
|
||||
.unit-cluster.red {
|
||||
background-color: var(--unit-background-red);
|
||||
}
|
||||
|
||||
.unit-cluster.blue {
|
||||
background-color: var(--unit-background-blue);
|
||||
}
|
||||
|
||||
.unit-cluster.neutral {
|
||||
background-color: var(--unit-background-neutral);
|
||||
}
|
||||
|
||||
.unit-cluster-id {
|
||||
background-color: transparent;
|
||||
color: #272727;
|
||||
font-size: 12px;
|
||||
font-weight: bolder;
|
||||
translate: -3px -1px;
|
||||
border-left: 3px solid #272727;
|
||||
height: 50px;
|
||||
padding-left: 4px;
|
||||
text-align: center;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.unit-icon {
|
||||
height: var(--unit-height);
|
||||
position: absolute;
|
||||
@ -404,6 +440,7 @@
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-is-in-hotgroup] .unit-hotgroup,
|
||||
[data-object|="unit"][data-is-cluster-leader] .unit-cluster,
|
||||
[data-object|="unit"][data-is-selected] .unit-ammo,
|
||||
[data-object|="unit"][data-is-selected] .unit-fuel,
|
||||
[data-object|="unit"][data-is-selected] .unit-health,
|
||||
@ -443,7 +480,7 @@
|
||||
color: var(--secondary-red-text);
|
||||
}
|
||||
|
||||
[data-object|="unit"][data-coalition="red"][data-is-selected] path {
|
||||
[data-object|="unit"][data-coalition="red"][data-is-selected] path:nth-child(1) {
|
||||
fill: var(--secondary-red-text);
|
||||
}
|
||||
|
||||
@ -561,6 +598,8 @@
|
||||
[data-object|="unit"][data-is-dead] .unit-vvi,
|
||||
[data-object|="unit"][data-is-dead] .unit-hotgroup,
|
||||
[data-object|="unit"][data-is-dead] .unit-hotgroup-id,
|
||||
[data-object|="unit"][data-is-dead] .unit-cluster,
|
||||
[data-object|="unit"][data-is-dead] .unit-cluster-id,
|
||||
[data-object|="unit"][data-is-dead] .unit-state,
|
||||
[data-object|="unit"][data-is-dead] .unit-fuel,
|
||||
[data-object|="unit"][data-is-dead] .unit-health,
|
||||
|
||||
@ -277,3 +277,34 @@ path.leaflet-interactive:focus {
|
||||
.ol-arrow-icon svg path {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.ol-cluster-icon {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
filter: drop-shadow(3px 3px 3px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
|
||||
.ol-cluster-icon.neutral {
|
||||
background-image: url("/images/markers/cluster-neutral.svg");;
|
||||
}
|
||||
|
||||
.ol-cluster-icon.blue {
|
||||
background-image: url("/images/markers/cluster-blue.svg");;
|
||||
}
|
||||
|
||||
.ol-cluster-icon.red {
|
||||
background-image: url("/images/markers/cluster-red.svg");;
|
||||
}
|
||||
|
||||
.ol-cluster-number {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #272727;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@ -216,7 +216,7 @@ export class ServerManager {
|
||||
}
|
||||
|
||||
getUnits(callback: CallableFunction, refresh: boolean = false, errorCallback: CallableFunction = () => {}) {
|
||||
this.GET(callback, errorCallback, UNITS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[UNITS_URI] }, "arraybuffer", refresh);
|
||||
this.GET(callback, errorCallback, UNITS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[UNITS_URI] }, "arraybuffer", false);
|
||||
}
|
||||
|
||||
getWeapons(callback: CallableFunction, refresh: boolean = false, errorCallback: CallableFunction = () => {}) {
|
||||
@ -343,7 +343,13 @@ export class ServerManager {
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
|
||||
cloneUnits(units: { ID: number; location: LatLng }[], deleteOriginal: boolean, spawnPoints: number, coalition: Coalition, callback: CallableFunction = () => {}) {
|
||||
cloneUnits(
|
||||
units: { ID: number; location: LatLng }[],
|
||||
deleteOriginal: boolean,
|
||||
spawnPoints: number,
|
||||
coalition: Coalition,
|
||||
callback: CallableFunction = () => {}
|
||||
) {
|
||||
var command = {
|
||||
units: units,
|
||||
coalition: coalition,
|
||||
@ -589,7 +595,7 @@ export class ServerManager {
|
||||
targetingRange: targetingRange,
|
||||
aimMethodRange: aimMethodRange,
|
||||
acquisitionRange: acquisitionRange,
|
||||
}
|
||||
};
|
||||
|
||||
var data = { setEngagementProperties: command };
|
||||
this.PUT(data, callback);
|
||||
@ -625,11 +631,16 @@ export class ServerManager {
|
||||
|
||||
loadEnvResources() {
|
||||
/* Load the drawings */
|
||||
this.getDrawings((drawingsData: { drawings: Record<string, Record<string, any>> }) => {
|
||||
if (drawingsData) {
|
||||
getApp().getDrawingsManager()?.initDrawings(drawingsData);
|
||||
}
|
||||
}, () => {});
|
||||
this.getDrawings(
|
||||
(drawingsData: { drawings: Record<string, Record<string, any>> }) => {
|
||||
if (drawingsData) {
|
||||
getApp().getDrawingsManager()?.initDrawings(drawingsData);
|
||||
}
|
||||
},
|
||||
() => {}
|
||||
);
|
||||
|
||||
// TODO: load navPoints
|
||||
}
|
||||
|
||||
startUpdate() {
|
||||
@ -795,10 +806,12 @@ export class ServerManager {
|
||||
return time;
|
||||
}, true);
|
||||
|
||||
this.getUnits((buffer: ArrayBuffer) => {
|
||||
var time = getApp().getUnitsManager()?.update(buffer, true);
|
||||
return time;
|
||||
}, true);
|
||||
window.setInterval(() => {
|
||||
this.getUnits((buffer: ArrayBuffer) => {
|
||||
var time = getApp().getUnitsManager()?.update(buffer, true);
|
||||
return time;
|
||||
}, true);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
checkSessionHash(newSessionHash: string) {
|
||||
|
||||
@ -31,6 +31,7 @@ export type MapOptions = {
|
||||
hideChromeWarning: boolean;
|
||||
hideSecureWarning: boolean;
|
||||
showMissionDrawings: boolean;
|
||||
clusterGroundUnits: boolean;
|
||||
};
|
||||
|
||||
export type MapHiddenTypes = {
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
faWifi,
|
||||
faHourglass,
|
||||
faInfo,
|
||||
faObjectGroup,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
@ -166,8 +167,8 @@ export function Header() {
|
||||
<FaFloppyDisk className={`absolute -top-3 text-2xl`} />
|
||||
<FaCheck
|
||||
className={`
|
||||
absolute left-[9px] top-[-6px] text-2xl text-olympus-900
|
||||
`}
|
||||
absolute left-[9px] top-[-6px] text-2xl text-olympus-900
|
||||
`}
|
||||
/>
|
||||
<FaCheck className={`absolute left-3 top-0 text-green-500`} />
|
||||
</div>
|
||||
@ -328,6 +329,13 @@ export function Header() {
|
||||
className={""}
|
||||
tooltip={"Hide/show units acquisition rings"}
|
||||
/>
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setOption("clusterGroundUnits", !mapOptions.clusterGroundUnits)}
|
||||
checked={mapOptions.clusterGroundUnits}
|
||||
icon={faObjectGroup}
|
||||
className={""}
|
||||
tooltip={"Enable/disable ground unit clustering"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<OlLabelToggle
|
||||
|
||||
@ -48,6 +48,7 @@ import {
|
||||
colors,
|
||||
UnitState,
|
||||
SPOTS_EDIT_ZOOM_TRANSITION,
|
||||
CLUSTERING_ZOOM_TRANSITION,
|
||||
} from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { Weapon } from "../weapon/weapon";
|
||||
@ -191,6 +192,8 @@ export abstract class Unit extends CustomMarker {
|
||||
#acquisitionRange: number = 0;
|
||||
#totalAmmo: number = 0;
|
||||
#previousTotalAmmo: number = 0;
|
||||
#isClusterLeader: boolean = false;
|
||||
#clusterUnits: Unit[] = [];
|
||||
|
||||
/* Inputs timers */
|
||||
#debounceTimeout: number | null = null;
|
||||
@ -477,8 +480,16 @@ export abstract class Unit extends CustomMarker {
|
||||
});
|
||||
|
||||
/* Update the marker when the options change */
|
||||
MapOptionsChangedEvent.on(() => {
|
||||
this.#redrawMarker();
|
||||
MapOptionsChangedEvent.on((mapOptions, key) => {
|
||||
if (
|
||||
key === undefined ||
|
||||
key === "hideGroupMembers" ||
|
||||
key === "clusterGroundUnits" ||
|
||||
key === "showUnitLabels" ||
|
||||
key === "AWACSMode" ||
|
||||
key === "AWACSCoalition"
|
||||
)
|
||||
this.#redrawMarker();
|
||||
|
||||
/* Circles don't like to be updated when the map is zooming */
|
||||
if (!getApp().getMap().isZooming()) this.#drawRanges();
|
||||
@ -909,7 +920,7 @@ export abstract class Unit extends CustomMarker {
|
||||
|
||||
/* When the group leader is selected, if grouping is active, all the other group members are also selected */
|
||||
if (this.getCategory() === "GroundUnit" && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION) {
|
||||
if (this.#isLeader) {
|
||||
if (this.#isLeader && this.getGroupMembers().length > 0) {
|
||||
/* Redraw the marker in case the leader unit was replaced by a group marker, like for SAM Sites */
|
||||
this.#redrawMarker();
|
||||
this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected));
|
||||
@ -918,6 +929,17 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
}
|
||||
|
||||
/* When the group leader is selected, if clustering is active, all the other group members are also selected */
|
||||
if (this.getCategory() === "GroundUnit" && getApp().getMap().getZoom() < CLUSTERING_ZOOM_TRANSITION) {
|
||||
if (this.#isClusterLeader && this.#clusterUnits.length > 0) {
|
||||
/* Redraw the marker in case the leader unit was replaced by a group marker, like for SAM Sites */
|
||||
this.#redrawMarker();
|
||||
this.#clusterUnits.forEach((unit: Unit) => unit.setSelected(selected));
|
||||
} else {
|
||||
this.#updateMarker();
|
||||
}
|
||||
}
|
||||
|
||||
/* Activate the selection effects on the marker */
|
||||
this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected);
|
||||
|
||||
@ -1107,6 +1129,17 @@ export abstract class Unit extends CustomMarker {
|
||||
el.append(hotgroup);
|
||||
}
|
||||
|
||||
/* Cluster indicator */
|
||||
if (iconOptions.showCluster) {
|
||||
var cluster = document.createElement("div");
|
||||
cluster.classList.add("unit-cluster");
|
||||
cluster.classList.add(this.getCoalition());
|
||||
var clusterId = document.createElement("div");
|
||||
clusterId.classList.add("unit-cluster-id");
|
||||
cluster.appendChild(clusterId);
|
||||
el.append(cluster);
|
||||
}
|
||||
|
||||
/* Main icon */
|
||||
if (iconOptions.showUnitIcon) {
|
||||
var unitIcon = document.createElement("div");
|
||||
@ -1227,6 +1260,13 @@ export abstract class Unit extends CustomMarker {
|
||||
!this.getSelected() &&
|
||||
this.getCategory() == "GroundUnit" &&
|
||||
getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION &&
|
||||
(this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0))) ||
|
||||
/* Hide the unit if clustering is activated, the unit is in a cluster, it is not selected, and the zoom is higher than the clustering threshold */
|
||||
(getApp().getMap().getOptions().clusterGroundUnits &&
|
||||
!this.#isClusterLeader &&
|
||||
!this.getSelected() &&
|
||||
this.getCategory() == "GroundUnit" &&
|
||||
getApp().getMap().getZoom() < CLUSTERING_ZOOM_TRANSITION &&
|
||||
(this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)));
|
||||
|
||||
/* Force dead units to be hidden */
|
||||
@ -1287,6 +1327,14 @@ export abstract class Unit extends CustomMarker {
|
||||
return getApp().getUnitsManager().getUnitByID(this.#leaderID);
|
||||
}
|
||||
|
||||
setClusterUnits(clusterUnits: Unit[]) {
|
||||
this.#clusterUnits = clusterUnits;
|
||||
}
|
||||
|
||||
setIsClusterLeader(clusterLeader: boolean) {
|
||||
this.#isClusterLeader = clusterLeader;
|
||||
}
|
||||
|
||||
canFulfillRole(roles: string | string[]) {
|
||||
if (typeof roles === "string") roles = [roles];
|
||||
|
||||
@ -1788,12 +1836,28 @@ export abstract class Unit extends CustomMarker {
|
||||
if (hasFox3 != newHasFox3) element.querySelector(".unit")?.toggleAttribute("data-has-fox-3", newHasFox3);
|
||||
if (hasOtherAmmo != newHasOtherAmmo) element.querySelector(".unit")?.toggleAttribute("data-has-other-ammo", newHasOtherAmmo);
|
||||
|
||||
/* Draw the hotgroup element */
|
||||
element.querySelector(".unit")?.toggleAttribute("data-is-in-hotgroup", this.#hotgroup != null);
|
||||
if (this.#hotgroup) {
|
||||
const hotgroupEl = element.querySelector(".unit-hotgroup-id") as HTMLElement;
|
||||
if (hotgroupEl) hotgroupEl.innerText = String(this.#hotgroup);
|
||||
}
|
||||
/* Draw the hotgroup element */
|
||||
element.querySelector(".unit")?.toggleAttribute("data-is-in-hotgroup", this.#hotgroup != null);
|
||||
if (this.#hotgroup) {
|
||||
const hotgroupEl = element.querySelector(".unit-hotgroup-id") as HTMLElement;
|
||||
if (hotgroupEl) hotgroupEl.innerText = String(this.#hotgroup);
|
||||
}
|
||||
|
||||
/* Draw the cluster element */
|
||||
element
|
||||
.querySelector(".unit")
|
||||
?.toggleAttribute(
|
||||
"data-is-cluster-leader",
|
||||
this.#isClusterLeader &&
|
||||
this.#clusterUnits.length > 1 &&
|
||||
getApp().getMap().getOptions().clusterGroundUnits &&
|
||||
getApp().getMap().getZoom() < CLUSTERING_ZOOM_TRANSITION &&
|
||||
!this.getSelected()
|
||||
);
|
||||
if (this.#isClusterLeader && this.#clusterUnits.length > 1) {
|
||||
const clusterEl = element.querySelector(".unit-cluster-id") as HTMLElement;
|
||||
if (clusterEl) clusterEl.innerText = String(this.#clusterUnits.length);
|
||||
}
|
||||
|
||||
/* Set bullseyes positions */
|
||||
const bullseyes = getApp().getMissionManager().getBullseyes();
|
||||
@ -2361,6 +2425,7 @@ export abstract class AirUnit extends Unit {
|
||||
showSummary: belongsToCommandedCoalition || this.getDetectionMethods().some((value) => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value)),
|
||||
showCallsign: belongsToCommandedCoalition && /*TODO !getApp().getMap().getOptions().AWACSMode || */ this.getHuman(),
|
||||
rotateToHeading: false,
|
||||
showCluster: false,
|
||||
} as ObjectIconOptions;
|
||||
}
|
||||
|
||||
@ -2448,6 +2513,7 @@ export class GroundUnit extends Unit {
|
||||
showSummary: false,
|
||||
showCallsign: belongsToCommandedCoalition && /*TODO !getApp().getMap().getOptions().AWACSMode || */ this.getHuman(),
|
||||
rotateToHeading: false,
|
||||
showCluster: true,
|
||||
} as ObjectIconOptions;
|
||||
}
|
||||
|
||||
@ -2476,6 +2542,7 @@ export class GroundUnit extends Unit {
|
||||
checkZoomRedraw(): boolean {
|
||||
return (
|
||||
this.getIsLeader() &&
|
||||
this.getGroupMembers().length > 0 &&
|
||||
(getApp().getMap().getOptions().hideGroupMembers as boolean) &&
|
||||
((getApp().getMap().getZoom() >= GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() < GROUPING_ZOOM_TRANSITION) ||
|
||||
(getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() >= GROUPING_ZOOM_TRANSITION))
|
||||
@ -2513,6 +2580,7 @@ export class NavyUnit extends Unit {
|
||||
showSummary: false,
|
||||
showCallsign: belongsToCommandedCoalition && /*TODO !getApp().getMap().getOptions().AWACSMode || */ this.getHuman(),
|
||||
rotateToHeading: false,
|
||||
showCluster: false,
|
||||
} as ObjectIconOptions;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
import { DomEvent, DomUtil, LatLng, LatLngBounds } from "leaflet";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { AirUnit, Unit } from "./unit";
|
||||
import { AirUnit, GroundUnit, NavyUnit, Unit } from "./unit";
|
||||
import {
|
||||
areaContains,
|
||||
bearingAndDistanceToLatLng,
|
||||
@ -13,7 +13,17 @@ import {
|
||||
msToKnots,
|
||||
} from "../other/utils";
|
||||
import { CoalitionPolygon } from "../map/coalitionarea/coalitionpolygon";
|
||||
import { BLUE_COMMANDER, DELETE_CYCLE_TIME, DELETE_SLOW_THRESHOLD, DataIndexes, GAME_MASTER, IADSDensities, OlympusState, RED_COMMANDER, UnitControlSubState, alarmStates } from "../constants/constants";
|
||||
import {
|
||||
BLUE_COMMANDER,
|
||||
DELETE_CYCLE_TIME,
|
||||
DELETE_SLOW_THRESHOLD,
|
||||
DataIndexes,
|
||||
GAME_MASTER,
|
||||
IADSDensities,
|
||||
OlympusState,
|
||||
RED_COMMANDER,
|
||||
UnitControlSubState, alarmStates,
|
||||
} from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { citiesDatabase } from "./databases/citiesdatabase";
|
||||
import { TemporaryUnitMarker } from "../map/markers/temporaryunitmarker";
|
||||
@ -40,6 +50,7 @@ import { UnitDatabase } from "./databases/unitdatabase";
|
||||
import * as turf from "@turf/turf";
|
||||
import { PathMarker } from "../map/markers/pathmarker";
|
||||
import { Coalition } from "../types/types";
|
||||
import { ClusterMarker } from "../map/markers/clustermarker";
|
||||
|
||||
/** 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
|
||||
@ -354,8 +365,51 @@ export class UnitsManager {
|
||||
});
|
||||
}
|
||||
|
||||
/* Compute the base clusters */
|
||||
this.#clusters = this.computeClusters();
|
||||
/* Compute the base air unit clusters */
|
||||
this.#clusters = this.computeClusters(AirUnit);
|
||||
|
||||
/* Compute the base ground unit clusters */
|
||||
Object.values(this.#units).forEach((unit: Unit) => unit.setIsClusterLeader(true));
|
||||
if (getApp().getMap().getOptions().clusterGroundUnits) {
|
||||
/* Get a list of all existing ground unit types */
|
||||
let groundUnitTypes: string[] = [];
|
||||
Object.values(this.#units)
|
||||
.filter((unit) => unit.getAlive())
|
||||
.forEach((unit: Unit) => {
|
||||
if (unit.getCategory() === "GroundUnit" && !groundUnitTypes.includes(unit.getType())) groundUnitTypes.push(unit.getType());
|
||||
});
|
||||
|
||||
["blue", "red", "neutral"].forEach((coalition: string) => {
|
||||
groundUnitTypes.forEach((type: string) => {
|
||||
let clusters = this.computeClusters(
|
||||
GroundUnit,
|
||||
(unit: Unit) => {
|
||||
if (getApp().getMap().getOptions().hideGroupMembers) return unit.getType() === type && unit.getIsLeader();
|
||||
else return unit.getType() === type;
|
||||
},
|
||||
2,
|
||||
coalition as Coalition,
|
||||
5
|
||||
);
|
||||
|
||||
/* Find the unit closest to the cluster center */
|
||||
Object.values(clusters).forEach((clusterUnits: Unit[]) => {
|
||||
const clusterCenter = turf.center(
|
||||
turf.featureCollection(clusterUnits.map((unit: Unit) => turf.point([unit.getPosition().lng, unit.getPosition().lat])))
|
||||
);
|
||||
const clusterCenterCoords = clusterCenter.geometry.coordinates;
|
||||
const clusterCenterLatLng = new LatLng(clusterCenterCoords[1], clusterCenterCoords[0]);
|
||||
|
||||
const closestUnit = clusterUnits.reduce((prev, current) => {
|
||||
return prev.getPosition().distanceTo(clusterCenterLatLng) < current.getPosition().distanceTo(clusterCenterLatLng) ? prev : current;
|
||||
});
|
||||
|
||||
clusterUnits.forEach((unit: Unit) => unit.setIsClusterLeader(unit === closestUnit));
|
||||
closestUnit.setClusterUnits(clusterUnits);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (fullUpdate) UnitsRefreshedEvent.dispatch(Object.values(this.#units));
|
||||
else UnitsUpdatedEvent.dispatch(updatedUnits);
|
||||
@ -1294,7 +1348,9 @@ export class UnitsManager {
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode === BLUE_COMMANDER) coalition = "blue";
|
||||
else if (getApp().getMissionManager().getCommandModeOptions().commandMode === RED_COMMANDER) coalition = "red";
|
||||
|
||||
getApp().getServerManager().cloneUnits(unitsData, true, 0 /* No spawn points, we delete the original units */, coalition as Coalition);
|
||||
getApp()
|
||||
.getServerManager()
|
||||
.cloneUnits(unitsData, true, 0 /* No spawn points, we delete the original units */, coalition as Coalition);
|
||||
this.#showActionMessage(units, `created a group`);
|
||||
} else {
|
||||
getApp().addInfoMessage(`Groups can only be created from units of the same category`);
|
||||
@ -1507,13 +1563,19 @@ export class UnitsManager {
|
||||
|
||||
getApp()
|
||||
.getServerManager()
|
||||
.cloneUnits(units, false, getApp().getMissionManager().getCommandModeOptions().commandMode === GAME_MASTER? 0: spawnPoints, coalition as Coalition, (res: any) => {
|
||||
if (res !== undefined) {
|
||||
markers.forEach((marker: TemporaryUnitMarker) => {
|
||||
marker.setCommandHash(res);
|
||||
});
|
||||
.cloneUnits(
|
||||
units,
|
||||
false,
|
||||
getApp().getMissionManager().getCommandModeOptions().commandMode === GAME_MASTER ? 0 : spawnPoints,
|
||||
coalition as Coalition,
|
||||
(res: any) => {
|
||||
if (res !== undefined) {
|
||||
markers.forEach((marker: TemporaryUnitMarker) => {
|
||||
marker.setCommandHash(res);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
getApp().addInfoMessage(`${this.#copiedUnits.length} units pasted`);
|
||||
} else {
|
||||
@ -1691,36 +1753,48 @@ export class UnitsManager {
|
||||
getApp().addInfoMessage("Aircrafts can be air spawned during the SETUP phase only");
|
||||
return false;
|
||||
}
|
||||
spawnPoints = getApp().getMissionManager().getCommandModeOptions().commandMode === GAME_MASTER? 0: units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return points + this.getDatabase().getSpawnPointsByName(unit.unitType);
|
||||
}, 0);
|
||||
spawnPoints =
|
||||
getApp().getMissionManager().getCommandModeOptions().commandMode === GAME_MASTER
|
||||
? 0
|
||||
: units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return points + this.getDatabase().getSpawnPointsByName(unit.unitType);
|
||||
}, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints, callback);
|
||||
} else if (category === "helicopter") {
|
||||
if (airbase == "" && spawnsRestricted) {
|
||||
getApp().addInfoMessage("Helicopters can be air spawned during the SETUP phase only");
|
||||
return false;
|
||||
}
|
||||
spawnPoints = getApp().getMissionManager().getCommandModeOptions().commandMode === GAME_MASTER? 0: units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return points + this.getDatabase().getSpawnPointsByName(unit.unitType);
|
||||
}, 0);
|
||||
spawnPoints =
|
||||
getApp().getMissionManager().getCommandModeOptions().commandMode === GAME_MASTER
|
||||
? 0
|
||||
: units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return points + this.getDatabase().getSpawnPointsByName(unit.unitType);
|
||||
}, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints, callback);
|
||||
} else if (category === "groundunit") {
|
||||
if (spawnsRestricted) {
|
||||
getApp().addInfoMessage("Ground units can be spawned during the SETUP phase only");
|
||||
return false;
|
||||
}
|
||||
spawnPoints = getApp().getMissionManager().getCommandModeOptions().commandMode === GAME_MASTER? 0: units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return points + this.getDatabase().getSpawnPointsByName(unit.unitType);
|
||||
}, 0);
|
||||
spawnPoints =
|
||||
getApp().getMissionManager().getCommandModeOptions().commandMode === GAME_MASTER
|
||||
? 0
|
||||
: units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return points + this.getDatabase().getSpawnPointsByName(unit.unitType);
|
||||
}, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnGroundUnits(units, coalition, country, immediate, spawnPoints, callback);
|
||||
} else if (category === "navyunit") {
|
||||
if (spawnsRestricted) {
|
||||
getApp().addInfoMessage("Navy units can be spawned during the SETUP phase only");
|
||||
return false;
|
||||
}
|
||||
spawnPoints = getApp().getMissionManager().getCommandModeOptions().commandMode === GAME_MASTER? 0: units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return points + this.getDatabase().getSpawnPointsByName(unit.unitType);
|
||||
}, 0);
|
||||
spawnPoints =
|
||||
getApp().getMissionManager().getCommandModeOptions().commandMode === GAME_MASTER
|
||||
? 0
|
||||
: units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return points + this.getDatabase().getSpawnPointsByName(unit.unitType);
|
||||
}, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback);
|
||||
}
|
||||
|
||||
@ -1751,20 +1825,32 @@ export class UnitsManager {
|
||||
return this.#AWACSReference;
|
||||
}
|
||||
|
||||
computeClusters(filter: (unit: Unit) => boolean = (unit) => true, distance: number = 5 /* km */) {
|
||||
computeClusters(
|
||||
unitType: typeof AirUnit | typeof GroundUnit | typeof NavyUnit,
|
||||
filter: (unit: Unit) => boolean = (unit) => true,
|
||||
distance: number = 5 /* km */,
|
||||
coalition?: Coalition,
|
||||
minPoints?: number
|
||||
) {
|
||||
let units = Object.values(this.#units)
|
||||
.filter((unit) => unit.getAlive() && unit instanceof AirUnit)
|
||||
.filter((unit) => unit.getAlive() && unit instanceof unitType)
|
||||
.filter(filter);
|
||||
|
||||
if (coalition !== undefined) {
|
||||
units = units.filter((unit) => unit.getCoalition() === coalition);
|
||||
}
|
||||
|
||||
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 });
|
||||
var clustered = turf.clustersDbscan(geojson, distance, { minPoints: 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]);
|
||||
if (feature.properties.cluster !== undefined) {
|
||||
if (clusters[feature.properties.cluster] === undefined) clusters[feature.properties.cluster] = [] as Unit[];
|
||||
clusters[feature.properties.cluster].push(units[idx]);
|
||||
}
|
||||
});
|
||||
|
||||
return clusters;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
call npm run tsc
|
||||
|
||||
echo D|xcopy /Y /S /E .\public ..\..\build\frontend\public
|
||||
echo D|xcopy /Y /S /E .\databases ..\..\build\frontend\public\databases
|
||||
echo D|xcopy /Y /S /E .\views ..\..\build\frontend\cert
|
||||
echo D|xcopy /Y /S /E .\build ..\..\build\frontend\build
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user