Readded context menus

This commit is contained in:
Davide Passoni
2024-11-08 17:04:01 +01:00
parent df939f1ac3
commit 644404c4e6
23 changed files with 770 additions and 640 deletions

View File

@@ -308,6 +308,7 @@ export const MAP_OPTIONS_DEFAULTS = {
fillSelectedRing: false, fillSelectedRing: false,
showMinimap: false, showMinimap: false,
protectDCSUnits: true, protectDCSUnits: true,
keepRelativePositions: true
} as MapOptions; } as MapOptions;
export const MAP_HIDDEN_TYPES_DEFAULTS = { export const MAP_HIDDEN_TYPES_DEFAULTS = {
@@ -399,8 +400,7 @@ export enum AudioMessageType {
settings, settings,
} }
export const CONTEXT_ACTION_COLORS = [null, "white", "green", "purple", "blue", "red"]; export enum ContextActionType {
export enum ContextActionColors {
NO_COLOR, NO_COLOR,
MOVE, MOVE,
OTHER, OTHER,
@@ -408,3 +408,6 @@ export enum ContextActionColors {
ENGAGE, ENGAGE,
DELETE, DELETE,
} }
export const CONTEXT_ACTION_COLORS = [null, "white", "green", "purple", "blue", "red"];

View File

@@ -80,15 +80,17 @@ export class ServerStatusUpdatedEvent {
} }
} }
export class UnitDatabaseLoadedEvent { export class UnitDatabaseLoadedEvent extends BaseOlympusEvent {}
static on(callback: () => void) {
export class InfoPopupEvent {
static on(callback: (messages: string[]) => void) {
document.addEventListener(this.name, (ev: CustomEventInit) => { document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(); callback(ev.detail.messages);
}); });
} }
static dispatch() { static dispatch(messages: string[]) {
document.dispatchEvent(new CustomEvent(this.name)); document.dispatchEvent(new CustomEvent(this.name, {detail: {messages}}));
console.log(`Event ${this.name} dispatched`); console.log(`Event ${this.name} dispatched`);
} }
} }
@@ -223,6 +225,58 @@ export class SelectedUnitsChangedEvent {
} }
} }
export class UnitExplosionRequestEvent {
static on(callback: (units: Unit[]) => void) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(ev.detail.units);
});
}
static dispatch(units: Unit[]) {
document.dispatchEvent(new CustomEvent(this.name, {detail: {units}}));
console.log(`Event ${this.name} dispatched`);
}
}
export class FormationCreationRequestEvent {
static on(callback: (leader: Unit, wingmen: Unit[]) => void) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(ev.detail.leader, ev.detail.wingmen);
});
}
static dispatch(leader: Unit, wingmen: Unit[]) {
document.dispatchEvent(new CustomEvent(this.name, {detail: {leader, wingmen}}));
console.log(`Event ${this.name} dispatched`);
}
}
export class MapContextMenuRequestEvent {
static on(callback: (latlng: L.LatLng) => void) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(ev.detail.latlng);
});
}
static dispatch(latlng: L.LatLng) {
document.dispatchEvent(new CustomEvent(this.name, {detail: {latlng}}));
console.log(`Event ${this.name} dispatched`);
}
}
export class UnitContextMenuRequestEvent {
static on(callback: (unit: Unit) => void) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(ev.detail.unit);
});
}
static dispatch(unit: Unit) {
document.dispatchEvent(new CustomEvent(this.name, {detail: {unit}}));
console.log(`Event ${this.name} dispatched`);
}
}
/************** Command mode events ***************/ /************** Command mode events ***************/
export class CommandModeOptionsChangedEvent { export class CommandModeOptionsChangedEvent {
static on(callback: (options: CommandModeOptions) => void) { static on(callback: (options: CommandModeOptions) => void) {

View File

@@ -45,8 +45,11 @@ import {
ContextActionChangedEvent, ContextActionChangedEvent,
ContextActionSetChangedEvent, ContextActionSetChangedEvent,
HiddenTypesChangedEvent, HiddenTypesChangedEvent,
MapContextMenuRequestEvent,
MapOptionsChangedEvent, MapOptionsChangedEvent,
MapSourceChangedEvent, MapSourceChangedEvent,
SelectionClearedEvent,
UnitSelectedEvent,
UnitUpdatedEvent, UnitUpdatedEvent,
} from "../events"; } from "../events";
import { ContextActionSet } from "../unit/contextactionset"; import { ContextActionSet } from "../unit/contextactionset";
@@ -116,6 +119,10 @@ export class Map extends L.Map {
/* Coalition areas drawing */ /* Coalition areas drawing */
#coalitionAreas: (CoalitionPolygon | CoalitionCircle)[] = []; #coalitionAreas: (CoalitionPolygon | CoalitionCircle)[] = [];
/* Units movement */
#destinationPreviewMarkers: { [key: number]: TemporaryUnitMarker | TargetMarker } = {};
#destinationRotation: number = 0;
/* Unit context actions */ /* Unit context actions */
#contextActionSet: null | ContextActionSet = null; #contextActionSet: null | ContextActionSet = null;
#contextAction: null | ContextAction = null; #contextAction: null | ContextAction = null;
@@ -190,6 +197,7 @@ export class Map extends L.Map {
/* Custom touch events for touchscreen support */ /* Custom touch events for touchscreen support */
L.DomEvent.on(this.getContainer(), "touchstart", this.#onMouseDown, this); L.DomEvent.on(this.getContainer(), "touchstart", this.#onMouseDown, this);
L.DomEvent.on(this.getContainer(), "touchend", this.#onMouseUp, this); L.DomEvent.on(this.getContainer(), "touchend", this.#onMouseUp, this);
L.DomEvent.on(this.getContainer(), "wheel", this.#onWheel, this);
/* Event listeners */ /* Event listeners */
AppStateChangedEvent.on((state, subState) => this.#onStateChanged(state, subState)); AppStateChangedEvent.on((state, subState) => this.#onStateChanged(state, subState));
@@ -204,6 +212,7 @@ export class Map extends L.Map {
UnitUpdatedEvent.on((unit) => { UnitUpdatedEvent.on((unit) => {
if (this.#centeredUnit != null && unit == this.#centeredUnit) this.#panToUnit(this.#centeredUnit); if (this.#centeredUnit != null && unit == this.#centeredUnit) this.#panToUnit(this.#centeredUnit);
if (unit.getSelected()) this.#moveDestinationPreviewMarkers();
}); });
MapOptionsChangedEvent.on((options) => { MapOptionsChangedEvent.on((options) => {
@@ -255,6 +264,11 @@ export class Map extends L.Map {
} }
}); });
UnitSelectedEvent.on((unit) => this.#updateDestinationPreviewMarkers());
SelectionClearedEvent.on(() => this.#updateDestinationPreviewMarkers());
ContextActionChangedEvent.on((contextAction) => this.#updateDestinationPreviewMarkers());
MapOptionsChangedEvent.on((mapOptions) => this.#moveDestinationPreviewMarkers());
/* Pan interval */ /* Pan interval */
this.#panInterval = window.setInterval(() => { this.#panInterval = window.setInterval(() => {
if (this.#panUp || this.#panDown || this.#panRight || this.#panLeft) if (this.#panUp || this.#panDown || this.#panRight || this.#panLeft)
@@ -724,6 +738,18 @@ export class Map extends L.Map {
return 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) { setOption(key, value) {
this.#options[key] = value; this.#options[key] = value;
MapOptionsChangedEvent.dispatch(this.#options); MapOptionsChangedEvent.dispatch(this.#options);
@@ -781,8 +807,8 @@ export class Map extends L.Map {
//} //}
} }
executeContextAction(targetUnit: Unit | null, targetPosition: L.LatLng | null) { executeContextAction(targetUnit: Unit | null, targetPosition: L.LatLng | null, originalEvent?: MouseEvent) {
this.#contextAction?.executeCallback(targetUnit, targetPosition); this.#contextAction?.executeCallback(targetUnit, targetPosition, originalEvent);
} }
getContextActionSet() { getContextActionSet() {
@@ -793,8 +819,8 @@ export class Map extends L.Map {
return this.#contextAction; return this.#contextAction;
} }
executeDefaultContextAction(targetUnit: Unit | null, targetPosition: L.LatLng | null) { executeDefaultContextAction(targetUnit: Unit | null, targetPosition: L.LatLng | null, originalEvent?: MouseEvent) {
this.#contextActionSet?.getDefaultContextAction()?.executeCallback(targetUnit, targetPosition); this.#contextActionSet?.getDefaultContextAction()?.executeCallback(targetUnit, targetPosition, originalEvent);
} }
preventClicks() { preventClicks() {
@@ -831,10 +857,9 @@ export class Map extends L.Map {
} else if (subState === SpawnSubState.SPAWN_EFFECT) { } else if (subState === SpawnSubState.SPAWN_EFFECT) {
console.log(`Effect request table:`); console.log(`Effect request table:`);
console.log(this.#effectRequestTable); console.log(this.#effectRequestTable);
if (this.#effectRequestTable?.type === 'explosion') if (this.#effectRequestTable?.type === "explosion") this.#currentEffectMarker = new ExplosionMarker(new L.LatLng(0, 0));
this.#currentEffectMarker = new ExplosionMarker(new L.LatLng(0, 0)) else if (this.#effectRequestTable?.type === "smoke")
else if (this.#effectRequestTable?.type === 'smoke') this.#currentEffectMarker = new SmokeMarker(new L.LatLng(0, 0), this.#effectRequestTable.smokeColor ?? "white");
this.#currentEffectMarker = new SmokeMarker(new L.LatLng(0, 0), this.#effectRequestTable.smokeColor ?? "white")
this.#currentEffectMarker?.addTo(this); this.#currentEffectMarker?.addTo(this);
} }
} else if (state === OlympusState.UNIT_CONTROL) { } else if (state === OlympusState.UNIT_CONTROL) {
@@ -874,6 +899,8 @@ export class Map extends L.Map {
this.#isMouseDown = false; this.#isMouseDown = false;
window.clearTimeout(this.#longPressTimer); window.clearTimeout(this.#longPressTimer);
this.scrollWheelZoom.enable();
this.#isMouseOnCooldown = true; this.#isMouseOnCooldown = true;
this.#mouseCooldownTimer = window.setTimeout(() => { this.#mouseCooldownTimer = window.setTimeout(() => {
this.#isMouseOnCooldown = false; this.#isMouseOnCooldown = false;
@@ -887,6 +914,8 @@ export class Map extends L.Map {
return; return;
} }
this.scrollWheelZoom.disable();
this.#shortPressTimer = window.setTimeout(() => { this.#shortPressTimer = window.setTimeout(() => {
/* If the mouse is no longer being pressed, execute the short press action */ /* If the mouse is no longer being pressed, execute the short press action */
if (!this.#isMouseDown) this.#onShortPress(e); if (!this.#isMouseDown) this.#onShortPress(e);
@@ -898,6 +927,11 @@ export class Map extends L.Map {
}, 350); }, 350);
} }
#onWheel(e: any) {
//this.#destinationRotation += e.deltaY / 25;
//this.#moveDestinationPreviewMarkers();
}
#onDoubleClick(e: any) { #onDoubleClick(e: any) {
console.log(`Double click at ${e.latlng}`); console.log(`Double click at ${e.latlng}`);
@@ -947,14 +981,12 @@ export class Map extends L.Map {
else if (this.#effectRequestTable.explosionType === "White phosphorous") else if (this.#effectRequestTable.explosionType === "White phosphorous")
getApp().getServerManager().spawnExplosion(50, "phosphorous", pressLocation); getApp().getServerManager().spawnExplosion(50, "phosphorous", pressLocation);
const explosionMarker = new ExplosionMarker(pressLocation, 5); this.addExplosionMarker(pressLocation);
explosionMarker.addTo(this);
} else if (this.#effectRequestTable.type === "smoke") { } else if (this.#effectRequestTable.type === "smoke") {
getApp() getApp()
.getServerManager() .getServerManager()
.spawnSmoke(this.#effectRequestTable.smokeColor ?? "white", pressLocation); .spawnSmoke(this.#effectRequestTable.smokeColor ?? "white", pressLocation);
const smokeMarker = new SmokeMarker(pressLocation, this.#effectRequestTable.smokeColor ?? "white"); this.addSmokeMarker(pressLocation, this.#effectRequestTable.smokeColor ?? "white");
smokeMarker.addTo(this);
} }
} }
} }
@@ -982,10 +1014,10 @@ export class Map extends L.Map {
} }
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) { } else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
if (e.type === "touchstart" || e.originalEvent.buttons === 1) { if (e.type === "touchstart" || e.originalEvent.buttons === 1) {
if (this.#contextAction !== null) this.executeContextAction(null, pressLocation); if (this.#contextAction !== null) this.executeContextAction(null, pressLocation, e.originalEvent);
else getApp().setState(OlympusState.IDLE); else getApp().setState(OlympusState.IDLE);
} else if (e.originalEvent.buttons === 2) { } else if (e.originalEvent.buttons === 2) {
this.executeDefaultContextAction(null, pressLocation); this.executeDefaultContextAction(null, pressLocation, e.originalEvent);
} }
} else if (getApp().getState() === OlympusState.JTAC) { } else if (getApp().getState() === OlympusState.JTAC) {
if (getApp().getSubState() === JTACSubState.SELECT_TARGET) { if (getApp().getSubState() === JTACSubState.SELECT_TARGET) {
@@ -1055,6 +1087,7 @@ export class Map extends L.Map {
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) { } else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
if (e.originalEvent.button === 2) { if (e.originalEvent.button === 2) {
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.MAP_CONTEXT_MENU); getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.MAP_CONTEXT_MENU);
MapContextMenuRequestEvent.dispatch(pressLocation);
} else { } else {
if (e.type === "touchstart") document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e })); if (e.type === "touchstart") document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e }));
else document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e.originalEvent })); else document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e.originalEvent }));
@@ -1070,10 +1103,10 @@ export class Map extends L.Map {
this.#lastMousePosition.y = e.originalEvent.y; this.#lastMousePosition.y = e.originalEvent.y;
this.#lastMouseCoordinates = e.latlng; this.#lastMouseCoordinates = e.latlng;
if (this.#currentSpawnMarker) if (this.#currentSpawnMarker) this.#currentSpawnMarker.setLatLng(e.latlng);
this.#currentSpawnMarker.setLatLng(e.latlng); if (this.#currentEffectMarker) this.#currentEffectMarker.setLatLng(e.latlng);
if (this.#currentEffectMarker)
this.#currentEffectMarker.setLatLng(e.latlng); this.#moveDestinationPreviewMarkers();
} }
#onMapMove(e: any) { #onMapMove(e: any) {
@@ -1190,4 +1223,37 @@ export class Map extends L.Map {
} else this.#IPToTargetLine.setLatLngs([this.#targetPoint.getLatLng(), this.#IPPoint.getLatLng()]); } 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 (["move", "path", "land-at-point"].includes(this.#contextAction?.getId() ?? "")) {
this.#destinationPreviewMarkers[unit.ID] = new TemporaryUnitMarker(new L.LatLng(0, 0), unit.getName(), unit.getCoalition());
} else if (this.#contextAction?.getTarget() === "position" && this.#contextAction?.getId() !== "land") {
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);
});
}
}
} }

View File

@@ -7,7 +7,7 @@ import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionDa
import { Coalition } from "../types/types"; import { Coalition } from "../types/types";
import { Carrier } from "./carrier"; import { Carrier } from "./carrier";
import { NavyUnit } from "../unit/unit"; import { NavyUnit } from "../unit/unit";
import { CommandModeOptionsChangedEvent } from "../events"; import { CommandModeOptionsChangedEvent, InfoPopupEvent } from "../events";
/** The MissionManager */ /** The MissionManager */
export class MissionManager { export class MissionManager {
@@ -94,7 +94,7 @@ export class MissionManager {
if (data.mission.theatre != this.#theatre) { if (data.mission.theatre != this.#theatre) {
this.#theatre = data.mission.theatre; this.#theatre = data.mission.theatre;
getApp().getMap().setTheatre(this.#theatre); getApp().getMap().setTheatre(this.#theatre);
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Map set to " + this.#theatre); getApp().addInfoMessage("Map set to " + this.#theatre);
} }
/* Set the date and time data */ /* Set the date and time data */

View File

@@ -21,7 +21,7 @@ import { ServerManager } from "./server/servermanager";
import { AudioManager } from "./audio/audiomanager"; import { AudioManager } from "./audio/audiomanager";
import { NO_SUBSTATE, OlympusState, OlympusSubState } from "./constants/constants"; import { NO_SUBSTATE, OlympusState, OlympusSubState } from "./constants/constants";
import { AppStateChangedEvent, ConfigLoadedEvent, SelectedUnitsChangedEvent } from "./events"; import { AppStateChangedEvent, ConfigLoadedEvent, InfoPopupEvent, SelectedUnitsChangedEvent } from "./events";
import { OlympusConfig } from "./interfaces"; import { OlympusConfig } from "./interfaces";
export var VERSION = "{{OLYMPUS_VERSION_NUMBER}}"; export var VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
@@ -33,6 +33,7 @@ export class OlympusApp {
#config: OlympusConfig | null = null; #config: OlympusConfig | null = null;
#state: OlympusState = OlympusState.NOT_INITIALIZED; #state: OlympusState = OlympusState.NOT_INITIALIZED;
#subState: OlympusSubState = NO_SUBSTATE; #subState: OlympusSubState = NO_SUBSTATE;
#infoMessages: string[] = [];
/* Main leaflet map, extended by custom methods */ /* Main leaflet map, extended by custom methods */
#map: Map | null = null; #map: Map | null = null;
@@ -157,4 +158,13 @@ export class OlympusApp {
getSubState() { getSubState() {
return this.#subState; return this.#subState;
} }
addInfoMessage(message: string) {
this.#infoMessages.push(message);
InfoPopupEvent.dispatch(this.#infoMessages);
setTimeout(() => {
this.#infoMessages.shift();
InfoPopupEvent.dispatch(this.#infoMessages);
}, 5000)
}
} }

View File

@@ -227,63 +227,6 @@ export function polygonArea(polygon: Polygon) {
return turf.area(poly); return turf.area(poly);
} }
export function randomUnitBlueprint(
unitDatabase: UnitDatabase,
options: {
type?: string;
role?: string;
ranges?: string[];
eras?: string[];
coalition?: string;
}
) {
/* Start from all the unit blueprints in the database */
var unitBlueprints = unitDatabase.getBlueprints();
/* If a specific type or role is provided, use only the blueprints of that type or role */
if (options.type && options.role) {
console.error("Can't create random unit if both type and role are provided. Either create by type or by role.");
return null;
}
if (options.type) {
unitBlueprints = unitDatabase.getByType(options.type);
} else if (options.role) {
unitBlueprints = unitDatabase.getByType(options.role);
}
/* Keep only the units that have a range included in the requested values */
if (options.ranges) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
var rangeType = "";
var range = unitBlueprint.acquisitionRange;
if (range !== undefined) {
if (range >= 0 && range < 10000) rangeType = "Short range";
else if (range >= 10000 && range < 100000) rangeType = "Medium range";
else if (range >= 100000 && range < 999999) rangeType = "Long range";
}
return options.ranges?.includes(rangeType);
});
}
/* Keep only the units that have an era included in the requested values */
if (options.eras) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
return unitBlueprint.era ? options.eras?.includes(unitBlueprint.era) : true;
});
}
/* Keep only the units that have the correct coalition, if selected */
if (options.coalition) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
return unitBlueprint.coalition && unitBlueprint.coalition !== "" ? options.coalition === unitBlueprint.coalition : true;
});
}
var index = Math.floor(Math.random() * unitBlueprints.length);
return unitBlueprints[index];
}
export function enumToState(state: number) { export function enumToState(state: number) {
if (state < states.length) return states[state]; if (state < states.length) return states[state];
else return states[0]; else return states[0];
@@ -347,9 +290,8 @@ export function convertDateAndTimeToDate(dateAndTime: DateAndTime) {
export function getGroundElevation(latlng: LatLng, callback: CallableFunction) { export function getGroundElevation(latlng: LatLng, callback: CallableFunction) {
/* Get the ground elevation from the server endpoint */ /* Get the ground elevation from the server endpoint */
/* TODO */
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open("GET", `api/elevation/${latlng.lat}/${latlng.lng}`, true); xhr.open("GET", window.location.href.split("?")[0].replace("vite/", "") + `api/elevation/${latlng.lat}/${latlng.lng}`, true);
xhr.timeout = 500; // ms xhr.timeout = 500; // ms
xhr.responseType = "json"; xhr.responseType = "json";
xhr.onload = () => { xhr.onload = () => {

View File

@@ -14,7 +14,7 @@ import {
reactionsToThreat, reactionsToThreat,
} from "../constants/constants"; } from "../constants/constants";
import { AirbasesData, BullseyesData, CommandModeOptions, GeneralSettings, MissionData, Radio, ServerRequestOptions, ServerStatus, TACAN } from "../interfaces"; import { AirbasesData, BullseyesData, CommandModeOptions, GeneralSettings, MissionData, Radio, ServerRequestOptions, ServerStatus, TACAN } from "../interfaces";
import { ServerStatusUpdatedEvent } from "../events"; import { InfoPopupEvent, ServerStatusUpdatedEvent } from "../events";
export class ServerManager { export class ServerManager {
#connected: boolean = false; #connected: boolean = false;
@@ -350,12 +350,6 @@ export class ServerManager {
this.PUT(data, callback); this.PUT(data, callback);
} }
showFormationMenu(ID: number, isLeader: boolean, wingmenIDs: number[], callback: CallableFunction = () => {}) {
var command = { ID: ID, wingmenIDs: wingmenIDs, isLeader: isLeader };
var data = { setLeader: command };
this.PUT(data, callback);
}
setROE(ID: number, ROE: string, callback: CallableFunction = () => {}) { setROE(ID: number, ROE: string, callback: CallableFunction = () => {}) {
var command = { ID: ID, ROE: ROEs.indexOf(ROE) }; var command = { ID: ID, ROE: ROEs.indexOf(ROE) };
var data = { setROE: command }; var data = { setROE: command };
@@ -660,7 +654,7 @@ export class ServerManager {
setConnected(newConnected: boolean) { setConnected(newConnected: boolean) {
if (this.#connected != newConnected) { if (this.#connected != newConnected) {
//newConnected ? (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Connected to DCS Olympus server") : (getApp().getPopupsManager().get("infoPopup") as Popup).setText("Disconnected from DCS Olympus server"); newConnected ? getApp().addInfoMessage("Connected to DCS Olympus server") : getApp().addInfoMessage("Disconnected from DCS Olympus server");
if (newConnected) { if (newConnected) {
document.getElementById("splash-screen")?.classList.add("hide"); document.getElementById("splash-screen")?.classList.add("hide");
document.getElementById("gray-out")?.classList.add("hide"); document.getElementById("gray-out")?.classList.add("hide");
@@ -676,7 +670,7 @@ export class ServerManager {
setPaused(newPaused: boolean) { setPaused(newPaused: boolean) {
this.#paused = newPaused; this.#paused = newPaused;
//this.#paused ? (getApp().getPopupsManager().get("infoPopup") as Popup).setText("View paused") : (getApp().getPopupsManager().get("infoPopup") as Popup).setText("View unpaused"); this.#paused ? getApp().addInfoMessage("View paused") : getApp().addInfoMessage("View unpaused");
} }
getPaused() { getPaused() {

View File

@@ -89,6 +89,15 @@ export class ShortcutManager {
ctrlKey: false, ctrlKey: false,
shiftKey: false, shiftKey: false,
}) })
.addKeyboardShortcut("toggleRelativePositions", {
altKey: false,
callback: () => {
getApp().getMap().setOption("keepRelativePositions", !getApp().getMap().getOptions().keepRelativePositions);
},
code: "KeyP",
ctrlKey: false,
shiftKey: false,
})
.addKeyboardShortcut("increaseCameraZoom", { .addKeyboardShortcut("increaseCameraZoom", {
altKey: true, altKey: true,
callback: () => { callback: () => {

View File

@@ -21,6 +21,7 @@ export type MapOptions = {
fillSelectedRing: boolean; fillSelectedRing: boolean;
showMinimap: boolean; showMinimap: boolean;
protectDCSUnits: boolean; protectDCSUnits: boolean;
keepRelativePositions: boolean;
}; };
export type MapHiddenTypes = { export type MapHiddenTypes = {

View File

@@ -6,7 +6,7 @@ import { OlTooltip } from "./oltooltip";
export function OlStateButton(props: { export function OlStateButton(props: {
className?: string; className?: string;
borderColor?: string | null; buttonColor?: string | null;
checked: boolean; checked: boolean;
icon: IconProp; icon: IconProp;
tooltip: string; tooltip: string;
@@ -21,10 +21,13 @@ export function OlStateButton(props: {
` `
h-[40px] w-[40px] flex-none rounded-md text-lg font-medium h-[40px] w-[40px] flex-none rounded-md text-lg font-medium
dark:bg-olympus-600 dark:text-gray-300 dark:hover:bg-olympus-300 dark:bg-olympus-600 dark:text-gray-300 dark:hover:bg-olympus-300
dark:data-[checked='true']:bg-blue-500
dark:data-[checked='true']:text-white
`; `;
let textColor = "white";
if (props.checked && props.buttonColor == "white") {
textColor = "#243141"
}
return ( return (
<> <>
<button <button
@@ -37,7 +40,8 @@ export function OlStateButton(props: {
type="button" type="button"
className={className} className={className}
style={{ style={{
border: props.borderColor ? "2px solid " + props.borderColor : "0px solid transparent" border: props.buttonColor ? "2px solid " + props.buttonColor : "0px solid transparent",
background: props.checked ? (props.buttonColor? props.buttonColor: "#3b82f6"): "#243141",
}} }}
onMouseEnter={() => { onMouseEnter={() => {
setHover(true); setHover(true);
@@ -47,7 +51,7 @@ export function OlStateButton(props: {
}} }}
> >
<div className="m-auto flex w-fit content-center justify-center gap-2"> <div className="m-auto flex w-fit content-center justify-center gap-2">
<FontAwesomeIcon icon={props.icon} className="m-auto" /> <FontAwesomeIcon icon={props.icon} className="m-auto" style={{color: textColor}} />
{props.children} {props.children}
</div> </div>
</button> </button>

View File

@@ -5,13 +5,21 @@ import { CONTEXT_ACTION_COLORS, NO_SUBSTATE, OlympusState, OlympusSubState, Unit
import { OlDropdownItem } from "../components/oldropdown"; import { OlDropdownItem } from "../components/oldropdown";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { LatLng } from "leaflet"; import { LatLng } from "leaflet";
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent, SelectionClearedEvent } from "../../events"; import {
AppStateChangedEvent,
ContextActionChangedEvent,
ContextActionSetChangedEvent,
MapContextMenuRequestEvent,
SelectionClearedEvent,
UnitContextMenuRequestEvent,
} from "../../events";
import { ContextActionSet } from "../../unit/contextactionset"; import { ContextActionSet } from "../../unit/contextactionset";
import { getApp } from "../../olympusapp";
export function MapContextMenu(props: {}) { export function MapContextMenu(props: {}) {
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED); const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState); const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
const [contextActionSet, setContextActionsSet] = useState(null as ContextActionSet | null); const [contextActionSet, setcontextActionSet] = useState(null as ContextActionSet | null);
const [xPosition, setXPosition] = useState(0); const [xPosition, setXPosition] = useState(0);
const [yPosition, setYPosition] = useState(0); const [yPosition, setYPosition] = useState(0);
const [latLng, setLatLng] = useState(null as null | LatLng); const [latLng, setLatLng] = useState(null as null | LatLng);
@@ -19,14 +27,24 @@ export function MapContextMenu(props: {}) {
var contentRef = useRef(null); var contentRef = useRef(null);
// TODO show at correct position
useEffect(() => { useEffect(() => {
AppStateChangedEvent.on((state, subState) => { AppStateChangedEvent.on((state, subState) => {
setAppState(state); setAppState(state);
setAppSubState(subState); setAppSubState(subState);
}); });
ContextActionSetChangedEvent.on((contextActionSet) => setContextActionsSet(contextActionSet)); ContextActionSetChangedEvent.on((contextActionSet) => setcontextActionSet(contextActionSet));
MapContextMenuRequestEvent.on((latlng) => {
setLatLng(latlng);
const containerPoint = getApp().getMap().latLngToContainerPoint(latlng);
setXPosition(getApp().getMap().getContainer().offsetLeft + containerPoint.x);
setYPosition(getApp().getMap().getContainer().offsetTop + containerPoint.y);
});
UnitContextMenuRequestEvent.on((unit) => {
setUnit(unit);
const containerPoint = getApp().getMap().latLngToContainerPoint(unit.getPosition());
setXPosition(getApp().getMap().getContainer().offsetLeft + containerPoint.x);
setYPosition(getApp().getMap().getContainer().offsetTop + containerPoint.y);
});
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -50,64 +68,63 @@ export function MapContextMenu(props: {}) {
} }
}); });
let reorderedActions: ContextAction[] = []; let reorderedActions: ContextAction[] = contextActionSet
CONTEXT_ACTION_COLORS.forEach((color) => { ? Object.values(contextActionSet.getContextActions(appSubState === UnitControlSubState.MAP_CONTEXT_MENU ? "position" : "unit")).sort(
if (contextActionSet) (a: ContextAction, b: ContextAction) => (a.getOptions().type < b.getOptions().type ? -1 : 1)
Object.values(contextActionSet.getContextActions()).forEach((contextAction: ContextAction) => { )
if (color === null && contextAction.getOptions().buttonColor === undefined) reorderedActions.push(contextAction); : [];
else if (color === contextAction.getOptions().buttonColor) reorderedActions.push(contextAction);
});
});
return ( return (
<> <>
{appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.UNIT_CONTEXT_MENU && ( {appState === OlympusState.UNIT_CONTROL &&
<> (appSubState === UnitControlSubState.MAP_CONTEXT_MENU || appSubState === UnitControlSubState.UNIT_CONTEXT_MENU) && (
<div <>
ref={contentRef}
className={`absolute flex min-w-80 gap-2 rounded-md bg-olympus-600`}
>
<div <div
ref={contentRef}
className={` className={`
flex w-full flex-col gap-2 overflow-x-auto no-scrollbar p-2 absolute flex min-w-80 gap-2 rounded-md bg-olympus-600
`} `}
> >
{contextActionSet && <div
Object.values(contextActionSet.getContextActions(latLng ? "position" : "unit")).map((contextActionIt) => { className={`
const colorString = contextActionIt.getOptions().buttonColor flex w-full flex-col gap-2 overflow-x-auto no-scrollbar p-2
? ` `}
>
{contextActionSet &&
reorderedActions.map((contextActionIt) => {
const colorString = `
border-2 border-2
border-${contextActionIt.getOptions().buttonColor}-500 border-${CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type]}-500
` `;
: "";
return (
<OlDropdownItem
className={`
flex w-full content-center gap-2 text-white
${colorString}
`}
onClick={() => {
if (contextActionIt.getOptions().executeImmediately) {
contextActionIt.executeCallback(null, null);
} else {
if (latLng !== null) {
contextActionIt.executeCallback(null, latLng);
} else if (unit !== null) {
contextActionIt.executeCallback(unit, null);
}
}
}} return (
> <OlDropdownItem
<FontAwesomeIcon className="my-auto" icon={contextActionIt.getIcon()} /> className={`
<div>{contextActionIt.getLabel()}</div> flex w-full content-center gap-2 text-white
</OlDropdownItem> ${colorString}
); `}
})} onClick={() => {
if (contextActionIt.getOptions().executeImmediately) {
contextActionIt.executeCallback(null, null);
} else {
if (appSubState === UnitControlSubState.MAP_CONTEXT_MENU ) {
contextActionIt.executeCallback(null, latLng);
} else if (unit !== null) {
contextActionIt.executeCallback(unit, null);
}
}
getApp().setState(OlympusState.UNIT_CONTROL)
}}
>
<FontAwesomeIcon className="my-auto" icon={contextActionIt.getIcon()} />
<div>{contextActionIt.getLabel()}</div>
</OlDropdownItem>
);
})}
</div>
</div> </div>
</div> </>
</> )}
)}
</> </>
); );
} }

View File

@@ -9,8 +9,9 @@ import { OlAccordion } from "../components/olaccordion";
import { OlUnitListEntry } from "../components/olunitlistentry"; import { OlUnitListEntry } from "../components/olunitlistentry";
import { olButtonsVisibilityAircraft, olButtonsVisibilityHelicopter } from "../components/olicons"; import { olButtonsVisibilityAircraft, olButtonsVisibilityHelicopter } from "../components/olicons";
import { UnitSpawnMenu } from "./unitspawnmenu"; import { UnitSpawnMenu } from "./unitspawnmenu";
import { AirbaseSelectedEvent, UnitDatabaseLoadedEvent } from "../../events"; import { AirbaseSelectedEvent, CommandModeOptionsChangedEvent, UnitDatabaseLoadedEvent } from "../../events";
import { getApp } from "../../olympusapp"; import { getApp } from "../../olympusapp";
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, GAME_MASTER, RED_COMMANDER } from "../../constants/constants";
enum CategoryAccordion { enum CategoryAccordion {
NONE, NONE,
@@ -27,6 +28,8 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
const [blueprints, setBlueprints] = useState([] as UnitBlueprint[]); const [blueprints, setBlueprints] = useState([] as UnitBlueprint[]);
const [roles, setRoles] = useState({ aircraft: [] as string[], helicopter: [] as string[] }); const [roles, setRoles] = useState({ aircraft: [] as string[], helicopter: [] as string[] });
const [openAccordion, setOpenAccordion] = useState(CategoryAccordion.NONE); const [openAccordion, setOpenAccordion] = useState(CategoryAccordion.NONE);
const [commandModeOptions, setCommandModeOptions] = useState(COMMAND_MODE_OPTIONS_DEFAULTS);
const [showCost, setShowCost] = useState(false);
useEffect(() => { useEffect(() => {
AirbaseSelectedEvent.on((airbase) => { AirbaseSelectedEvent.on((airbase) => {
@@ -45,6 +48,12 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
.getRoles((unit) => unit.category === "helicopter"), .getRoles((unit) => unit.category === "helicopter"),
}); });
}); });
CommandModeOptionsChangedEvent.on((commandModeOptions) => {
setCommandModeOptions(commandModeOptions);
setShowCost(!(commandModeOptions.commandMode === GAME_MASTER || !commandModeOptions.restrictSpawns));
setOpenAccordion(CategoryAccordion.NONE);
});
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -132,112 +141,137 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
</div> </div>
</OlAccordion> </OlAccordion>
</div> </div>
<div className="mt-5 flex gap-2 px-5 text-white bold"> {(commandModeOptions.commandMode === GAME_MASTER ||
{blueprint && ( (commandModeOptions.commandMode === BLUE_COMMANDER && airbase?.getCoalition() === "blue") ||
<FaArrowLeft (commandModeOptions.commandMode === RED_COMMANDER && airbase?.getCoalition() === "red")) && (
className={` <>
my-auto h-8 w-8 cursor-pointer rounded-md p-2 <div className="mt-5 flex gap-2 px-5 text-white bold">
dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white {blueprint && (
`} <FaArrowLeft
onClick={() => setBlueprint(null)} className={`
/> my-auto h-8 w-8 cursor-pointer rounded-md p-2
)} dark:text-gray-500 dark:hover:bg-gray-700
<span className="my-auto">Spawn units at airbase</span> dark:hover:text-white
</div> `}
{blueprint === null && ( onClick={() => setBlueprint(null)}
<div className="p-5"> />
<OlSearchBar onChange={(value) => setFilterString(value)} text={filterString} /> )}
<OlAccordion <span className="my-auto">Spawn units at airbase</span>
title={`Aircraft`} </div>
open={openAccordion == CategoryAccordion.AIRCRAFT} {blueprint === null && (
onClick={() => { <div className="p-5">
setOpenAccordion(openAccordion === CategoryAccordion.AIRCRAFT ? CategoryAccordion.NONE : CategoryAccordion.AIRCRAFT); <OlSearchBar onChange={(value) => setFilterString(value)} text={filterString} />
setSelectedRole(null); <OlAccordion
}} title={`Aircraft`}
> open={openAccordion == CategoryAccordion.AIRCRAFT}
<div className="mb-2 flex flex-wrap gap-1"> onClick={() => {
{roles.aircraft.sort().map((role) => { setOpenAccordion(openAccordion === CategoryAccordion.AIRCRAFT ? CategoryAccordion.NONE : CategoryAccordion.AIRCRAFT);
return ( setSelectedRole(null);
<div }}
key={role} >
data-selected={selectedRole === role} <div className="mb-2 flex flex-wrap gap-1">
className={` {roles.aircraft.sort().map((role) => {
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5 return (
text-xs font-bold text-olympus-50 <div
data-[selected='true']:bg-blue-500 key={role}
data-[selected='true']:text-gray-200 data-selected={selectedRole === role}
`} className={`
onClick={() => { cursor-pointer rounded-full bg-olympus-800 px-2
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role); py-0.5 text-xs font-bold text-olympus-50
}} data-[selected='true']:bg-blue-500
> data-[selected='true']:text-gray-200
{role} `}
</div> onClick={() => {
); selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
})} }}
>
{role}
</div>
);
})}
</div>
<div
className={`
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
no-scrollbar
`}
>
{filteredBlueprints
.filter((blueprint) => blueprint.category === "aircraft")
.map((blueprint) => {
return (
<OlUnitListEntry
key={blueprint.name}
icon={olButtonsVisibilityAircraft}
blueprint={blueprint}
onClick={() => setBlueprint(blueprint)}
showCost={showCost}
cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
/>
);
})}
</div>
</OlAccordion>
<OlAccordion
title={`Helicopters`}
open={openAccordion == CategoryAccordion.HELICOPTER}
onClick={() => {
setOpenAccordion(openAccordion === CategoryAccordion.HELICOPTER ? CategoryAccordion.NONE : CategoryAccordion.HELICOPTER);
setSelectedRole(null);
}}
>
<div className="mb-2 flex flex-wrap gap-1">
{roles.helicopter.sort().map((role) => {
return (
<div
key={role}
data-selected={selectedRole === role}
className={`
cursor-pointer rounded-full bg-olympus-800 px-2
py-0.5 text-xs font-bold text-olympus-50
data-[selected='true']:bg-blue-500
data-[selected='true']:text-gray-200
`}
onClick={() => {
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
}}
>
{role}
</div>
);
})}
</div>
<div
className={`
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
no-scrollbar
`}
>
{filteredBlueprints
.filter((blueprint) => blueprint.category === "helicopter")
.map((blueprint) => {
return (
<OlUnitListEntry
key={blueprint.name}
icon={olButtonsVisibilityHelicopter}
blueprint={blueprint}
onClick={() => setBlueprint(blueprint)}
showCost={showCost}
cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
/>
);
})}
</div>
</OlAccordion>
</div> </div>
<div )}
className={` <>
flex max-h-[450px] flex-col gap-1 overflow-y-scroll {!(blueprint === null) && (
no-scrollbar <UnitSpawnMenu blueprint={blueprint} spawnAtLocation={false} airbase={airbase} coalition={(airbase?.getCoalition() ?? "blue") as Coalition} />
`} )}
> </>
{filteredBlueprints </>
.filter((blueprint) => blueprint.category === "aircraft")
.map((entry) => {
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityAircraft} blueprint={entry} onClick={() => setBlueprint(entry)} />;
})}
</div>
</OlAccordion>
<OlAccordion
title={`Helicopters`}
open={openAccordion == CategoryAccordion.HELICOPTER}
onClick={() => {
setOpenAccordion(openAccordion === CategoryAccordion.HELICOPTER ? CategoryAccordion.NONE : CategoryAccordion.HELICOPTER);
setSelectedRole(null);
}}
>
<div className="mb-2 flex flex-wrap gap-1">
{roles.helicopter.sort().map((role) => {
return (
<div
key={role}
data-selected={selectedRole === role}
className={`
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
text-xs font-bold text-olympus-50
data-[selected='true']:bg-blue-500
data-[selected='true']:text-gray-200
`}
onClick={() => {
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
}}
>
{role}
</div>
);
})}
</div>
<div
className={`
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
no-scrollbar
`}
>
{filteredBlueprints
.filter((blueprint) => blueprint.category === "helicopter")
.map((entry) => {
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityHelicopter} blueprint={entry} onClick={() => setBlueprint(entry)} />;
})}
</div>
</OlAccordion>
</div>
)} )}
<>
{!(blueprint === null) && (
<UnitSpawnMenu blueprint={blueprint} spawnAtLocation={false} airbase={airbase} coalition={(airbase?.getCoalition() ?? "blue") as Coalition} />
)}
</>
</div> </div>
</Menu> </Menu>
); );

View File

@@ -4,19 +4,21 @@ import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
import { useDrag } from "../libs/useDrag"; import { useDrag } from "../libs/useDrag";
import { Unit } from "../../unit/unit"; import { Unit } from "../../unit/unit";
import { OlRangeSlider } from "../components/olrangeslider"; import { OlRangeSlider } from "../components/olrangeslider";
import { FormationCreationRequestEvent } from "../../events";
export function FormationMenu(props: { export function FormationMenu(props: {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
leader: Unit | null;
wingmen: Unit[] | null;
children?: JSX.Element | JSX.Element[]; children?: JSX.Element | JSX.Element[];
}) { }) {
const [leader, setLeader] = useState(null as Unit | null)
const [wingmen, setWingmen] = useState(null as Unit[] | null)
/* The useDrag custom hook used to handle the dragging of the units requires that the number of hooks remains unchanged. /* The useDrag custom hook used to handle the dragging of the units requires that the number of hooks remains unchanged.
The units array is therefore initialized to 128 units maximum. */ The units array is therefore initialized to 128 units maximum. */
let units = Array(128).fill(null) as (Unit | null)[]; let units = Array(128).fill(null) as (Unit | null)[];
units[0] = props.leader; units[0] = leader;
props.wingmen?.forEach((unit, idx) => { wingmen?.forEach((unit, idx) => {
if (idx < units.length) units[idx + 1] = unit; if (idx < units.length) units[idx + 1] = unit;
}); });
@@ -53,6 +55,13 @@ export function FormationMenu(props: {
}); });
}); });
useEffect(() => {
FormationCreationRequestEvent.on((leader, wingmen) => {
setLeader(leader);
setWingmen(wingmen);
})
})
/* When the formation type is changed, reset the position to the center and the position of the silhouettes depending on the aircraft */ /* When the formation type is changed, reset the position to the center and the position of the silhouettes depending on the aircraft */
useEffect(() => { useEffect(() => {
if (scrollRef.current && containerRef.current) { if (scrollRef.current && containerRef.current) {

View File

@@ -1,132 +1,52 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useState } from "react";
import { ContextActionSet } from "../../unit/contextactionset"; import { AppStateChangedEvent, ContextActionChangedEvent, InfoPopupEvent } from "../../events";
import { OlStateButton } from "../components/olstatebutton";
import { getApp } from "../../olympusapp";
import { ContextAction } from "../../unit/contextaction";
import { CONTEXT_ACTION_COLORS } from "../../constants/constants";
import { FaInfoCircle } from "react-icons/fa";
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
import { OlympusState } from "../../constants/constants"; import { OlympusState } from "../../constants/constants";
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent } from "../../events"; import { ContextAction } from "../../unit/contextaction";
export function InfoBar(props: {}) { export function InfoBar(props: {}) {
const [messages, setMessages] = useState([] as string[]);
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED); const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
const [contextActionSet, setContextActionsSet] = useState(null as ContextActionSet | null);
const [contextAction, setContextAction] = useState(null as ContextAction | null); const [contextAction, setContextAction] = useState(null as ContextAction | null);
const [scrolledLeft, setScrolledLeft] = useState(true);
const [scrolledRight, setScrolledRight] = useState(false);
/* Initialize the "scroll" position of the element */
var scrollRef = useRef(null);
useEffect(() => {
if (scrollRef.current) onScroll(scrollRef.current);
});
useEffect(() => { useEffect(() => {
InfoPopupEvent.on((messages) => setMessages([...messages]));
AppStateChangedEvent.on((state, subState) => setAppState(state)); AppStateChangedEvent.on((state, subState) => setAppState(state));
ContextActionSetChangedEvent.on((contextActionSet) => setContextActionsSet(contextActionSet));
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction)); ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
}, []); }, []);
function onScroll(el) { let topString = "";
const sl = el.scrollLeft; if (appState === OlympusState.UNIT_CONTROL) {
const sr = el.scrollWidth - el.scrollLeft - el.clientWidth; if (contextAction === null) {
topString = "top-32";
sl < 1 && !scrolledLeft && setScrolledLeft(true); } else {
sl > 1 && scrolledLeft && setScrolledLeft(false); topString = "top-48";
}
sr < 1 && !scrolledRight && setScrolledRight(true); } else {
sr > 1 && scrolledRight && setScrolledRight(false); topString = "top-16";
} }
let reorderedActions: ContextAction[] = [];
CONTEXT_ACTION_COLORS.forEach((color) => {
if (contextActionSet) {
Object.values(contextActionSet.getContextActions()).forEach((contextAction: ContextAction) => {
if (color === null && contextAction.getOptions().buttonColor === undefined) reorderedActions.push(contextAction);
else if (color === contextAction.getOptions().buttonColor) reorderedActions.push(contextAction);
});
}
});
return ( return (
<> <div
{appState === OlympusState.UNIT_CONTROL && contextActionSet && Object.keys(contextActionSet.getContextActions()).length > 0 && ( className={`
<> absolute left-[50%]
${topString}
flex w-[400px] max-w-[80%] translate-x-[calc(-50%+2rem)]
`}
>
{messages.map((message, idx) => {
return (
<div <div
className={` className={`
absolute left-[50%] top-16 flex max-w-[80%] absolute left-0 w-full gap-2 rounded-md bg-olympus-800/90 px-4
translate-x-[calc(-50%+2rem)] gap-2 rounded-md bg-gray-200 py-2 text-center text-sm text-white backdrop-blur-lg
dark:bg-olympus-900 backdrop-grayscale
`} `}
style={{ top: `${idx * 20}px` }}
> >
{!scrolledLeft && ( {message}
<FaChevronLeft
className={`
absolute left-0 h-full w-6 rounded-lg px-2 py-3.5
text-gray-200
dark:bg-olympus-900
`}
/>
)}
<div className="flex gap-2 overflow-x-auto no-scrollbar p-2" onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
{reorderedActions.map((contextActionIt: ContextAction) => {
return (
<OlStateButton
key={contextActionIt.getId()}
checked={contextActionIt === contextAction}
icon={contextActionIt.getIcon()}
tooltip={contextActionIt.getLabel()}
borderColor={contextActionIt.getOptions().buttonColor}
onClick={() => {
if (contextActionIt.getOptions().executeImmediately) {
contextActionIt.executeCallback(null, null);
} else {
contextActionIt !== contextAction ? getApp().getMap().setContextAction(contextActionIt) : getApp().getMap().setContextAction(null);
}
}}
/>
);
})}
</div>
{!scrolledRight && (
<FaChevronRight
className={`
absolute right-0 h-full w-6 rounded-lg px-2 py-3.5
text-gray-200
dark:bg-olympus-900
`}
/>
)}
</div> </div>
{contextAction && ( );
<div })}
className={` </div>
absolute left-[50%] top-32 flex min-w-[300px]
translate-x-[calc(-50%+2rem)] items-center gap-2 rounded-md
bg-gray-200 p-4
dark:bg-olympus-800
`}
>
<FaInfoCircle
className={`
mr-2 hidden min-w-8 text-sm text-blue-500
md:block
`}
/>
<div
className={`
px-2
dark:text-gray-400
md:border-l-[1px] md:px-5
`}
>
{contextAction.getDescription()}
</div>
</div>
)}
</>
)}
</>
); );
} }

View File

@@ -1,12 +1,19 @@
import React from "react"; import React, { useEffect, useState } from "react";
import { Menu } from "./components/menu"; import { Menu } from "./components/menu";
import { OlCheckbox } from "../components/olcheckbox"; import { OlCheckbox } from "../components/olcheckbox";
import { OlRangeSlider } from "../components/olrangeslider"; import { OlRangeSlider } from "../components/olrangeslider";
import { OlNumberInput } from "../components/olnumberinput"; import { OlNumberInput } from "../components/olnumberinput";
import { MapOptions } from "../../types/types";
import { getApp } from "../../olympusapp"; import { getApp } from "../../olympusapp";
import { MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
import { MapOptionsChangedEvent } from "../../events";
export function OptionsMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
useEffect(() => {
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
}, []);
export function OptionsMenu(props: { open: boolean; onClose: () => void; options: MapOptions; children?: JSX.Element | JSX.Element[] }) {
return ( return (
<Menu title="User preferences" open={props.open} showBackButton={false} onClose={props.onClose}> <Menu title="User preferences" open={props.open} showBackButton={false} onClose={props.onClose}>
<div <div
@@ -22,10 +29,10 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; options
dark:hover:bg-olympus-400 dark:hover:bg-olympus-400
`} `}
onClick={() => { onClick={() => {
getApp().getMap().setOption("showUnitLabels", !props.options.showUnitLabels); getApp().getMap().setOption("showUnitLabels", !mapOptions.showUnitLabels);
}} }}
> >
<OlCheckbox checked={props.options.showUnitLabels} onChange={() => {}}></OlCheckbox> <OlCheckbox checked={mapOptions.showUnitLabels} onChange={() => {}}></OlCheckbox>
<span>Show Unit Labels</span> <span>Show Unit Labels</span>
<kbd <kbd
className={` className={`
@@ -44,10 +51,10 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; options
dark:hover:bg-olympus-400 dark:hover:bg-olympus-400
`} `}
onClick={() => { onClick={() => {
getApp().getMap().setOption("showUnitsEngagementRings", !props.options.showUnitsEngagementRings); getApp().getMap().setOption("showUnitsEngagementRings", !mapOptions.showUnitsEngagementRings);
}} }}
> >
<OlCheckbox checked={props.options.showUnitsEngagementRings} onChange={() => {}}></OlCheckbox> <OlCheckbox checked={mapOptions.showUnitsEngagementRings} onChange={() => {}}></OlCheckbox>
<span>Show Threat Rings</span> <span>Show Threat Rings</span>
<kbd <kbd
className={` className={`
@@ -66,10 +73,10 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; options
dark:hover:bg-olympus-400 dark:hover:bg-olympus-400
`} `}
onClick={() => { onClick={() => {
getApp().getMap().setOption("showUnitsAcquisitionRings", !props.options.showUnitsAcquisitionRings); getApp().getMap().setOption("showUnitsAcquisitionRings", !mapOptions.showUnitsAcquisitionRings);
}} }}
> >
<OlCheckbox checked={props.options.showUnitsAcquisitionRings} onChange={() => {}}></OlCheckbox> <OlCheckbox checked={mapOptions.showUnitsAcquisitionRings} onChange={() => {}}></OlCheckbox>
<span>Show Detection rings</span> <span>Show Detection rings</span>
<kbd <kbd
className={` className={`
@@ -88,10 +95,10 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; options
dark:hover:bg-olympus-400 dark:hover:bg-olympus-400
`} `}
onClick={() => { onClick={() => {
getApp().getMap().setOption("showUnitTargets", !props.options.showUnitTargets); getApp().getMap().setOption("showUnitTargets", !mapOptions.showUnitTargets);
}} }}
> >
<OlCheckbox checked={props.options.showUnitTargets} onChange={() => {}}></OlCheckbox> <OlCheckbox checked={mapOptions.showUnitTargets} onChange={() => {}}></OlCheckbox>
<span>Show Detection lines</span> <span>Show Detection lines</span>
<kbd <kbd
className={` className={`
@@ -110,10 +117,10 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; options
dark:hover:bg-olympus-400 dark:hover:bg-olympus-400
`} `}
onClick={() => { onClick={() => {
getApp().getMap().setOption("hideUnitsShortRangeRings", !props.options.hideUnitsShortRangeRings); getApp().getMap().setOption("hideUnitsShortRangeRings", !mapOptions.hideUnitsShortRangeRings);
}} }}
> >
<OlCheckbox checked={props.options.hideUnitsShortRangeRings} onChange={() => {}}></OlCheckbox> <OlCheckbox checked={mapOptions.hideUnitsShortRangeRings} onChange={() => {}}></OlCheckbox>
<span>Hide Short range Rings</span> <span>Hide Short range Rings</span>
<kbd <kbd
className={` className={`
@@ -132,10 +139,32 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; options
dark:hover:bg-olympus-400 dark:hover:bg-olympus-400
`} `}
onClick={() => { onClick={() => {
getApp().getMap().setOption("hideGroupMembers", !props.options.hideGroupMembers); getApp().getMap().setOption("keepRelativePositions", !mapOptions.keepRelativePositions);
}} }}
> >
<OlCheckbox checked={props.options.hideGroupMembers} onChange={() => {}}></OlCheckbox> <OlCheckbox checked={mapOptions.keepRelativePositions} onChange={() => {}}></OlCheckbox>
<span>Keep units relative positions</span>
<kbd
className={`
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
text-xs font-semibold text-gray-800
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
`}
>
P
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp().getMap().setOption("hideGroupMembers", !mapOptions.hideGroupMembers);
}}
>
<OlCheckbox checked={mapOptions.hideGroupMembers} onChange={() => {}}></OlCheckbox>
<span>Hide Group members</span> <span>Hide Group members</span>
<kbd <kbd
className={` className={`
@@ -154,10 +183,10 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; options
dark:hover:bg-olympus-400 dark:hover:bg-olympus-400
`} `}
onClick={() => { onClick={() => {
getApp().getMap().setOption("showMinimap", !props.options.showMinimap); getApp().getMap().setOption("showMinimap", !mapOptions.showMinimap);
}} }}
> >
<OlCheckbox checked={props.options.showMinimap} onChange={() => {}}></OlCheckbox> <OlCheckbox checked={mapOptions.showMinimap} onChange={() => {}}></OlCheckbox>
<span>Show minimap</span> <span>Show minimap</span>
<kbd <kbd
className={` className={`

View File

@@ -167,7 +167,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
blueprint={blueprint} blueprint={blueprint}
onClick={() => setBlueprint(blueprint)} onClick={() => setBlueprint(blueprint)}
showCost={showCost} showCost={showCost}
cost={blueprint.cost ?? 10} cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
/> />
); );
})} })}
@@ -219,7 +219,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
blueprint={blueprint} blueprint={blueprint}
onClick={() => setBlueprint(blueprint)} onClick={() => setBlueprint(blueprint)}
showCost={showCost} showCost={showCost}
cost={blueprint.cost ?? 10} cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
/> />
); );
})} })}
@@ -250,7 +250,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
blueprint={blueprint} blueprint={blueprint}
onClick={() => setBlueprint(blueprint)} onClick={() => setBlueprint(blueprint)}
showCost={showCost} showCost={showCost}
cost={blueprint.cost ?? 10} cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
/> />
); );
})} })}
@@ -281,7 +281,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
blueprint={blueprint} blueprint={blueprint}
onClick={() => setBlueprint(blueprint)} onClick={() => setBlueprint(blueprint)}
showCost={showCost} showCost={showCost}
cost={blueprint.cost ?? 10} cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
/> />
); );
})} })}
@@ -336,7 +336,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
blueprint={blueprint} blueprint={blueprint}
onClick={() => setBlueprint(blueprint)} onClick={() => setBlueprint(blueprint)}
showCost={showCost} showCost={showCost}
cost={blueprint.cost ?? 10} cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
/> />
); );
})} })}
@@ -388,7 +388,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
blueprint={blueprint} blueprint={blueprint}
onClick={() => setBlueprint(blueprint)} onClick={() => setBlueprint(blueprint)}
showCost={showCost} showCost={showCost}
cost={blueprint.cost ?? 10} cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
/> />
); );
})} })}

View File

@@ -11,7 +11,7 @@ import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChange
export function UnitControlBar(props: {}) { export function UnitControlBar(props: {}) {
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED); const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
const [contextActionSet, setContextActionsSet] = useState(null as ContextActionSet | null); const [contextActionSet, setcontextActionSet] = useState(null as ContextActionSet | null);
const [contextAction, setContextAction] = useState(null as ContextAction | null); const [contextAction, setContextAction] = useState(null as ContextAction | null);
const [scrolledLeft, setScrolledLeft] = useState(true); const [scrolledLeft, setScrolledLeft] = useState(true);
const [scrolledRight, setScrolledRight] = useState(false); const [scrolledRight, setScrolledRight] = useState(false);
@@ -24,7 +24,7 @@ export function UnitControlBar(props: {}) {
useEffect(() => { useEffect(() => {
AppStateChangedEvent.on((state, subState) => setAppState(state)); AppStateChangedEvent.on((state, subState) => setAppState(state));
ContextActionSetChangedEvent.on((contextActionSet) => setContextActionsSet(contextActionSet)); ContextActionSetChangedEvent.on((contextActionSet) => setcontextActionSet(contextActionSet));
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction)); ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
}, []); }, []);
@@ -39,15 +39,9 @@ export function UnitControlBar(props: {}) {
sr > 1 && scrolledRight && setScrolledRight(false); sr > 1 && scrolledRight && setScrolledRight(false);
} }
let reorderedActions: ContextAction[] = []; let reorderedActions: ContextAction[] = contextActionSet
CONTEXT_ACTION_COLORS.forEach((color) => { ? Object.values(contextActionSet.getContextActions()).sort((a: ContextAction, b: ContextAction) => (a.getOptions().type < b.getOptions().type ? -1 : 1))
if (contextActionSet) { : [];
Object.values(contextActionSet.getContextActions()).forEach((contextAction: ContextAction) => {
if (color === null && contextAction.getOptions().buttonColor === undefined) reorderedActions.push(contextAction);
else if (color === contextAction.getOptions().buttonColor) reorderedActions.push(contextAction);
});
}
});
return ( return (
<> <>
@@ -77,7 +71,7 @@ export function UnitControlBar(props: {}) {
checked={contextActionIt === contextAction} checked={contextActionIt === contextAction}
icon={contextActionIt.getIcon()} icon={contextActionIt.getIcon()}
tooltip={contextActionIt.getLabel()} tooltip={contextActionIt.getLabel()}
borderColor={contextActionIt.getOptions().buttonColor} buttonColor={CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type ?? 0]}
onClick={() => { onClick={() => {
if (contextActionIt.getOptions().executeImmediately) { if (contextActionIt.getOptions().executeImmediately) {
contextActionIt.executeCallback(null, null); contextActionIt.executeCallback(null, null);
@@ -99,6 +93,7 @@ export function UnitControlBar(props: {}) {
/> />
)} )}
</div> </div>
{/*}
{contextAction && ( {contextAction && (
<div <div
className={` className={`
@@ -125,6 +120,7 @@ export function UnitControlBar(props: {}) {
</div> </div>
</div> </div>
)} )}
{*/}
</> </>
)} )}
</> </>

View File

@@ -1,12 +1,18 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { Menu } from "./components/menu"; import { Menu } from "./components/menu";
import { OlDropdown, OlDropdownItem } from "../components/oldropdown"; import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
import { Unit } from "../../unit/unit"; import { Unit } from "../../unit/unit";
import { getApp } from "../../olympusapp"; import { getApp } from "../../olympusapp";
import { UnitExplosionRequestEvent } from "../../events";
export function UnitExplosionMenu(props: { open: boolean; onClose: () => void; units: Unit[] | null; children?: JSX.Element | JSX.Element[] }) { export function UnitExplosionMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
const [units, setUnits] = useState(null as null | Unit[])
const [explosionType, setExplosionType] = useState("High explosive"); const [explosionType, setExplosionType] = useState("High explosive");
useEffect(() => {
UnitExplosionRequestEvent.on((units) => setUnits(units))
}, [])
return ( return (
<Menu title="Unit explosion menu" open={props.open} showBackButton={false} onClose={props.onClose}> <Menu title="Unit explosion menu" open={props.open} showBackButton={false} onClose={props.onClose}>
<div className="flex h-full flex-col gap-4 p-4"> <div className="flex h-full flex-col gap-4 p-4">
@@ -26,16 +32,16 @@ export function UnitExplosionMenu(props: { open: boolean; onClose: () => void; u
); );
})} })}
</OlDropdown> </OlDropdown>
{props.units !== null && ( {units !== null && (
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
if (explosionType === "High explosive") { if (explosionType === "High explosive") {
getApp()?.getUnitsManager().delete(true, "normal", props.units); getApp()?.getUnitsManager().delete(true, "normal", units);
} else if (explosionType === "Napalm") { } else if (explosionType === "Napalm") {
getApp()?.getUnitsManager().delete(true, "napalm", props.units); getApp()?.getUnitsManager().delete(true, "napalm", units);
} else if (explosionType === "White phosphorous") { } else if (explosionType === "White phosphorous") {
getApp()?.getUnitsManager().delete(true, "phosphorous", props.units); getApp()?.getUnitsManager().delete(true, "phosphorous", units);
} }
props.onClose(); props.onClose();
}} }}

View File

@@ -36,6 +36,7 @@ import { UnitExplosionMenu } from "./panels/unitexplosionmenu";
import { JTACMenu } from "./panels/jtacmenu"; import { JTACMenu } from "./panels/jtacmenu";
import { AppStateChangedEvent, MapOptionsChangedEvent } from "../events"; import { AppStateChangedEvent, MapOptionsChangedEvent } from "../events";
import { GameMasterMenu } from "./panels/gamemastermenu"; import { GameMasterMenu } from "./panels/gamemastermenu";
import { InfoBar } from "./panels/infobar";
export type OlympusUIState = { export type OlympusUIState = {
mainMenuVisible: boolean; mainMenuVisible: boolean;
@@ -52,23 +53,16 @@ export type OlympusUIState = {
export function UI() { export function UI() {
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED); const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState); const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
const [checkingPassword, setCheckingPassword] = useState(false); const [checkingPassword, setCheckingPassword] = useState(false);
const [loginError, setLoginError] = useState(false); const [loginError, setLoginError] = useState(false);
const [commandMode, setCommandMode] = useState(null as null | string); const [commandMode, setCommandMode] = useState(null as null | string);
const [formationLeader, setFormationLeader] = useState(null as null | Unit);
const [formationWingmen, setFormationWingmen] = useState(null as null | Unit[]);
const [unitExplosionUnits, setUnitExplosionUnits] = useState([] as Unit[]);
useEffect(() => { useEffect(() => {
AppStateChangedEvent.on((state, subState) => { AppStateChangedEvent.on((state, subState) => {
setAppState(state); setAppState(state);
setAppSubState(subState); setAppSubState(subState);
}); });
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
}, []); }, []);
function checkPassword(password: string) { function checkPassword(password: string) {
@@ -142,16 +136,14 @@ export function UI() {
<div id="map-container" className="z-0 h-full w-screen" /> <div id="map-container" className="z-0 h-full w-screen" />
<MainMenu open={appState === OlympusState.MAIN_MENU} onClose={() => getApp().setState(OlympusState.IDLE)} /> <MainMenu open={appState === OlympusState.MAIN_MENU} onClose={() => getApp().setState(OlympusState.IDLE)} />
<SpawnMenu open={appState === OlympusState.SPAWN} onClose={() => getApp().setState(OlympusState.IDLE)} /> <SpawnMenu open={appState === OlympusState.SPAWN} onClose={() => getApp().setState(OlympusState.IDLE)} />
<OptionsMenu open={appState === OlympusState.OPTIONS} onClose={() => getApp().setState(OlympusState.IDLE)} options={mapOptions} /* TODO remove */ /> <OptionsMenu open={appState === OlympusState.OPTIONS} onClose={() => getApp().setState(OlympusState.IDLE)}/>
<UnitControlMenu <UnitControlMenu
open={appState === OlympusState.UNIT_CONTROL && appSubState !== UnitControlSubState.FORMATION} open={appState === OlympusState.UNIT_CONTROL && ![UnitControlSubState.FORMATION, UnitControlSubState.UNIT_EXPLOSION_MENU].includes(appSubState as UnitControlSubState)}
onClose={() => getApp().setState(OlympusState.IDLE)} onClose={() => getApp().setState(OlympusState.IDLE)}
/> />
<FormationMenu <FormationMenu
open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.FORMATION} open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.FORMATION}
leader={formationLeader}
wingmen={formationWingmen}
onClose={() => getApp().setState(OlympusState.IDLE)} onClose={() => getApp().setState(OlympusState.IDLE)}
/> />
@@ -160,7 +152,7 @@ export function UI() {
<AudioMenu open={appState === OlympusState.AUDIO} onClose={() => getApp().setState(OlympusState.IDLE)} /> <AudioMenu open={appState === OlympusState.AUDIO} onClose={() => getApp().setState(OlympusState.IDLE)} />
<GameMasterMenu open={appState === OlympusState.GAME_MASTER} onClose={() => getApp().setState(OlympusState.IDLE)} /> <GameMasterMenu open={appState === OlympusState.GAME_MASTER} onClose={() => getApp().setState(OlympusState.IDLE)} />
{/* TODO} <UnitExplosionMenu open={appState === OlympusState.MAIN_MENU} units={unitExplosionUnits} onClose={() => getApp().setState(OlympusState.IDLE)} /> {*/} <UnitExplosionMenu open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.UNIT_EXPLOSION_MENU} onClose={() => getApp().setState(OlympusState.IDLE)} />
<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} /> <JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />
<MiniMapPanel /> <MiniMapPanel />
@@ -168,6 +160,7 @@ export function UI() {
<UnitControlBar /> <UnitControlBar />
<MapContextMenu /> <MapContextMenu />
<SideBar /> <SideBar />
<InfoBar />
</div> </div>
</div> </div>
); );

View File

@@ -1,13 +1,14 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { Unit } from "./unit"; import { Unit } from "./unit";
import { LatLng } from "leaflet"; import { LatLng } from "leaflet";
import { ContextActionType } from "../constants/constants";
export interface ContextActionOptions { export interface ContextActionOptions {
executeImmediately?: boolean; executeImmediately?: boolean;
buttonColor?: string | null; type: ContextActionType;
} }
export type ContextActionCallback = (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => void; export type ContextActionCallback = (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null, originalEvent?: MouseEvent) => void;
export class ContextAction { export class ContextAction {
#id: string = ""; #id: string = "";
@@ -64,7 +65,7 @@ export class ContextAction {
return this.#target; return this.#target;
} }
executeCallback(targetUnit: Unit | null, targetPosition: LatLng | null) { executeCallback(targetUnit: Unit | null, targetPosition: LatLng | null, originalEvent?: MouseEvent) {
if (this.#callback) this.#callback(this.#units, targetUnit, targetPosition); if (this.#callback) this.#callback(this.#units, targetUnit, targetPosition, originalEvent);
} }
} }

View File

@@ -226,4 +226,61 @@ export class UnitDatabase {
shortLabel: "", shortLabel: "",
}; };
} }
getRandomUnit(
options: {
type?: string;
role?: string;
ranges?: string[];
eras?: string[];
coalition?: string;
}
) {
/* Start from all the unit blueprints in the database */
var unitBlueprints = this.getBlueprints();
/* If a specific type or role is provided, use only the blueprints of that type or role */
if (options.type && options.role) {
console.error("Can't create random unit if both type and role are provided. Either create by type or by role.");
return null;
}
if (options.type) {
unitBlueprints = this.getByType(options.type);
} else if (options.role) {
unitBlueprints = this.getByType(options.role);
}
/* Keep only the units that have a range included in the requested values */
if (options.ranges) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
var rangeType = "";
var range = unitBlueprint.acquisitionRange;
if (range !== undefined) {
if (range >= 0 && range < 10000) rangeType = "Short range";
else if (range >= 10000 && range < 100000) rangeType = "Medium range";
else if (range >= 100000 && range < 999999) rangeType = "Long range";
}
return options.ranges?.includes(rangeType);
});
}
/* Keep only the units that have an era included in the requested values */
if (options.eras) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
return unitBlueprint.era ? options.eras?.includes(unitBlueprint.era) : true;
});
}
/* Keep only the units that have the correct coalition, if selected */
if (options.coalition) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
return unitBlueprint.coalition && unitBlueprint.coalition !== "" ? options.coalition === unitBlueprint.coalition : true;
});
}
var index = Math.floor(Math.random() * unitBlueprints.length);
return unitBlueprints[index];
}
} }

View File

@@ -18,7 +18,6 @@ import {
} from "../other/utils"; } from "../other/utils";
import { CustomMarker } from "../map/markers/custommarker"; import { CustomMarker } from "../map/markers/custommarker";
import { SVGInjector } from "@tanem/svg-injector"; import { SVGInjector } from "@tanem/svg-injector";
import { UnitDatabase } from "./databases/unitdatabase";
import { TargetMarker } from "../map/markers/targetmarker"; import { TargetMarker } from "../map/markers/targetmarker";
import { import {
DLINK, DLINK,
@@ -36,11 +35,10 @@ import {
GROUPING_ZOOM_TRANSITION, GROUPING_ZOOM_TRANSITION,
MAX_SHOTS_SCATTER, MAX_SHOTS_SCATTER,
SHOTS_SCATTER_DEGREES, SHOTS_SCATTER_DEGREES,
ContextActionColors,
CONTEXT_ACTION_COLORS,
OlympusState, OlympusState,
JTACSubState, JTACSubState,
UnitControlSubState, UnitControlSubState,
ContextActionType,
} from "../constants/constants"; } from "../constants/constants";
import { DataExtractor } from "../server/dataextractor"; import { DataExtractor } from "../server/dataextractor";
import { Weapon } from "../weapon/weapon"; import { Weapon } from "../weapon/weapon";
@@ -58,6 +56,7 @@ import {
} from "../ui/components/olicons"; } from "../ui/components/olicons";
import { import {
faExplosion, faExplosion,
faHand,
faLocationCrosshairs, faLocationCrosshairs,
faLocationDot, faLocationDot,
faMapLocation, faMapLocation,
@@ -69,7 +68,7 @@ import {
faXmarksLines, faXmarksLines,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { Carrier } from "../mission/carrier"; import { Carrier } from "../mission/carrier";
import { ContactsUpdatedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, UnitDeadEvent, UnitDeselectedEvent, UnitSelectedEvent, UnitUpdatedEvent } from "../events"; import { ContactsUpdatedEvent, FormationCreationRequestEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, UnitContextMenuRequestEvent, UnitDeadEvent, UnitDeselectedEvent, UnitExplosionRequestEvent, UnitSelectedEvent, UnitUpdatedEvent } from "../events";
var pathIcon = new Icon({ var pathIcon = new Icon({
iconUrl: "/vite/images/markers/marker-icon.png", iconUrl: "/vite/images/markers/marker-icon.png",
@@ -828,6 +827,22 @@ export abstract class Unit extends CustomMarker {
* *
*/ */
appendContextActions(contextActionSet: ContextActionSet) { appendContextActions(contextActionSet: ContextActionSet) {
contextActionSet.addContextAction(
this,
"stop",
"Stop unit",
"Stops the unit",
faHand,
null,
(units: Unit[], _1, _2) => {
getApp().getUnitsManager().clearDestinations(units);
},
{
executeImmediately: true,
type: ContextActionType.MOVE,
}
);
contextActionSet.addContextAction( contextActionSet.addContextAction(
this, this,
"move", "move",
@@ -835,11 +850,11 @@ export abstract class Unit extends CustomMarker {
"Click on the map to move the units there", "Click on the map to move the units there",
faLocationDot, faLocationDot,
"position", "position",
(units: Unit[], _, targetPosition) => { (units: Unit[], _, targetPosition, originalEvent) => {
getApp().getUnitsManager().clearDestinations(units); if (!originalEvent?.ctrlKey) getApp().getUnitsManager().clearDestinations(units);
if (targetPosition) getApp().getUnitsManager().addDestination(targetPosition, false, 0, units); if (targetPosition) getApp().getUnitsManager().addDestination(targetPosition, getApp().getMap().getOptions().keepRelativePositions, 0, units);
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.MOVE] } { type: ContextActionType.MOVE }
); );
contextActionSet.addContextAction( contextActionSet.addContextAction(
@@ -850,9 +865,9 @@ export abstract class Unit extends CustomMarker {
faRoute, faRoute,
"position", "position",
(units: Unit[], _, targetPosition) => { (units: Unit[], _, targetPosition) => {
if (targetPosition) getApp().getUnitsManager().addDestination(targetPosition, false, 0, units); if (targetPosition) getApp().getUnitsManager().addDestination(targetPosition, getApp().getMap().getOptions().keepRelativePositions, 0, units);
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.MOVE] } { type: ContextActionType.MOVE }
); );
contextActionSet.addContextAction( contextActionSet.addContextAction(
@@ -867,7 +882,7 @@ export abstract class Unit extends CustomMarker {
}, },
{ {
executeImmediately: true, executeImmediately: true,
buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.DELETE], type: ContextActionType.DELETE,
} }
); );
@@ -880,16 +895,17 @@ export abstract class Unit extends CustomMarker {
null, null,
(units: Unit[], _1, _2) => { (units: Unit[], _1, _2) => {
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.UNIT_EXPLOSION_MENU) getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.UNIT_EXPLOSION_MENU)
UnitExplosionRequestEvent.dispatch(units)
}, },
{ {
executeImmediately: true, executeImmediately: true,
buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.DELETE], type: ContextActionType.DELETE,
} }
); );
contextActionSet.addDefaultContextAction(this, "default", "Set destination", "", faRoute, null, (units: Unit[], targetUnit, targetPosition) => { contextActionSet.addDefaultContextAction(this, "default", "Set destination", "", faRoute, null, (units: Unit[], targetUnit, targetPosition, originalEvent) => {
if (targetPosition) { if (targetPosition) {
getApp().getUnitsManager().clearDestinations(units); if (!originalEvent?.ctrlKey) getApp().getUnitsManager().clearDestinations(units);
getApp().getUnitsManager().addDestination(targetPosition, false, 0, units); getApp().getUnitsManager().addDestination(targetPosition, false, 0, units);
} }
}); });
@@ -1175,6 +1191,7 @@ export abstract class Unit extends CustomMarker {
clearDestinations() { clearDestinations() {
if (!this.#human) this.#activePath = []; if (!this.#human) this.#activePath = [];
getApp().getServerManager().addDestination(this.ID, []);
} }
updatePathFromMarkers() { updatePathFromMarkers() {
@@ -1393,11 +1410,11 @@ export abstract class Unit extends CustomMarker {
#onShortPress(e: LeafletMouseEvent) { #onShortPress(e: LeafletMouseEvent) {
console.log(`Short press on ${this.getUnitName()}`); console.log(`Short press on ${this.getUnitName()}`);
if (getApp().getState() === OlympusState.IDLE || e.originalEvent.ctrlKey) { if (getApp().getState() !== OlympusState.UNIT_CONTROL || e.originalEvent.ctrlKey) {
if (!e.originalEvent.ctrlKey) getApp().getUnitsManager().deselectAllUnits(); if (!e.originalEvent.ctrlKey) getApp().getUnitsManager().deselectAllUnits();
this.setSelected(!this.getSelected()); this.setSelected(!this.getSelected());
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) { } else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
if (getApp().getMap().getContextAction()) getApp().getMap().executeContextAction(this, null); if (getApp().getMap().getContextAction()) getApp().getMap().executeContextAction(this, null, e.originalEvent);
else { else {
getApp().getUnitsManager().deselectAllUnits(); getApp().getUnitsManager().deselectAllUnits();
this.setSelected(!this.getSelected()); this.setSelected(!this.getSelected());
@@ -1413,6 +1430,7 @@ export abstract class Unit extends CustomMarker {
if (e.originalEvent.button === 2) { if (e.originalEvent.button === 2) {
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.UNIT_CONTEXT_MENU) getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.UNIT_CONTEXT_MENU)
UnitContextMenuRequestEvent.dispatch(this);
} }
} }
@@ -1837,7 +1855,7 @@ export abstract class AirUnit extends Unit {
(units: Unit[]) => { (units: Unit[]) => {
getApp().getUnitsManager().refuel(units); getApp().getUnitsManager().refuel(units);
}, },
{ executeImmediately: true, buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.ADMIN] } { executeImmediately: true, type: ContextActionType.ADMIN }
); );
contextActionSet.addContextAction( contextActionSet.addContextAction(
this, this,
@@ -1849,7 +1867,7 @@ export abstract class AirUnit extends Unit {
(units: Unit[]) => { (units: Unit[]) => {
getApp().getMap().centerOnUnit(units[0]); getApp().getMap().centerOnUnit(units[0]);
}, },
{ executeImmediately: true, buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.OTHER] } { executeImmediately: true, type: ContextActionType.OTHER }
); );
/* Context actions that require a target unit */ /* Context actions that require a target unit */
@@ -1863,7 +1881,7 @@ export abstract class AirUnit extends Unit {
(units: Unit[], targetUnit: Unit | null, _) => { (units: Unit[], targetUnit: Unit | null, _) => {
if (targetUnit) getApp().getUnitsManager().attackUnit(targetUnit.ID, units); if (targetUnit) getApp().getUnitsManager().attackUnit(targetUnit.ID, units);
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.ENGAGE] } { type: ContextActionType.ENGAGE }
); );
contextActionSet.addContextAction( contextActionSet.addContextAction(
@@ -1875,17 +1893,11 @@ export abstract class AirUnit extends Unit {
"unit", "unit",
(units: Unit[], targetUnit: Unit | null, _) => { (units: Unit[], targetUnit: Unit | null, _) => {
if (targetUnit) { if (targetUnit) {
document.dispatchEvent( getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.FORMATION);
new CustomEvent("showFormationMenu", { FormationCreationRequestEvent.dispatch(targetUnit, units.filter((unit) => unit !== targetUnit))
detail: {
leader: targetUnit,
wingmen: units.filter((unit) => unit !== targetUnit),
},
})
);
} }
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.ADMIN] } { type: ContextActionType.ADMIN }
); );
if (this.canTargetPoint()) { if (this.canTargetPoint()) {
@@ -1898,9 +1910,9 @@ export abstract class AirUnit extends Unit {
faLocationCrosshairs, faLocationCrosshairs,
"position", "position",
(units: Unit[], _, targetPosition: LatLng | null) => { (units: Unit[], _, targetPosition: LatLng | null) => {
if (targetPosition) getApp().getUnitsManager().bombPoint(targetPosition, units); if (targetPosition) getApp().getUnitsManager().bombPoint(targetPosition, getApp().getMap().getOptions().keepRelativePositions, 0, units);
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.ENGAGE] } { type: ContextActionType.ENGAGE }
); );
contextActionSet.addContextAction( contextActionSet.addContextAction(
@@ -1911,9 +1923,9 @@ export abstract class AirUnit extends Unit {
faXmarksLines, faXmarksLines,
"position", "position",
(units: Unit[], _, targetPosition: LatLng | null) => { (units: Unit[], _, targetPosition: LatLng | null) => {
if (targetPosition) getApp().getUnitsManager().carpetBomb(targetPosition, units); if (targetPosition) getApp().getUnitsManager().carpetBomb(targetPosition, getApp().getMap().getOptions().keepRelativePositions, 0, units);
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.ENGAGE] } { type: ContextActionType.ENGAGE }
); );
} }
@@ -1927,7 +1939,7 @@ export abstract class AirUnit extends Unit {
(units: Unit[], _, targetPosition: LatLng | null) => { (units: Unit[], _, targetPosition: LatLng | null) => {
if (targetPosition) getApp().getUnitsManager().landAt(targetPosition, units); if (targetPosition) getApp().getUnitsManager().landAt(targetPosition, units);
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.ADMIN] } { type: ContextActionType.ADMIN }
); );
} }
} }
@@ -1973,9 +1985,9 @@ export class Helicopter extends AirUnit {
olButtonsContextLandAtPoint, olButtonsContextLandAtPoint,
"position", "position",
(units: Unit[], _, targetPosition: LatLng | null) => { (units: Unit[], _, targetPosition: LatLng | null) => {
if (targetPosition) getApp().getUnitsManager().landAtPoint(targetPosition, units); if (targetPosition) getApp().getUnitsManager().landAtPoint(targetPosition, getApp().getMap().getOptions().keepRelativePositions, 0, units);
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.ADMIN] } { type: ContextActionType.ADMIN }
); );
} }
@@ -2024,7 +2036,7 @@ export class GroundUnit extends Unit {
(units: Unit[], _1, _2) => { (units: Unit[], _1, _2) => {
getApp().getUnitsManager().createGroup(units); getApp().getUnitsManager().createGroup(units);
}, },
{ executeImmediately: true, buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.OTHER] } { executeImmediately: true, type: ContextActionType.OTHER }
); );
contextActionSet.addContextAction( contextActionSet.addContextAction(
this, this,
@@ -2036,7 +2048,7 @@ export class GroundUnit extends Unit {
(units: Unit[]) => { (units: Unit[]) => {
getApp().getMap().centerOnUnit(units[0]); getApp().getMap().centerOnUnit(units[0]);
}, },
{ executeImmediately: true, buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.OTHER] } { executeImmediately: true, type: ContextActionType.OTHER }
); );
/* Context actions that require a target unit */ /* Context actions that require a target unit */
@@ -2050,7 +2062,7 @@ export class GroundUnit extends Unit {
(units: Unit[], targetUnit: Unit | null, _) => { (units: Unit[], targetUnit: Unit | null, _) => {
if (targetUnit) getApp().getUnitsManager().attackUnit(targetUnit.ID, units); if (targetUnit) getApp().getUnitsManager().attackUnit(targetUnit.ID, units);
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.ENGAGE] } { type: ContextActionType.ENGAGE }
); );
/* Context actions that require a target position */ /* Context actions that require a target position */
@@ -2063,9 +2075,9 @@ export class GroundUnit extends Unit {
faLocationCrosshairs, faLocationCrosshairs,
"position", "position",
(units: Unit[], _, targetPosition: LatLng | null) => { (units: Unit[], _, targetPosition: LatLng | null) => {
if (targetPosition) getApp().getUnitsManager().fireAtArea(targetPosition, units); if (targetPosition) getApp().getUnitsManager().fireAtArea(targetPosition, getApp().getMap().getOptions().keepRelativePositions, 0, units);
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.ENGAGE] } { type: ContextActionType.ENGAGE }
); );
contextActionSet.addContextAction( contextActionSet.addContextAction(
this, this,
@@ -2075,9 +2087,9 @@ export class GroundUnit extends Unit {
olButtonsContextSimulateFireFight, olButtonsContextSimulateFireFight,
"position", "position",
(units: Unit[], _, targetPosition: LatLng | null) => { (units: Unit[], _, targetPosition: LatLng | null) => {
if (targetPosition) getApp().getUnitsManager().simulateFireFight(targetPosition, units); if (targetPosition) getApp().getUnitsManager().simulateFireFight(targetPosition, getApp().getMap().getOptions().keepRelativePositions, 0, units);
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.ADMIN] } { type: ContextActionType.ADMIN }
); );
} }
} }
@@ -2148,7 +2160,7 @@ export class NavyUnit extends Unit {
(units: Unit[], _1, _2) => { (units: Unit[], _1, _2) => {
getApp().getUnitsManager().createGroup(units); getApp().getUnitsManager().createGroup(units);
}, },
{ executeImmediately: true, buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.OTHER] } { executeImmediately: true, type: ContextActionType.OTHER }
); );
contextActionSet.addContextAction( contextActionSet.addContextAction(
this, this,
@@ -2160,7 +2172,7 @@ export class NavyUnit extends Unit {
(units: Unit[]) => { (units: Unit[]) => {
getApp().getMap().centerOnUnit(units[0]); getApp().getMap().centerOnUnit(units[0]);
}, },
{ executeImmediately: true, buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.OTHER] } { executeImmediately: true, type: ContextActionType.OTHER }
); );
/* Context actions that require a target unit */ /* Context actions that require a target unit */
@@ -2174,7 +2186,7 @@ export class NavyUnit extends Unit {
(units: Unit[], targetUnit: Unit | null, _) => { (units: Unit[], targetUnit: Unit | null, _) => {
if (targetUnit) getApp().getUnitsManager().attackUnit(targetUnit.ID, units); if (targetUnit) getApp().getUnitsManager().attackUnit(targetUnit.ID, units);
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.ENGAGE] } { type: ContextActionType.ENGAGE }
); );
/* Context actions that require a target position */ /* Context actions that require a target position */
@@ -2186,9 +2198,9 @@ export class NavyUnit extends Unit {
faLocationCrosshairs, faLocationCrosshairs,
"position", "position",
(units: Unit[], _, targetPosition: LatLng | null) => { (units: Unit[], _, targetPosition: LatLng | null) => {
if (targetPosition) getApp().getUnitsManager().fireAtArea(targetPosition, units); if (targetPosition) getApp().getUnitsManager().fireAtArea(targetPosition, getApp().getMap().getOptions().keepRelativePositions, 0, units);
}, },
{ buttonColor: CONTEXT_ACTION_COLORS[ContextActionColors.ENGAGE] } { type: ContextActionType.ENGAGE }
); );
} }

View File

@@ -11,10 +11,6 @@ import {
mToFt, mToFt,
mercatorToLatLng, mercatorToLatLng,
msToKnots, msToKnots,
polyContains,
polygonArea,
randomPointInPoly,
randomUnitBlueprint,
} from "../other/utils"; } from "../other/utils";
import { CoalitionPolygon } from "../map/coalitionarea/coalitionpolygon"; import { CoalitionPolygon } from "../map/coalitionarea/coalitionpolygon";
import { DELETE_CYCLE_TIME, DELETE_SLOW_THRESHOLD, DataIndexes, GAME_MASTER, IADSDensities, OlympusState, UnitControlSubState } from "../constants/constants"; import { DELETE_CYCLE_TIME, DELETE_SLOW_THRESHOLD, DataIndexes, GAME_MASTER, IADSDensities, OlympusState, UnitControlSubState } from "../constants/constants";
@@ -33,6 +29,7 @@ import { ContextActionSet } from "./contextactionset";
import { import {
CommandModeOptionsChangedEvent, CommandModeOptionsChangedEvent,
ContactsUpdatedEvent, ContactsUpdatedEvent,
InfoPopupEvent,
SelectedUnitsChangedEvent, SelectedUnitsChangedEvent,
SelectionClearedEvent, SelectionClearedEvent,
UnitDatabaseLoadedEvent, UnitDatabaseLoadedEvent,
@@ -343,9 +340,7 @@ export class UnitsManager {
addDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number, units: Unit[] | null = null) { addDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
/* Compute the destination for each unit. If mantainRelativePosition is true, compute the destination so to hold the relative positions */ /* Compute the destination for each unit. If mantainRelativePosition is true, compute the destination so to hold the relative positions */
@@ -380,11 +375,9 @@ export class UnitsManager {
*/ */
clearDestinations(units: Unit[] | null = null) { clearDestinations(units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units: Unit[]) => {
for (let idx in units) { for (let idx in units) {
const unit = units[idx]; const unit = units[idx];
if (unit.getState() === "follow") { if (unit.getState() === "follow") {
@@ -408,9 +401,7 @@ export class UnitsManager {
*/ */
landAt(latlng: LatLng, units: Unit[] | null = null) { landAt(latlng: LatLng, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.landAt(latlng)); units.forEach((unit: Unit) => unit.landAt(latlng));
@@ -430,9 +421,7 @@ export class UnitsManager {
*/ */
changeSpeed(speedChange: string, units: Unit[] | null = null) { changeSpeed(speedChange: string, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.changeSpeed(speedChange)); units.forEach((unit: Unit) => unit.changeSpeed(speedChange));
@@ -450,9 +439,7 @@ export class UnitsManager {
*/ */
changeAltitude(altitudeChange: string, units: Unit[] | null = null) { changeAltitude(altitudeChange: string, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.changeAltitude(altitudeChange)); units.forEach((unit: Unit) => unit.changeAltitude(altitudeChange));
@@ -470,9 +457,7 @@ export class UnitsManager {
*/ */
setSpeed(speed: number, units: Unit[] | null = null) { setSpeed(speed: number, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.setSpeed(speed)); units.forEach((unit: Unit) => unit.setSpeed(speed));
@@ -491,9 +476,7 @@ export class UnitsManager {
*/ */
setSpeedType(speedType: string, units: Unit[] | null = null) { setSpeedType(speedType: string, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.setSpeedType(speedType)); units.forEach((unit: Unit) => unit.setSpeedType(speedType));
@@ -512,9 +495,7 @@ export class UnitsManager {
*/ */
setAltitude(altitude: number, units: Unit[] | null = null) { setAltitude(altitude: number, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.setAltitude(altitude)); units.forEach((unit: Unit) => unit.setAltitude(altitude));
@@ -533,9 +514,7 @@ export class UnitsManager {
*/ */
setAltitudeType(altitudeType: string, units: Unit[] | null = null) { setAltitudeType(altitudeType: string, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.setAltitudeType(altitudeType)); units.forEach((unit: Unit) => unit.setAltitudeType(altitudeType));
@@ -554,9 +533,7 @@ export class UnitsManager {
*/ */
setROE(ROE: string, units: Unit[] | null = null) { setROE(ROE: string, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.setROE(ROE)); units.forEach((unit: Unit) => unit.setROE(ROE));
@@ -575,9 +552,7 @@ export class UnitsManager {
*/ */
setReactionToThreat(reactionToThreat: string, units: Unit[] | null = null) { setReactionToThreat(reactionToThreat: string, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.setReactionToThreat(reactionToThreat)); units.forEach((unit: Unit) => unit.setReactionToThreat(reactionToThreat));
@@ -596,9 +571,7 @@ export class UnitsManager {
*/ */
setEmissionsCountermeasures(emissionCountermeasure: string, units: Unit[] | null = null) { setEmissionsCountermeasures(emissionCountermeasure: string, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.setEmissionsCountermeasures(emissionCountermeasure)); units.forEach((unit: Unit) => unit.setEmissionsCountermeasures(emissionCountermeasure));
@@ -617,9 +590,7 @@ export class UnitsManager {
*/ */
setOnOff(onOff: boolean, units: Unit[] | null = null) { setOnOff(onOff: boolean, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.setOnOff(onOff)); units.forEach((unit: Unit) => unit.setOnOff(onOff));
@@ -638,9 +609,7 @@ export class UnitsManager {
*/ */
setFollowRoads(followRoads: boolean, units: Unit[] | null = null) { setFollowRoads(followRoads: boolean, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.setFollowRoads(followRoads)); units.forEach((unit: Unit) => unit.setFollowRoads(followRoads));
@@ -660,9 +629,7 @@ export class UnitsManager {
setOperateAs(operateAsBool: boolean, units: Unit[] | null = null) { setOperateAs(operateAsBool: boolean, units: Unit[] | null = null) {
var operateAs = operateAsBool ? "blue" : "red"; var operateAs = operateAsBool ? "blue" : "red";
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.setOperateAs(operateAs)); units.forEach((unit: Unit) => unit.setOperateAs(operateAs));
@@ -681,9 +648,7 @@ export class UnitsManager {
*/ */
attackUnit(ID: number, units: Unit[] | null = null) { attackUnit(ID: number, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.attackUnit(ID)); units.forEach((unit: Unit) => unit.attackUnit(ID));
@@ -701,9 +666,7 @@ export class UnitsManager {
refuel(units: Unit[] | null = null) { refuel(units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.refuel()); units.forEach((unit: Unit) => unit.refuel());
@@ -724,9 +687,7 @@ export class UnitsManager {
*/ */
followUnit(ID: number, offset?: { x: number; y: number; z: number }, formation?: string, units: Unit[] | null = null) { followUnit(ID: number, offset?: { x: number; y: number; z: number }, formation?: string, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
if (offset == undefined) { if (offset == undefined) {
@@ -759,11 +720,10 @@ export class UnitsManager {
} else offset = undefined; } else offset = undefined;
} }
if (getApp().getMap().getOptions().protectDCSUnits && !units.every((unit) => unit.isControlledByOlympus())){ if (getApp().getMap().getOptions().protectDCSUnits && !units.every((unit) => unit.isControlledByOlympus())) {
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.PROTECTION); getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.PROTECTION);
this.#protectionCallback = callback; this.#protectionCallback = callback;
} } else callback(units);
else callback(units);
}; };
var count = 1; var count = 1;
var xr = 0; var xr = 0;
@@ -813,14 +773,19 @@ export class UnitsManager {
* @param latlng Location to bomb * @param latlng Location to bomb
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/ */
bombPoint(latlng: LatLng, units: Unit[] | null = null) { bombPoint(latlng: LatLng, mantainRelativePosition: boolean, rotation: number = 0, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.bombPoint(latlng)); /* Compute the target for each unit. If mantainRelativePosition is true, compute the target so to hold the relative positions */
var unitTargets: { [key: number]: LatLng } = {};
if (mantainRelativePosition) unitTargets = this.computeGroupDestination(latlng, rotation);
else
units.forEach((unit: Unit) => {
unitTargets[unit.ID] = latlng;
});
units.forEach((unit: Unit) => unit.bombPoint(unitTargets[unit.ID]));
this.#showActionMessage(units, `unit bombing point`); this.#showActionMessage(units, `unit bombing point`);
}; };
@@ -834,14 +799,19 @@ export class UnitsManager {
* @param latlng Location to bomb * @param latlng Location to bomb
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/ */
carpetBomb(latlng: LatLng, units: Unit[] | null = null) { carpetBomb(latlng: LatLng, mantainRelativePosition: boolean, rotation: number = 0, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.carpetBomb(latlng)); /* Compute the target for each unit. If mantainRelativePosition is true, compute the target so to hold the relative positions */
var unitTargets: { [key: number]: LatLng } = {};
if (mantainRelativePosition) unitTargets = this.computeGroupDestination(latlng, rotation);
else
units.forEach((unit: Unit) => {
unitTargets[unit.ID] = latlng;
});
units.forEach((unit: Unit) => unit.carpetBomb(unitTargets[unit.ID]));
this.#showActionMessage(units, `unit carpet bombing point`); this.#showActionMessage(units, `unit carpet bombing point`);
}; };
@@ -855,14 +825,19 @@ export class UnitsManager {
* @param latlng Location to fire at * @param latlng Location to fire at
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/ */
fireAtArea(latlng: LatLng, units: Unit[] | null = null) { fireAtArea(latlng: LatLng, mantainRelativePosition: boolean, rotation: number = 0, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.fireAtArea(latlng)); /* Compute the target for each unit. If mantainRelativePosition is true, compute the target so to hold the relative positions */
var unitTargets: { [key: number]: LatLng } = {};
if (mantainRelativePosition) unitTargets = this.computeGroupDestination(latlng, rotation);
else
units.forEach((unit: Unit) => {
unitTargets[unit.ID] = latlng;
});
units.forEach((unit: Unit) => unit.fireAtArea(unitTargets[unit.ID]));
this.#showActionMessage(units, `unit firing at area`); this.#showActionMessage(units, `unit firing at area`);
}; };
@@ -876,26 +851,34 @@ export class UnitsManager {
* @param latlng Location to fire at * @param latlng Location to fire at
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/ */
simulateFireFight(latlng: LatLng, units: Unit[] | null = null) { simulateFireFight(latlng: LatLng, mantainRelativePosition: boolean, rotation: number = 0, units: Unit[] | null = null) {
// TODO if (units === null) units = this.getSelectedUnits();
// if (units === null) units = units.filter((unit) => !unit.getHuman());
// units = this.getSelectedUnits();
// units = units.filter((unit => {!unit.getHuman()})); let callback = (units) => {
// getGroundElevation(latlng, (response: string) => {
// let callback = (units) => { var groundElevation: number | null = null;
// try {
// getGroundElevation(latlng, (response: string) => { groundElevation = parseFloat(response);
// var groundElevation: number | null = null; /* Compute the target for each unit. If mantainRelativePosition is true, compute the target so to hold the relative positions */
// try { var unitTargets: { [key: number]: LatLng } = {};
// groundElevation = parseFloat(response); if (mantainRelativePosition) unitTargets = this.computeGroupDestination(latlng, rotation);
// } catch { else
// console.warn("Simulate fire fight: could not retrieve ground elevation"); units.forEach((unit: Unit) => {
// } unitTargets[unit.ID] = latlng;
// });
//if (getApp().getMap().getOptions().protectDCSUnits && !units.every(unit => unit.isControlledByOlympus())) units.forEach((unit: Unit) => unit.simulateFireFight(unitTargets[unit.ID], groundElevation));
// this.showProtectedUnitsPopup(units.filter(unit => unit.isControlledByDCS()).length, callback);} units?.forEach((unit: Unit) => unit.simulateFireFight(latlng, groundElevation)); this.#showActionMessage(units, `simulating fire fight`);
// }); } catch {
// this.#showActionMessage(units, `unit simulating fire fight`); console.warn("Simulate fire fight: could not retrieve ground elevation");
}
});
};
if (getApp().getMap().getOptions().protectDCSUnits && !units.every((unit) => unit.isControlledByOlympus())) {
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.PROTECTION);
this.#protectionCallback = callback;
} else callback(units);
} }
/** Instruct units to enter into scenic AAA mode. Units will shoot in the air without aiming /** Instruct units to enter into scenic AAA mode. Units will shoot in the air without aiming
@@ -903,9 +886,7 @@ export class UnitsManager {
*/ */
scenicAAA(units: Unit[] | null = null) { scenicAAA(units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.scenicAAA()); units.forEach((unit: Unit) => unit.scenicAAA());
@@ -922,9 +903,7 @@ export class UnitsManager {
*/ */
missOnPurpose(units: Unit[] | null = null) { missOnPurpose(units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.missOnPurpose()); units.forEach((unit: Unit) => unit.missOnPurpose());
@@ -941,14 +920,19 @@ export class UnitsManager {
* @param latlng Point where to land * @param latlng Point where to land
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/ */
landAtPoint(latlng: LatLng, units: Unit[] | null = null) { landAtPoint(latlng: LatLng, mantainRelativePosition: boolean, rotation: number = 0, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.landAtPoint(latlng)); /* Compute the target for each unit. If mantainRelativePosition is true, compute the target so to hold the relative positions */
var unitTargets: { [key: number]: LatLng } = {};
if (mantainRelativePosition) unitTargets = this.computeGroupDestination(latlng, rotation);
else
units.forEach((unit: Unit) => {
unitTargets[unit.ID] = latlng;
});
units.forEach((unit: Unit) => unit.landAtPoint(unitTargets[unit.ID]));
this.#showActionMessage(units, `unit landing at point`); this.#showActionMessage(units, `unit landing at point`);
}; };
@@ -964,9 +948,7 @@ export class UnitsManager {
*/ */
setShotsScatter(shotsScatter: number, units: Unit[] | null = null) { setShotsScatter(shotsScatter: number, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.setShotsScatter(shotsScatter)); units.forEach((unit: Unit) => unit.setShotsScatter(shotsScatter));
@@ -985,9 +967,7 @@ export class UnitsManager {
*/ */
setShotsIntensity(shotsIntensity: number, units: Unit[] | null = null) { setShotsIntensity(shotsIntensity: number, units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
units.forEach((unit: Unit) => unit.setShotsIntensity(shotsIntensity)); units.forEach((unit: Unit) => unit.setShotsIntensity(shotsIntensity));
@@ -1022,9 +1002,7 @@ export class UnitsManager {
*/ */
createGroup(units: Unit[] | null = null) { createGroup(units: Unit[] | null = null) {
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
let callback = (units) => { let callback = (units) => {
if (this.getUnitsCategories(units).length == 1) { if (this.getUnitsCategories(units).length == 1) {
@@ -1033,14 +1011,14 @@ export class UnitsManager {
getApp().getServerManager().cloneUnits(unitsData, true, 0 /* No spawn points, we delete the original units */); getApp().getServerManager().cloneUnits(unitsData, true, 0 /* No spawn points, we delete the original units */);
this.#showActionMessage(units, `created a group`); this.#showActionMessage(units, `created a group`);
} else { } else {
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Groups can only be created from units of the same category`); getApp().addInfoMessage(`Groups can only be created from units of the same category`);
} }
if (getApp().getMap().getOptions().protectDCSUnits && !units.every((unit) => unit.isControlledByOlympus())) {
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.PROTECTION);
this.#protectionCallback = callback;
} else callback(units);
}; };
if (getApp().getMap().getOptions().protectDCSUnits && !units.every((unit) => unit.isControlledByOlympus())) {
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.PROTECTION);
this.#protectionCallback = callback;
} else callback(units);
} }
/** Set the hotgroup for the selected units. It will be the only hotgroup of the unit /** Set the hotgroup for the selected units. It will be the only hotgroup of the unit
@@ -1080,11 +1058,10 @@ export class UnitsManager {
this.#showActionMessage(units as Unit[], `deleted`); this.#showActionMessage(units as Unit[], `deleted`);
}; };
if ((getApp().getMap().getOptions().protectDCSUnits && !units.every((unit) => unit.isControlledByOlympus())) || units.find((unit) => unit.getHuman())){ if ((getApp().getMap().getOptions().protectDCSUnits && !units.every((unit) => unit.isControlledByOlympus())) || units.find((unit) => unit.getHuman())) {
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.PROTECTION); getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.PROTECTION);
this.#protectionCallback = callback; this.#protectionCallback = callback;
} } else callback(units);
else callback(units);
} }
/** Compute the destinations of every unit in the selected units. This function preserves the relative positions of the units, and rotates the whole formation by rotation. /** Compute the destinations of every unit in the selected units. This function preserves the relative positions of the units, and rotates the whole formation by rotation.
@@ -1098,9 +1075,7 @@ export class UnitsManager {
// TODO handle protected units // TODO handle protected units
if (units === null) units = this.getSelectedUnits(); if (units === null) units = this.getSelectedUnits();
units = units.filter((unit) => { units = units.filter((unit) => !unit.getHuman());
return !unit.getHuman();
});
if (units.length === 0) return {}; if (units.length === 0) return {};
@@ -1159,7 +1134,7 @@ export class UnitsManager {
}) })
) )
); /* Can be applied to humans too */ ); /* Can be applied to humans too */
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${this.#copiedUnits.length} units copied`); getApp().addInfoMessage(`${this.#copiedUnits.length} units copied`);
} }
/*********************** Unit manipulation functions ************************/ /*********************** Unit manipulation functions ************************/
@@ -1173,17 +1148,17 @@ export class UnitsManager {
/* If spawns are restricted, check that the user has the necessary spawn points */ /* If spawns are restricted, check that the user has the necessary spawn points */
if (getApp().getMissionManager().getCommandModeOptions().commandMode != GAME_MASTER) { if (getApp().getMissionManager().getCommandModeOptions().commandMode != GAME_MASTER) {
if (getApp().getMissionManager().getCommandModeOptions().restrictSpawns && getApp().getMissionManager().getRemainingSetupTime() < 0) { if (getApp().getMissionManager().getCommandModeOptions().restrictSpawns && getApp().getMissionManager().getRemainingSetupTime() < 0) {
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Units can be pasted only during SETUP phase`); getApp().addInfoMessage(`Units can be pasted only during SETUP phase`);
return false; return false;
} }
this.#copiedUnits.forEach((unit: UnitData) => { this.#copiedUnits.forEach((unit: UnitData) => {
let unitSpawnPoints = getUnitDatabaseByCategory(unit.category)?.getSpawnPointsByName(unit.name); let unitSpawnPoints = this.#unitDatabase.getSpawnPointsByName(unit.name);
if (unitSpawnPoints !== undefined) spawnPoints += unitSpawnPoints; if (unitSpawnPoints !== undefined) spawnPoints += unitSpawnPoints;
}); });
if (spawnPoints > getApp().getMissionManager().getAvailableSpawnPoints()) { if (spawnPoints > getApp().getMissionManager().getAvailableSpawnPoints()) {
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Not enough spawn points available!"); getApp().addInfoMessage("Not enough spawn points available!");
return false; return false;
} }
} }
@@ -1229,9 +1204,9 @@ export class UnitsManager {
} }
}); });
} }
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${this.#copiedUnits.length} units pasted`); getApp().addInfoMessage(`${this.#copiedUnits.length} units pasted`);
} else { } else {
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("No units copied!"); getApp().addInfoMessage("No units copied!");
} }
} }
@@ -1284,14 +1259,14 @@ export class UnitsManager {
/* Get a random blueprint depending on the selected parameters and spawn the unit */ /* Get a random blueprint depending on the selected parameters and spawn the unit */
let unitBlueprint: UnitBlueprint | null; let unitBlueprint: UnitBlueprint | null;
if (forceCoalition) if (forceCoalition)
unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { unitBlueprint = this.#unitDatabase.getRandomUnit({
type: type, type: type,
eras: activeEras, eras: activeEras,
ranges: activeRanges, ranges: activeRanges,
coalition: coalitionArea.getCoalition(), coalition: coalitionArea.getCoalition(),
}); });
else else
unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { unitBlueprint = this.#unitDatabase.getRandomUnit({
type: type, type: type,
eras: activeEras, eras: activeEras,
ranges: activeRanges, ranges: activeRanges,
@@ -1337,14 +1312,14 @@ export class UnitsManager {
/* Get a random blueprint depending on the selected parameters and spawn the unit */ /* Get a random blueprint depending on the selected parameters and spawn the unit */
let unitBlueprint: UnitBlueprint | null; let unitBlueprint: UnitBlueprint | null;
if (forceCoalition) if (forceCoalition)
unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { unitBlueprint = this.#unitDatabase.getRandomUnit({
type: type, type: type,
eras: activeEras, eras: activeEras,
ranges: activeRanges, ranges: activeRanges,
coalition: coalitionArea.getCoalition(), coalition: coalitionArea.getCoalition(),
}); });
else else
unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { unitBlueprint = this.#unitDatabase.getRandomUnit({
type: type, type: type,
eras: activeEras, eras: activeEras,
ranges: activeRanges, ranges: activeRanges,
@@ -1418,38 +1393,38 @@ export class UnitsManager {
if (category === "aircraft") { if (category === "aircraft") {
if (airbase == "" && spawnsRestricted) { if (airbase == "" && spawnsRestricted) {
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Aircrafts can be air spawned during the SETUP phase only"); getApp().addInfoMessage("Aircrafts can be air spawned during the SETUP phase only");
return false; return false;
} }
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {
return points + this.getDatabase().getSpawnPointsByName(unit.unitType) return points + this.getDatabase().getSpawnPointsByName(unit.unitType);
}, 0); }, 0);
spawnFunction = () => getApp().getServerManager().spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints, callback); spawnFunction = () => getApp().getServerManager().spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints, callback);
} else if (category === "helicopter") { } else if (category === "helicopter") {
if (airbase == "" && spawnsRestricted) { if (airbase == "" && spawnsRestricted) {
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Helicopters can be air spawned during the SETUP phase only"); getApp().addInfoMessage("Helicopters can be air spawned during the SETUP phase only");
return false; return false;
} }
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {
return points + this.getDatabase().getSpawnPointsByName(unit.unitType) return points + this.getDatabase().getSpawnPointsByName(unit.unitType);
}, 0); }, 0);
spawnFunction = () => getApp().getServerManager().spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints, callback); spawnFunction = () => getApp().getServerManager().spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints, callback);
} else if (category === "groundunit") { } else if (category === "groundunit") {
if (spawnsRestricted) { if (spawnsRestricted) {
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Ground units can be spawned during the SETUP phase only"); getApp().addInfoMessage("Ground units can be spawned during the SETUP phase only");
return false; return false;
} }
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {
return points + this.getDatabase().getSpawnPointsByName(unit.unitType) return points + this.getDatabase().getSpawnPointsByName(unit.unitType);
}, 0); }, 0);
spawnFunction = () => getApp().getServerManager().spawnGroundUnits(units, coalition, country, immediate, spawnPoints, callback); spawnFunction = () => getApp().getServerManager().spawnGroundUnits(units, coalition, country, immediate, spawnPoints, callback);
} else if (category === "navyunit") { } else if (category === "navyunit") {
if (spawnsRestricted) { if (spawnsRestricted) {
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Navy units can be spawned during the SETUP phase only"); getApp().addInfoMessage("Navy units can be spawned during the SETUP phase only");
return false; return false;
} }
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {
return points + this.getDatabase().getSpawnPointsByName(unit.unitType) return points + this.getDatabase().getSpawnPointsByName(unit.unitType);
}, 0); }, 0);
spawnFunction = () => getApp().getServerManager().spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback); spawnFunction = () => getApp().getServerManager().spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback);
} }
@@ -1459,7 +1434,7 @@ export class UnitsManager {
spawnFunction(); spawnFunction();
return true; return true;
} else { } else {
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Not enough spawn points available!"); getApp().addInfoMessage("Not enough spawn points available!");
return false; return false;
} }
} }
@@ -1527,10 +1502,8 @@ export class UnitsManager {
} }
#showActionMessage(units: Unit[], message: string) { #showActionMessage(units: Unit[], message: string) {
//if (units.length == 1) if (units.length == 1) getApp().addInfoMessage(`${units[0].getUnitName()} ${message}`);
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${units[0].getUnitName()} ${message}`); else if (units.length > 1) getApp().addInfoMessage(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`);
//else if (units.length > 1)
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`);
} }
#showSlowDeleteDialog(units: Unit[]) { #showSlowDeleteDialog(units: Unit[]) {
@@ -1562,15 +1535,15 @@ export class UnitsManager {
} }
#showNumberOfSelectedProtectedUnits() { #showNumberOfSelectedProtectedUnits() {
const map = getApp().getMap();
const units = this.getSelectedUnits(); const units = this.getSelectedUnits();
const numSelectedUnits = units.length; const numSelectedUnits = units.length;
//const numProtectedUnits = units.filter((unit: Unit) => map.getIsUnitProtected(unit)).length; const numProtectedUnits = units.filter((unit: Unit) => !unit.isControlledByOlympus() && !unit.getHuman()).length;
const numHumanUnits = units.filter((unit: Unit) => unit.getHuman()).length;
//if (numProtectedUnits === 1 && numSelectedUnits === numProtectedUnits) if (getApp().getMap().getOptions().protectDCSUnits && numProtectedUnits === 1 && numSelectedUnits === numProtectedUnits)
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Notice: unit is protected`); getApp().addInfoMessage(`Notice: unit is protected`);
if (getApp().getMap().getOptions().protectDCSUnits && numProtectedUnits > 1)
//if (numProtectedUnits > 1) getApp().addInfoMessage(`Notice: selection contains ${numProtectedUnits} protected units.`);
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Notice: selection contains ${numProtectedUnits} protected units.`); if (numHumanUnits) getApp().addInfoMessage(`Notice: selection contains ${numHumanUnits} human units.`);
} }
} }