mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
feat: implemented map toolbar
This commit is contained in:
@@ -3,6 +3,7 @@ import { Coalition, MapOptions } from "../types/types";
|
|||||||
import { CommandModeOptions } from "../interfaces";
|
import { CommandModeOptions } from "../interfaces";
|
||||||
import { ContextAction } from "../unit/contextaction";
|
import { ContextAction } from "../unit/contextaction";
|
||||||
import {
|
import {
|
||||||
|
faClone,
|
||||||
faExplosion,
|
faExplosion,
|
||||||
faHand,
|
faHand,
|
||||||
faLocationCrosshairs,
|
faLocationCrosshairs,
|
||||||
@@ -264,12 +265,12 @@ export const mapBounds = {
|
|||||||
|
|
||||||
export const defaultMapMirrors = {};
|
export const defaultMapMirrors = {};
|
||||||
export const defaultMapLayers = {
|
export const defaultMapLayers = {
|
||||||
"AWACS": {
|
AWACS: {
|
||||||
"urlTemplate": 'https://abcd.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png',
|
urlTemplate: "https://abcd.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png",
|
||||||
"minZoom": 1,
|
minZoom: 1,
|
||||||
"maxZoom": 19,
|
maxZoom: 19,
|
||||||
"attribution": `© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'`
|
attribution: `© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum OlympusState {
|
export enum OlympusState {
|
||||||
@@ -286,7 +287,7 @@ export enum OlympusState {
|
|||||||
OPTIONS = "Options",
|
OPTIONS = "Options",
|
||||||
AUDIO = "Audio",
|
AUDIO = "Audio",
|
||||||
AIRBASE = "Airbase",
|
AIRBASE = "Airbase",
|
||||||
GAME_MASTER = "Game master"
|
GAME_MASTER = "Game master",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NO_SUBSTATE = "No substate";
|
export const NO_SUBSTATE = "No substate";
|
||||||
@@ -304,7 +305,7 @@ export enum LoginSubState {
|
|||||||
NO_SUBSTATE = "No substate",
|
NO_SUBSTATE = "No substate",
|
||||||
CREDENTIALS = "Credentials",
|
CREDENTIALS = "Credentials",
|
||||||
COMMAND_MODE = "Command mode",
|
COMMAND_MODE = "Command mode",
|
||||||
CONNECT = "Connect"
|
CONNECT = "Connect",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DrawSubState {
|
export enum DrawSubState {
|
||||||
@@ -359,7 +360,7 @@ export const MAP_OPTIONS_DEFAULTS: MapOptions = {
|
|||||||
cameraPluginMode: "map",
|
cameraPluginMode: "map",
|
||||||
tabletMode: false,
|
tabletMode: false,
|
||||||
AWACSMode: false,
|
AWACSMode: false,
|
||||||
AWACSCoalition: "blue"
|
AWACSCoalition: "blue",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MAP_HIDDEN_TYPES_DEFAULTS = {
|
export const MAP_HIDDEN_TYPES_DEFAULTS = {
|
||||||
@@ -602,9 +603,7 @@ export namespace ContextActions {
|
|||||||
ContextActionTarget.POINT,
|
ContextActionTarget.POINT,
|
||||||
(units: Unit[], _, targetPosition: LatLng | null) => {
|
(units: Unit[], _, targetPosition: LatLng | null) => {
|
||||||
if (targetPosition)
|
if (targetPosition)
|
||||||
getApp()
|
getApp().getUnitsManager().bombPoint(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
||||||
.getUnitsManager()
|
|
||||||
.bombPoint(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
|
||||||
},
|
},
|
||||||
{ type: ContextActionType.ENGAGE, code: "KeyB", ctrlKey: false, shiftKey: false }
|
{ type: ContextActionType.ENGAGE, code: "KeyB", ctrlKey: false, shiftKey: false }
|
||||||
);
|
);
|
||||||
@@ -617,11 +616,9 @@ export namespace ContextActions {
|
|||||||
ContextActionTarget.POINT,
|
ContextActionTarget.POINT,
|
||||||
(units: Unit[], _, targetPosition: LatLng | null) => {
|
(units: Unit[], _, targetPosition: LatLng | null) => {
|
||||||
if (targetPosition)
|
if (targetPosition)
|
||||||
getApp()
|
getApp().getUnitsManager().carpetBomb(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
||||||
.getUnitsManager()
|
|
||||||
.carpetBomb(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
|
||||||
},
|
},
|
||||||
{ type: ContextActionType.ENGAGE, code: "KeyC", ctrlKey: false, shiftKey: false }
|
{ type: ContextActionType.ENGAGE, code: "KeyH", ctrlKey: false, shiftKey: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
export const LAND = new ContextAction(
|
export const LAND = new ContextAction(
|
||||||
@@ -644,9 +641,7 @@ export namespace ContextActions {
|
|||||||
ContextActionTarget.POINT,
|
ContextActionTarget.POINT,
|
||||||
(units: Unit[], _, targetPosition: LatLng | null) => {
|
(units: Unit[], _, targetPosition: LatLng | null) => {
|
||||||
if (targetPosition)
|
if (targetPosition)
|
||||||
getApp()
|
getApp().getUnitsManager().landAtPoint(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
||||||
.getUnitsManager()
|
|
||||||
.landAtPoint(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
|
||||||
},
|
},
|
||||||
{ type: ContextActionType.ADMIN, code: "KeyK", ctrlKey: false, shiftKey: false }
|
{ type: ContextActionType.ADMIN, code: "KeyK", ctrlKey: false, shiftKey: false }
|
||||||
);
|
);
|
||||||
@@ -660,7 +655,7 @@ export namespace ContextActions {
|
|||||||
(units: Unit[], _1, _2) => {
|
(units: Unit[], _1, _2) => {
|
||||||
getApp().getUnitsManager().createGroup(units);
|
getApp().getUnitsManager().createGroup(units);
|
||||||
},
|
},
|
||||||
{ type: ContextActionType.OTHER, code: "KeyG", ctrlKey: false, shiftKey: false, altKey: false }
|
{ type: ContextActionType.OTHER, code: "KeyG", ctrlKey: false, shiftKey: false, altKey: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ATTACK = new ContextAction(
|
export const ATTACK = new ContextAction(
|
||||||
@@ -683,9 +678,7 @@ export namespace ContextActions {
|
|||||||
ContextActionTarget.POINT,
|
ContextActionTarget.POINT,
|
||||||
(units: Unit[], _, targetPosition: LatLng | null) => {
|
(units: Unit[], _, targetPosition: LatLng | null) => {
|
||||||
if (targetPosition)
|
if (targetPosition)
|
||||||
getApp()
|
getApp().getUnitsManager().fireAtArea(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
||||||
.getUnitsManager()
|
|
||||||
.fireAtArea(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
|
||||||
},
|
},
|
||||||
{ type: ContextActionType.ENGAGE, code: "KeyV", ctrlKey: false, shiftKey: false }
|
{ type: ContextActionType.ENGAGE, code: "KeyV", ctrlKey: false, shiftKey: false }
|
||||||
);
|
);
|
||||||
@@ -714,6 +707,19 @@ export namespace ContextActions {
|
|||||||
(units: Unit[], _1, _2) => {
|
(units: Unit[], _1, _2) => {
|
||||||
getApp().getUnitsManager().setAWACSReference(units[0].ID);
|
getApp().getUnitsManager().setAWACSReference(units[0].ID);
|
||||||
},
|
},
|
||||||
{ type: ContextActionType.ADMIN, code: "KeyU", ctrlKey: false, shiftKey: false, altKey: false }
|
{ type: ContextActionType.ADMIN, code: "KeyU", ctrlKey: false, shiftKey: false, altKey: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CLONE = new ContextAction(
|
||||||
|
"clone",
|
||||||
|
"Clone unit",
|
||||||
|
"Clone the unit at the given location",
|
||||||
|
faClone,
|
||||||
|
ContextActionTarget.POINT,
|
||||||
|
(units: Unit[], _1, targetPosition) => {
|
||||||
|
getApp().getUnitsManager().copy(units);
|
||||||
|
if (targetPosition) getApp().getUnitsManager().paste(targetPosition);
|
||||||
|
},
|
||||||
|
{ type: ContextActionType.ADMIN, code: "KeyC", ctrlKey: false, shiftKey: false, altKey: false }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AudioSink } from "./audio/audiosink";
|
import { AudioSink } from "./audio/audiosink";
|
||||||
import { AudioSource } from "./audio/audiosource";
|
import { AudioSource } from "./audio/audiosource";
|
||||||
import { OlympusState, OlympusSubState } from "./constants/constants";
|
import { OlympusState, OlympusSubState } from "./constants/constants";
|
||||||
import { CommandModeOptions, OlympusConfig, ServerStatus, SessionData, SpawnRequestTable } from "./interfaces";
|
import { CommandModeOptions, OlympusConfig, ServerStatus, SessionData, SpawnRequestTable, UnitData } from "./interfaces";
|
||||||
import { CoalitionCircle } from "./map/coalitionarea/coalitioncircle";
|
import { CoalitionCircle } from "./map/coalitionarea/coalitioncircle";
|
||||||
import { CoalitionPolygon } from "./map/coalitionarea/coalitionpolygon";
|
import { CoalitionPolygon } from "./map/coalitionarea/coalitionpolygon";
|
||||||
import { Airbase } from "./mission/airbase";
|
import { Airbase } from "./mission/airbase";
|
||||||
@@ -242,6 +242,32 @@ export class AirbaseSelectedEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SelectionEnabledChangedEvent {
|
||||||
|
static on(callback: (enabled: boolean) => void) {
|
||||||
|
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||||
|
callback(ev.detail.enabled);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static dispatch(enabled: boolean) {
|
||||||
|
document.dispatchEvent(new CustomEvent(this.name, { detail: { enabled } }));
|
||||||
|
console.log(`Event ${this.name} dispatched`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export class PasteEnabledChangedEvent {
|
||||||
|
static on(callback: (enabled: boolean) => void) {
|
||||||
|
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||||
|
callback(ev.detail.enabled);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static dispatch(enabled: boolean) {
|
||||||
|
document.dispatchEvent(new CustomEvent(this.name, { detail: { enabled } }));
|
||||||
|
console.log(`Event ${this.name} dispatched`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export class ContactsUpdatedEvent {
|
export class ContactsUpdatedEvent {
|
||||||
static on(callback: () => void) {
|
static on(callback: () => void) {
|
||||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||||
@@ -281,6 +307,19 @@ export class ContextActionChangedEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CopiedUnitsEvents {
|
||||||
|
static on(callback: (unitsData: UnitData[]) => void) {
|
||||||
|
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||||
|
callback(ev.detail.unitsData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static dispatch(unitsData: UnitData[]) {
|
||||||
|
document.dispatchEvent(new CustomEvent(this.name, { detail: { unitsData } }));
|
||||||
|
console.log(`Event ${this.name} dispatched`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class UnitUpdatedEvent extends BaseUnitEvent {
|
export class UnitUpdatedEvent extends BaseUnitEvent {
|
||||||
static dispatch(unit: Unit) {
|
static dispatch(unit: Unit) {
|
||||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { unit } }));
|
document.dispatchEvent(new CustomEvent(this.name, { detail: { unit } }));
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export var BoxSelect = Handler.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onMouseDown: function (e: any) {
|
_onMouseDown: function (e: any) {
|
||||||
if (this._map.getEnableSelection() && e.button == 0) {
|
if (this._map.getSelectionEnabled() && e.button == 0) {
|
||||||
// Clear the deferred resetState if it hasn't executed yet, otherwise it
|
// Clear the deferred resetState if it hasn't executed yet, otherwise it
|
||||||
// will interrupt the interaction and orphan a box element in the container.
|
// will interrupt the interaction and orphan a box element in the container.
|
||||||
this._clearDeferredResetState();
|
this._clearDeferredResetState();
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ import {
|
|||||||
MapOptionsChangedEvent,
|
MapOptionsChangedEvent,
|
||||||
MapSourceChangedEvent,
|
MapSourceChangedEvent,
|
||||||
MouseMovedEvent,
|
MouseMovedEvent,
|
||||||
|
PasteEnabledChangedEvent,
|
||||||
SelectionClearedEvent,
|
SelectionClearedEvent,
|
||||||
|
SelectionEnabledChangedEvent,
|
||||||
SpawnContextMenuRequestEvent,
|
SpawnContextMenuRequestEvent,
|
||||||
StarredSpawnsChangedEvent,
|
StarredSpawnsChangedEvent,
|
||||||
UnitDeselectedEvent,
|
UnitDeselectedEvent,
|
||||||
@@ -113,7 +115,8 @@ export class Map extends L.Map {
|
|||||||
#lastMouseCoordinates: L.LatLng = new L.LatLng(0, 0);
|
#lastMouseCoordinates: L.LatLng = new L.LatLng(0, 0);
|
||||||
#previousZoom: number = 0;
|
#previousZoom: number = 0;
|
||||||
#keepRelativePositions: boolean = false;
|
#keepRelativePositions: boolean = false;
|
||||||
#enableSelection: boolean = false;
|
#selectionEnabled: boolean = false;
|
||||||
|
#pasteEnabled: boolean = false;
|
||||||
|
|
||||||
/* Camera control plugin */
|
/* Camera control plugin */
|
||||||
#slaveDCSCamera: boolean = false;
|
#slaveDCSCamera: boolean = false;
|
||||||
@@ -363,10 +366,10 @@ export class Map extends L.Map {
|
|||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
ctrlKey: false,
|
ctrlKey: false,
|
||||||
})
|
})
|
||||||
.addShortcut("toggleEnableSelection", {
|
.addShortcut("toggleSelectionEnabled", {
|
||||||
label: "Toggle box selection",
|
label: "Toggle box selection",
|
||||||
keyUpCallback: () => this.setEnableSelection(false),
|
keyUpCallback: () => this.setSelectionEnabled(false),
|
||||||
keyDownCallback: () => this.setEnableSelection(true),
|
keyDownCallback: () => this.setSelectionEnabled(true),
|
||||||
code: "ShiftLeft",
|
code: "ShiftLeft",
|
||||||
altKey: false,
|
altKey: false,
|
||||||
ctrlKey: false,
|
ctrlKey: false,
|
||||||
@@ -755,12 +758,22 @@ export class Map extends L.Map {
|
|||||||
return this.#keepRelativePositions;
|
return this.#keepRelativePositions;
|
||||||
}
|
}
|
||||||
|
|
||||||
setEnableSelection(enableSelection: boolean) {
|
setSelectionEnabled(selectionEnabled: boolean) {
|
||||||
this.#enableSelection = enableSelection;
|
this.#selectionEnabled = selectionEnabled;
|
||||||
|
SelectionEnabledChangedEvent.dispatch(selectionEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
getEnableSelection() {
|
getSelectionEnabled() {
|
||||||
return this.#enableSelection;
|
return this.#selectionEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPasteEnabled(pasteEnabled: boolean) {
|
||||||
|
this.#pasteEnabled = pasteEnabled;
|
||||||
|
PasteEnabledChangedEvent.dispatch(pasteEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
getPasteEnabled() {
|
||||||
|
return this.#pasteEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
increaseCameraZoom() {
|
increaseCameraZoom() {
|
||||||
@@ -805,8 +818,12 @@ export class Map extends L.Map {
|
|||||||
this.#currentSpawnMarker = null;
|
this.#currentSpawnMarker = null;
|
||||||
this.#currentEffectMarker?.removeFrom(this);
|
this.#currentEffectMarker?.removeFrom(this);
|
||||||
this.#currentEffectMarker = null;
|
this.#currentEffectMarker = null;
|
||||||
this.setContextAction(null);
|
|
||||||
if (state !== OlympusState.UNIT_CONTROL) getApp().getUnitsManager().deselectAllUnits();
|
if (state !== OlympusState.UNIT_CONTROL) {
|
||||||
|
getApp().getUnitsManager().deselectAllUnits();
|
||||||
|
this.setContextAction(null);
|
||||||
|
this.setContextActionSet(null);
|
||||||
|
}
|
||||||
if (state !== OlympusState.DRAW || (state === OlympusState.DRAW && subState !== DrawSubState.EDIT)) this.deselectAllCoalitionAreas();
|
if (state !== OlympusState.DRAW || (state === OlympusState.DRAW && subState !== DrawSubState.EDIT)) this.deselectAllCoalitionAreas();
|
||||||
this.getContainer().classList.remove(`explosion-cursor`);
|
this.getContainer().classList.remove(`explosion-cursor`);
|
||||||
["white", "blue", "red", "green", "orange"].forEach((color) => this.getContainer().classList.remove(`smoke-${color}-cursor`));
|
["white", "blue", "red", "green", "orange"].forEach((color) => this.getContainer().classList.remove(`smoke-${color}-cursor`));
|
||||||
@@ -908,6 +925,10 @@ export class Map extends L.Map {
|
|||||||
if (!this.#isSelecting) {
|
if (!this.#isSelecting) {
|
||||||
console.log(`Left short click at ${e.latlng}`);
|
console.log(`Left short click at ${e.latlng}`);
|
||||||
|
|
||||||
|
if (this.#pasteEnabled) {
|
||||||
|
getApp().getUnitsManager().paste(e.latlng)
|
||||||
|
}
|
||||||
|
|
||||||
/* Execute the short click action */
|
/* Execute the short click action */
|
||||||
if (getApp().getState() === OlympusState.IDLE) {
|
if (getApp().getState() === OlympusState.IDLE) {
|
||||||
/* Do nothing */
|
/* Do nothing */
|
||||||
@@ -1023,6 +1044,10 @@ export class Map extends L.Map {
|
|||||||
}
|
}
|
||||||
getApp().setState(OlympusState.JTAC);
|
getApp().setState(OlympusState.JTAC);
|
||||||
this.#drawIPToTargetLine();
|
this.#drawIPToTargetLine();
|
||||||
|
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
|
||||||
|
if (this.#contextAction !== null) this.executeContextAction(null, e.latlng, e.originalEvent);
|
||||||
|
else if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE);
|
||||||
|
else getApp().setState(OlympusState.UNIT_CONTROL);
|
||||||
} else {
|
} else {
|
||||||
if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE);
|
if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE);
|
||||||
else getApp().setState(OlympusState.UNIT_CONTROL);
|
else getApp().setState(OlympusState.UNIT_CONTROL);
|
||||||
@@ -1041,8 +1066,7 @@ export class Map extends L.Map {
|
|||||||
SpawnContextMenuRequestEvent.dispatch(e.latlng);
|
SpawnContextMenuRequestEvent.dispatch(e.latlng);
|
||||||
getApp().setState(OlympusState.SPAWN_CONTEXT);
|
getApp().setState(OlympusState.SPAWN_CONTEXT);
|
||||||
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
|
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
|
||||||
if (this.#contextAction !== null) this.executeContextAction(null, e.latlng, e.originalEvent);
|
this.executeDefaultContextAction(null, e.latlng, e.originalEvent);
|
||||||
else this.executeDefaultContextAction(null, e.latlng, e.originalEvent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1062,6 +1086,8 @@ export class Map extends L.Map {
|
|||||||
|
|
||||||
if (this.#debounceTimeout) window.clearTimeout(this.#debounceTimeout);
|
if (this.#debounceTimeout) window.clearTimeout(this.#debounceTimeout);
|
||||||
|
|
||||||
|
this.setPasteEnabled(false);
|
||||||
|
|
||||||
if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE);
|
if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE);
|
||||||
else getApp().setState(getApp().getState());
|
else getApp().setState(getApp().getState());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,9 +172,10 @@
|
|||||||
|
|
||||||
/*** Cursors ***/
|
/*** Cursors ***/
|
||||||
[data-is-dead],
|
[data-is-dead],
|
||||||
[data-object|="unit-missile"],
|
[data-object|="unit-missile"] *,
|
||||||
[data-object|="unit-bomb"] {
|
[data-object|="unit-bomb"] *{
|
||||||
cursor: default;
|
pointer-events: none;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Labels ***/
|
/*** Labels ***/
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ export function OlStateButton(props: {
|
|||||||
buttonColor?: string | null;
|
buttonColor?: string | null;
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
icon?: IconProp;
|
icon?: IconProp;
|
||||||
tooltip: string;
|
tooltip?: string | JSX.Element | JSX.Element[];
|
||||||
|
tooltipPosition?: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
onMouseUp?: () => void;
|
onMouseUp?: () => void;
|
||||||
onMouseDown?: () => void;
|
onMouseDown?: () => void;
|
||||||
@@ -21,7 +22,8 @@ export function OlStateButton(props: {
|
|||||||
const className =
|
const className =
|
||||||
(props.className ?? "") +
|
(props.className ?? "") +
|
||||||
`
|
`
|
||||||
h-[40px] w-[40px] flex-none rounded-md text-lg font-medium
|
pointer-events-auto h-[40px] w-[40px] flex-none rounded-md text-lg
|
||||||
|
font-medium
|
||||||
dark:bg-olympus-600 dark:text-gray-300
|
dark:bg-olympus-600 dark:text-gray-300
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -57,12 +59,12 @@ export function OlStateButton(props: {
|
|||||||
setHover(false);
|
setHover(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<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`}>
|
||||||
{props.icon && <FontAwesomeIcon icon={props.icon} className="m-auto" style={{ color: textColor }} />}
|
{props.icon && <FontAwesomeIcon icon={props.icon} className="m-auto" style={{ color: textColor }} />}
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} />}
|
{hover && props.tooltip && <OlTooltip buttonRef={buttonRef} content={props.tooltip} position={props.tooltipPosition}/>}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
export function OlTooltip(props: { content: string; buttonRef: React.MutableRefObject<null> }) {
|
export function OlTooltip(props: { content: string | JSX.Element | JSX.Element[]; buttonRef: React.MutableRefObject<null>; position?: string }) {
|
||||||
var contentRef = useRef(null);
|
var contentRef = useRef(null);
|
||||||
|
|
||||||
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
|
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
|
||||||
@@ -13,18 +13,18 @@ export function OlTooltip(props: { content: string; buttonRef: React.MutableRefO
|
|||||||
let [cxl, cyt, cxr, cyb, cw, ch] = [
|
let [cxl, cyt, cxr, cyb, cw, ch] = [
|
||||||
content.getBoundingClientRect().x,
|
content.getBoundingClientRect().x,
|
||||||
content.getBoundingClientRect().y,
|
content.getBoundingClientRect().y,
|
||||||
content.getBoundingClientRect().x + content.clientWidth,
|
content.getBoundingClientRect().x + content.offsetWidth,
|
||||||
content.getBoundingClientRect().y + content.clientHeight,
|
content.getBoundingClientRect().y + content.offsetHeight,
|
||||||
content.clientWidth,
|
content.offsetWidth,
|
||||||
content.clientHeight,
|
content.offsetHeight,
|
||||||
];
|
];
|
||||||
let [bxl, byt, bxr, byb, bbw, bh] = [
|
let [bxl, byt, bxr, byb, bbw, bh] = [
|
||||||
button.getBoundingClientRect().x,
|
button.getBoundingClientRect().x,
|
||||||
button.getBoundingClientRect().y,
|
button.getBoundingClientRect().y,
|
||||||
button.getBoundingClientRect().x + button.clientWidth,
|
button.getBoundingClientRect().x + button.offsetWidth,
|
||||||
button.getBoundingClientRect().y + button.clientHeight,
|
button.getBoundingClientRect().y + button.offsetHeight,
|
||||||
button.clientWidth,
|
button.offsetWidth,
|
||||||
button.clientHeight,
|
button.offsetHeight,
|
||||||
];
|
];
|
||||||
|
|
||||||
/* Limit the maximum height */
|
/* Limit the maximum height */
|
||||||
@@ -37,19 +37,29 @@ export function OlTooltip(props: { content: string; buttonRef: React.MutableRefO
|
|||||||
var cxc = (cxl + cxr) / 2;
|
var cxc = (cxl + cxr) / 2;
|
||||||
var bxc = (bxl + bxr) / 2;
|
var bxc = (bxl + bxr) / 2;
|
||||||
|
|
||||||
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */
|
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content depending on the requested position */
|
||||||
var offsetX = bxc - cxc;
|
var offsetX = 0;
|
||||||
var offsetY = byb - cyt + 8;
|
var offsetY = 0;
|
||||||
|
|
||||||
|
if (props.position === undefined || props.position === "below") {
|
||||||
|
offsetX = bxc - cxc;
|
||||||
|
offsetY = byb - cyt + 8;
|
||||||
|
} else if (props.position === "side") {
|
||||||
|
offsetX = bxr + 8;
|
||||||
|
offsetY = byt - cyt + (bh - ch) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
/* Compute the new position of the left and right margins of the content */
|
/* Compute the new position of the left and right margins of the content */
|
||||||
cxl += offsetX;
|
let ncxl = cxl + offsetX;
|
||||||
cxr += offsetX;
|
let ncxr = cxr + offsetX;
|
||||||
cyb += offsetY;
|
let ncyb = cyb + offsetY;
|
||||||
|
|
||||||
/* Try and move the content so it is inside the screen */
|
/* Try and move the content so it is inside the screen */
|
||||||
if (cxl < 0) offsetX -= cxl;
|
if (ncxl < 0) offsetX -= cxl;
|
||||||
if (cxr > window.innerWidth) offsetX -= cxr - window.innerWidth;
|
if (ncxr > window.innerWidth) {
|
||||||
if (cyb > window.innerHeight) offsetY -= bh + ch + 16;
|
offsetX = bxl - cxl - cw - 12;
|
||||||
|
}
|
||||||
|
if (ncyb > window.innerHeight) offsetY -= bh + ch + 16;
|
||||||
|
|
||||||
/* Apply the offset */
|
/* Apply the offset */
|
||||||
content.style.left = `${offsetX}px`;
|
content.style.left = `${offsetX}px`;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, OlympusSubState, Spawn
|
|||||||
import { AppStateChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent, ShortcutsChangedEvent } from "../../events";
|
import { AppStateChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent, ShortcutsChangedEvent } from "../../events";
|
||||||
import { ContextAction } from "../../unit/contextaction";
|
import { ContextAction } from "../../unit/contextaction";
|
||||||
import { ContextActionSet } from "../../unit/contextactionset";
|
import { ContextActionSet } from "../../unit/contextactionset";
|
||||||
|
import { MapToolBar } from "./maptoolbar";
|
||||||
|
|
||||||
export function ControlsPanel(props: {}) {
|
export function ControlsPanel(props: {}) {
|
||||||
const [controls, setControls] = useState(
|
const [controls, setControls] = useState(
|
||||||
@@ -19,8 +20,8 @@ export function ControlsPanel(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 [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||||
const [shortcuts, setShortcuts] = useState({})
|
const [shortcuts, setShortcuts] = useState({});
|
||||||
const [contextActionSet, setContextActionSet] = useState(null as null | ContextActionSet)
|
const [contextActionSet, setContextActionSet] = useState(null as null | ContextActionSet);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
AppStateChangedEvent.on((state, subState) => {
|
AppStateChangedEvent.on((state, subState) => {
|
||||||
@@ -74,53 +75,28 @@ export function ControlsPanel(props: {}) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else if (appState === OlympusState.UNIT_CONTROL) {
|
} else if (appState === OlympusState.UNIT_CONTROL) {
|
||||||
if (!mapOptions.tabletMode) {
|
controls.unshift({
|
||||||
controls = Object.values(contextActionSet?.getContextActions() ?? {})
|
actions: ["RMB"],
|
||||||
.sort((a: ContextAction, b: ContextAction) => (a.getLabel() > b.getLabel() ? 1 : -1))
|
text: "Move",
|
||||||
.filter((contextAction: ContextAction) => contextAction.getOptions().code)
|
});
|
||||||
.map((contextAction: ContextAction) => {
|
controls.push({
|
||||||
let actions: (string | IconDefinition)[] = [];
|
actions: ["RMB", "Hold"],
|
||||||
contextAction.getOptions().shiftKey && actions.push("Shift");
|
target: faMap,
|
||||||
contextAction.getOptions().altKey && actions.push("Alt");
|
text: "Show point actions",
|
||||||
contextAction.getOptions().ctrlKey && actions.push("Ctrl");
|
});
|
||||||
actions.push(
|
controls.push({
|
||||||
(contextAction.getOptions().code as string)
|
actions: ["RMB", "Hold"],
|
||||||
.replace("Key", "")
|
target: faFighterJet,
|
||||||
.replace("ControlLeft", "Left Ctrl")
|
text: "Show unit actions",
|
||||||
.replace("AltLeft", "Left Alt")
|
});
|
||||||
.replace("ShiftLeft", "Left Shift")
|
controls.push({
|
||||||
.replace("ControlRight", "Right Ctrl")
|
actions: shortcuts["toggleRelativePositions"]?.toActions(),
|
||||||
.replace("AltRight", "Right Alt")
|
text: "Activate group movement",
|
||||||
.replace("ShiftRight", "Right Shift")
|
});
|
||||||
);
|
controls.push({
|
||||||
return {
|
actions: [...shortcuts["toggleRelativePositions"]?.toActions(), "Wheel"],
|
||||||
actions: actions,
|
text: "Rotate formation",
|
||||||
text: contextAction.getLabel(),
|
});
|
||||||
};
|
|
||||||
});
|
|
||||||
controls.unshift({
|
|
||||||
actions: ["RMB"],
|
|
||||||
text: "Move",
|
|
||||||
});
|
|
||||||
controls.push({
|
|
||||||
actions: ["RMB", "Hold"],
|
|
||||||
target: faMap,
|
|
||||||
text: "Show point actions",
|
|
||||||
});
|
|
||||||
controls.push({
|
|
||||||
actions: ["RMB", "Hold"],
|
|
||||||
target: faFighterJet,
|
|
||||||
text: "Show unit actions",
|
|
||||||
});
|
|
||||||
controls.push({
|
|
||||||
actions: shortcuts["toggleRelativePositions"]?.toActions(),
|
|
||||||
text: "Activate group movement",
|
|
||||||
});
|
|
||||||
controls.push({
|
|
||||||
actions: [...shortcuts["toggleRelativePositions"]?.toActions(), "Wheel"],
|
|
||||||
text: "Rotate formation",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (appState === OlympusState.SPAWN) {
|
} else if (appState === OlympusState.SPAWN) {
|
||||||
controls = [
|
controls = [
|
||||||
{
|
{
|
||||||
@@ -151,8 +127,8 @@ export function ControlsPanel(props: {}) {
|
|||||||
controls = baseControls;
|
controls = baseControls;
|
||||||
controls.push({
|
controls.push({
|
||||||
actions: ["LMB"],
|
actions: ["LMB"],
|
||||||
text: "Return to idle state"
|
text: "Return to idle state",
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setControls(controls);
|
setControls(controls);
|
||||||
@@ -163,11 +139,13 @@ export function ControlsPanel(props: {}) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
absolute right-[0px]
|
absolute right-[0px] top-16
|
||||||
${mapOptions.showMinimap ? `bottom-[233px]` : `bottom-[65px]`}
|
${mapOptions.showMinimap ? `bottom-[233px]` : `bottom-[65px]`}
|
||||||
flex w-[310px] flex-col items-center justify-between gap-1 p-3 text-sm
|
pointer-events-none flex w-[310px] flex-col items-center justify-between
|
||||||
|
gap-1 p-3 text-sm
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
|
<MapToolBar />
|
||||||
{controls?.map((control) => {
|
{controls?.map((control) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -189,9 +167,14 @@ export function ControlsPanel(props: {}) {
|
|||||||
return (
|
return (
|
||||||
<div key={idx} className="flex gap-1">
|
<div key={idx} className="flex gap-1">
|
||||||
<div>
|
<div>
|
||||||
{typeof action === "string" || typeof action === "number" ? action : <FontAwesomeIcon icon={action} className={`
|
{typeof action === "string" || typeof action === "number" ? (
|
||||||
my-auto ml-auto
|
action
|
||||||
`} />}
|
) : (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={action}
|
||||||
|
className={`my-auto ml-auto`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "string" && <div>+</div>}
|
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "string" && <div>+</div>}
|
||||||
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "number" && <div>x</div>}
|
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "number" && <div>x</div>}
|
||||||
|
|||||||
275
frontend/react/src/ui/panels/maptoolbar.tsx
Normal file
275
frontend/react/src/ui/panels/maptoolbar.tsx
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { ContextActionSet } from "../../unit/contextactionset";
|
||||||
|
import { OlStateButton } from "../components/olstatebutton";
|
||||||
|
import { getApp } from "../../olympusapp";
|
||||||
|
import { ContextAction, ContextActionOptions } from "../../unit/contextaction";
|
||||||
|
import { CONTEXT_ACTION_COLORS, ContextActionTarget, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||||
|
import { FaChevronDown, FaChevronUp } from "react-icons/fa6";
|
||||||
|
import { OlympusState } from "../../constants/constants";
|
||||||
|
import {
|
||||||
|
AppStateChangedEvent,
|
||||||
|
ContextActionChangedEvent,
|
||||||
|
ContextActionSetChangedEvent,
|
||||||
|
CopiedUnitsEvents,
|
||||||
|
MapOptionsChangedEvent,
|
||||||
|
PasteEnabledChangedEvent,
|
||||||
|
SelectedUnitsChangedEvent,
|
||||||
|
SelectionClearedEvent,
|
||||||
|
SelectionEnabledChangedEvent,
|
||||||
|
ShortcutsChangedEvent,
|
||||||
|
} from "../../events";
|
||||||
|
import { faCopy, faObjectGroup, faPaste } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { Shortcut } from "../../shortcut/shortcut";
|
||||||
|
import { ShortcutOptions, UnitData } from "../../interfaces";
|
||||||
|
import { Unit } from "../../unit/unit";
|
||||||
|
|
||||||
|
export function MapToolBar(props: {}) {
|
||||||
|
const [appState, setAppState] = useState(OlympusState.IDLE);
|
||||||
|
const [contextActionSet, setcontextActionSet] = useState(null as ContextActionSet | null);
|
||||||
|
const [contextAction, setContextAction] = useState(null as ContextAction | null);
|
||||||
|
const [scrolledTop, setScrolledTop] = useState(true);
|
||||||
|
const [scrolledBottom, setScrolledBottom] = useState(false);
|
||||||
|
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||||
|
const [selectionEnabled, setSelectionEnabled] = useState(false);
|
||||||
|
const [pasteEnabled, setPasteEnabled] = useState(false);
|
||||||
|
const [controller, setController] = useState(new AbortController());
|
||||||
|
const [shortcuts, setShortcuts] = useState(
|
||||||
|
{} as {
|
||||||
|
[key: string]: Shortcut;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||||
|
const [copiedUnitsData, setCopiedUnitsData] = useState([] as UnitData[]);
|
||||||
|
|
||||||
|
/* Initialize the "scroll" position of the element */
|
||||||
|
var scrollRef = useRef(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (scrollRef.current) onScroll(scrollRef.current);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||||
|
ContextActionSetChangedEvent.on((contextActionSet) => setcontextActionSet(contextActionSet));
|
||||||
|
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
|
||||||
|
MapOptionsChangedEvent.on((mapOptions) => setMapOptions(mapOptions));
|
||||||
|
SelectionEnabledChangedEvent.on((selectionEnabled) => setSelectionEnabled(selectionEnabled));
|
||||||
|
PasteEnabledChangedEvent.on((pasteEnabled) => setPasteEnabled(pasteEnabled));
|
||||||
|
ShortcutsChangedEvent.on((shortcuts) => setShortcuts({ ...shortcuts }));
|
||||||
|
SelectedUnitsChangedEvent.on((selectedUnits) => setSelectedUnits(selectedUnits));
|
||||||
|
SelectionClearedEvent.on(() => setSelectedUnits([]));
|
||||||
|
CopiedUnitsEvents.on((unitsData) => setCopiedUnitsData(unitsData));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function onScroll(el) {
|
||||||
|
const sl = el.scrollTop;
|
||||||
|
const sr = el.scrollHeight - el.scrollTop - el.clientHeight;
|
||||||
|
|
||||||
|
sl < 1 && !scrolledTop && setScrolledTop(true);
|
||||||
|
sl > 1 && scrolledTop && setScrolledTop(false);
|
||||||
|
|
||||||
|
sr < 1 && !scrolledBottom && setScrolledBottom(true);
|
||||||
|
sr > 1 && scrolledBottom && setScrolledBottom(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shortcutCombination(options: ShortcutOptions | ContextActionOptions) {
|
||||||
|
if (options === undefined) return <></>;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{options.ctrlKey && (
|
||||||
|
<kbd
|
||||||
|
className={`
|
||||||
|
my-auto ml-auto text-nowrap rounded-lg border border-gray-200
|
||||||
|
bg-gray-100 px-2 py-1 text-xs font-semibold text-gray-800
|
||||||
|
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Ctrl
|
||||||
|
</kbd>
|
||||||
|
)}
|
||||||
|
{options.altKey && (
|
||||||
|
<kbd
|
||||||
|
className={`
|
||||||
|
my-auto ml-auto text-nowrap rounded-lg border border-gray-200
|
||||||
|
bg-gray-100 px-2 py-1 text-xs font-semibold text-gray-800
|
||||||
|
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Alt
|
||||||
|
</kbd>
|
||||||
|
)}
|
||||||
|
{options.shiftKey && (
|
||||||
|
<kbd
|
||||||
|
className={`
|
||||||
|
my-auto ml-auto text-nowrap rounded-lg border border-gray-200
|
||||||
|
bg-gray-100 px-2 py-1 text-xs font-semibold text-gray-800
|
||||||
|
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Shift
|
||||||
|
</kbd>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{options.code && (
|
||||||
|
<kbd
|
||||||
|
className={`
|
||||||
|
my-auto ml-auto text-nowrap rounded-lg border border-gray-200
|
||||||
|
bg-gray-100 px-2 py-1 text-xs font-semibold text-gray-800
|
||||||
|
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{options.code?.replace("Key", "")}
|
||||||
|
</kbd>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let reorderedActions: ContextAction[] = contextActionSet
|
||||||
|
? Object.values(contextActionSet.getContextActions()).sort((a: ContextAction, b: ContextAction) => (a.getOptions().type < b.getOptions().type ? -1 : 1))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
relative top-0 mb-auto ml-auto flex max-h-[calc(100%-200px)] gap-2
|
||||||
|
rounded-md bg-olympus-900
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{!scrolledTop && (
|
||||||
|
<FaChevronUp
|
||||||
|
className={`
|
||||||
|
absolute top-0 h-6 w-full rounded-lg px-3.5 py-1 text-gray-200
|
||||||
|
dark:bg-olympus-900
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className={`flex flex-col gap-2 overflow-y-auto no-scrollbar p-2`} onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<OlStateButton
|
||||||
|
key={"select"}
|
||||||
|
checked={selectionEnabled}
|
||||||
|
icon={faObjectGroup}
|
||||||
|
tooltip={
|
||||||
|
<div className="flex content-center gap-2">
|
||||||
|
{shortcutCombination(shortcuts["toggleSelectionEnabled"]?.getOptions())}
|
||||||
|
<div className="my-auto">Box selection</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
tooltipPosition="side"
|
||||||
|
onClick={() => {
|
||||||
|
getApp().getMap().setSelectionEnabled(!selectionEnabled);
|
||||||
|
if (!selectionEnabled) {
|
||||||
|
getApp()
|
||||||
|
.getMap()
|
||||||
|
.getContainer()
|
||||||
|
.addEventListener(
|
||||||
|
"mouseup",
|
||||||
|
() => {
|
||||||
|
getApp().getMap().setSelectionEnabled(false);
|
||||||
|
},
|
||||||
|
{ once: true, signal: controller.signal }
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
controller.abort();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{selectedUnits.length > 0 && (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<OlStateButton
|
||||||
|
key={"copy"}
|
||||||
|
checked={false}
|
||||||
|
icon={faCopy}
|
||||||
|
tooltip={
|
||||||
|
<div className="flex content-center gap-2">
|
||||||
|
{shortcutCombination(shortcuts["copyUnits"]?.getOptions())}
|
||||||
|
<div className="my-auto">Copy selected units</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
tooltipPosition="side"
|
||||||
|
onClick={() => {
|
||||||
|
getApp().getUnitsManager().copy(selectedUnits);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{copiedUnitsData.length > 0 && (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<OlStateButton key={"paste"} checked={pasteEnabled} icon={faPaste} tooltip={
|
||||||
|
<div className="flex content-center gap-2">
|
||||||
|
{shortcutCombination(shortcuts["pasteUnits"]?.getOptions())}
|
||||||
|
<div className="my-auto">Paste copied units</div>
|
||||||
|
</div>
|
||||||
|
} tooltipPosition="side" onClick={() => {
|
||||||
|
getApp().getMap().setPasteEnabled(!pasteEnabled)
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
|
||||||
|
{reorderedActions.map((contextActionIt: ContextAction) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<OlStateButton
|
||||||
|
key={contextActionIt.getId()}
|
||||||
|
checked={contextActionIt === contextAction}
|
||||||
|
icon={contextActionIt.getIcon()}
|
||||||
|
tooltip={
|
||||||
|
<div className="flex content-center gap-2">
|
||||||
|
{shortcutCombination(contextActionIt.getOptions())}
|
||||||
|
<div className="my-auto">{contextActionIt.getLabel()}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
tooltipPosition="side"
|
||||||
|
buttonColor={CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type ?? 0]}
|
||||||
|
onClick={() => {
|
||||||
|
if (contextActionIt.getTarget() === ContextActionTarget.NONE) {
|
||||||
|
contextActionIt.executeCallback(null, null);
|
||||||
|
} else {
|
||||||
|
contextActionIt !== contextAction ? getApp().getMap().setContextAction(contextActionIt) : getApp().getMap().setContextAction(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{!scrolledBottom && (
|
||||||
|
<FaChevronDown
|
||||||
|
className={`
|
||||||
|
absolute bottom-0 h-6 w-full rounded-lg px-3.5 py-1
|
||||||
|
text-gray-200
|
||||||
|
dark:bg-olympus-900
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
|
||||||
|
{/*}
|
||||||
|
{contextAction && (
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
absolute left-[50%] top-16 flex translate-x-[calc(-50%+2rem)]
|
||||||
|
items-center gap-2 rounded-md bg-gray-200 p-4
|
||||||
|
dark:bg-olympus-800
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={contextAction.getIcon()}
|
||||||
|
className={`
|
||||||
|
mr-2 hidden text-xl text-blue-500
|
||||||
|
md:block
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
<div className={`text-gray-200`}>{contextAction.getDescription()}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{*/}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ export function MiniMapPanel(props: {}) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let miniMap = document.querySelector(".leaflet-control-minimap");
|
let miniMap = document.querySelector(".leaflet-control-minimap");
|
||||||
if (miniMap) {
|
if (miniMap) {
|
||||||
miniMap.classList.add("rounded-t-lg");
|
miniMap.classList.add("rounded-b-lg");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export function SideBar() {
|
|||||||
checked={appState === OlympusState.MAIN_MENU}
|
checked={appState === OlympusState.MAIN_MENU}
|
||||||
icon={faEllipsisV}
|
icon={faEllipsisV}
|
||||||
tooltip="Hide/show main menu"
|
tooltip="Hide/show main menu"
|
||||||
|
tooltipPosition="side"
|
||||||
></OlStateButton>
|
></OlStateButton>
|
||||||
<OlStateButton
|
<OlStateButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -40,6 +41,7 @@ export function SideBar() {
|
|||||||
checked={appState === OlympusState.SPAWN}
|
checked={appState === OlympusState.SPAWN}
|
||||||
icon={faPlusSquare}
|
icon={faPlusSquare}
|
||||||
tooltip="Hide/show unit spawn menu"
|
tooltip="Hide/show unit spawn menu"
|
||||||
|
tooltipPosition="side"
|
||||||
></OlStateButton>
|
></OlStateButton>
|
||||||
<OlStateButton
|
<OlStateButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -48,6 +50,7 @@ export function SideBar() {
|
|||||||
checked={appState === OlympusState.UNIT_CONTROL}
|
checked={appState === OlympusState.UNIT_CONTROL}
|
||||||
icon={faGamepad}
|
icon={faGamepad}
|
||||||
tooltip="Hide/show selection tool and unit control menu"
|
tooltip="Hide/show selection tool and unit control menu"
|
||||||
|
tooltipPosition="side"
|
||||||
></OlStateButton>
|
></OlStateButton>
|
||||||
<OlStateButton
|
<OlStateButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -56,6 +59,7 @@ export function SideBar() {
|
|||||||
checked={appState === OlympusState.DRAW}
|
checked={appState === OlympusState.DRAW}
|
||||||
icon={faPencil}
|
icon={faPencil}
|
||||||
tooltip="Hide/show drawing menu"
|
tooltip="Hide/show drawing menu"
|
||||||
|
tooltipPosition="side"
|
||||||
></OlStateButton>
|
></OlStateButton>
|
||||||
<OlStateButton
|
<OlStateButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -64,6 +68,7 @@ export function SideBar() {
|
|||||||
checked={appState === OlympusState.AUDIO}
|
checked={appState === OlympusState.AUDIO}
|
||||||
icon={faVolumeHigh}
|
icon={faVolumeHigh}
|
||||||
tooltip="Hide/show audio menu"
|
tooltip="Hide/show audio menu"
|
||||||
|
tooltipPosition="side"
|
||||||
></OlStateButton>
|
></OlStateButton>
|
||||||
{/*}<OlStateButton
|
{/*}<OlStateButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -80,6 +85,7 @@ export function SideBar() {
|
|||||||
checked={appState === OlympusState.AWACS}
|
checked={appState === OlympusState.AWACS}
|
||||||
icon={faA}
|
icon={faA}
|
||||||
tooltip="Hide/show AWACS menu"
|
tooltip="Hide/show AWACS menu"
|
||||||
|
tooltipPosition="side"
|
||||||
></OlStateButton>
|
></OlStateButton>
|
||||||
<OlStateButton
|
<OlStateButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -88,6 +94,7 @@ export function SideBar() {
|
|||||||
checked={appState === OlympusState.GAME_MASTER}
|
checked={appState === OlympusState.GAME_MASTER}
|
||||||
icon={faCrown}
|
icon={faCrown}
|
||||||
tooltip="Hide/show Game Master menu"
|
tooltip="Hide/show Game Master menu"
|
||||||
|
tooltipPosition="side"
|
||||||
></OlStateButton>
|
></OlStateButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,6 +105,7 @@ export function SideBar() {
|
|||||||
checked={false}
|
checked={false}
|
||||||
icon={faQuestionCircle}
|
icon={faQuestionCircle}
|
||||||
tooltip="Open user guide on separate window"
|
tooltip="Open user guide on separate window"
|
||||||
|
tooltipPosition="side"
|
||||||
></OlStateButton>
|
></OlStateButton>
|
||||||
<OlStateButton
|
<OlStateButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -106,6 +114,7 @@ export function SideBar() {
|
|||||||
checked={appState === OlympusState.OPTIONS}
|
checked={appState === OlympusState.OPTIONS}
|
||||||
icon={faCog}
|
icon={faCog}
|
||||||
tooltip="Hide/show settings menu"
|
tooltip="Hide/show settings menu"
|
||||||
|
tooltipPosition="side"
|
||||||
></OlStateButton>
|
></OlStateButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
|
||||||
import { ContextActionSet } from "../../unit/contextactionset";
|
|
||||||
import { OlStateButton } from "../components/olstatebutton";
|
|
||||||
import { getApp } from "../../olympusapp";
|
|
||||||
import { ContextAction } from "../../unit/contextaction";
|
|
||||||
import { CONTEXT_ACTION_COLORS, ContextActionTarget, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
|
||||||
import { FaChevronDown,FaChevronUp } from "react-icons/fa6";
|
|
||||||
import { OlympusState } from "../../constants/constants";
|
|
||||||
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent } from "../../events";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
export function UnitControlBar(props: {}) {
|
|
||||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
|
||||||
const [contextActionSet, setcontextActionSet] = useState(null as ContextActionSet | null);
|
|
||||||
const [contextAction, setContextAction] = useState(null as ContextAction | null);
|
|
||||||
const [scrolledTop, setScrolledTop] = useState(true);
|
|
||||||
const [scrolledBottom, setScrolledBottom] = useState(false);
|
|
||||||
const [menuHidden, setMenuHidden] = useState(false);
|
|
||||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
|
||||||
|
|
||||||
/* Initialize the "scroll" position of the element */
|
|
||||||
var scrollRef = useRef(null);
|
|
||||||
useEffect(() => {
|
|
||||||
if (scrollRef.current) onScroll(scrollRef.current);
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
|
||||||
ContextActionSetChangedEvent.on((contextActionSet) => setcontextActionSet(contextActionSet));
|
|
||||||
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
|
|
||||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function onScroll(el) {
|
|
||||||
const sl = el.scrollTop;
|
|
||||||
const sr = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
||||||
|
|
||||||
sl < 1 && !scrolledTop && setScrolledTop(true);
|
|
||||||
sl > 1 && scrolledTop && setScrolledTop(false);
|
|
||||||
|
|
||||||
sr < 1 && !scrolledBottom && setScrolledBottom(true);
|
|
||||||
sr > 1 && scrolledBottom && setScrolledBottom(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let reorderedActions: ContextAction[] = contextActionSet
|
|
||||||
? Object.values(contextActionSet.getContextActions()).sort((a: ContextAction, b: ContextAction) => (a.getOptions().type < b.getOptions().type ? -1 : 1))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{appState === OlympusState.UNIT_CONTROL && contextActionSet && Object.keys(contextActionSet.getContextActions()).length > 0 && (
|
|
||||||
<>
|
|
||||||
{mapOptions.tabletMode && (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
data-menuhidden={menuHidden}
|
|
||||||
className={`
|
|
||||||
absolute right-2 top-16 flex max-h-[80%] gap-2 rounded-md
|
|
||||||
bg-gray-200
|
|
||||||
dark:bg-olympus-900
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{!scrolledTop && (
|
|
||||||
<FaChevronUp
|
|
||||||
className={`
|
|
||||||
absolute top-0 h-6 w-full rounded-lg px-2 py-3.5
|
|
||||||
text-gray-200
|
|
||||||
dark:bg-olympus-900
|
|
||||||
`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className={`
|
|
||||||
flex flex-col gap-2 overflow-y-auto no-scrollbar p-2
|
|
||||||
`} onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
|
||||||
{reorderedActions.map((contextActionIt: ContextAction) => {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<OlStateButton
|
|
||||||
key={contextActionIt.getId()}
|
|
||||||
checked={contextActionIt === contextAction}
|
|
||||||
icon={contextActionIt.getIcon()}
|
|
||||||
tooltip={contextActionIt.getLabel()}
|
|
||||||
buttonColor={CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type ?? 0]}
|
|
||||||
onClick={() => {
|
|
||||||
if (contextActionIt.getTarget() === ContextActionTarget.NONE) {
|
|
||||||
contextActionIt.executeCallback(null, null);
|
|
||||||
} else {
|
|
||||||
contextActionIt !== contextAction
|
|
||||||
? getApp().getMap().setContextAction(contextActionIt)
|
|
||||||
: getApp().getMap().setContextAction(null);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
{!scrolledBottom && (
|
|
||||||
<FaChevronDown
|
|
||||||
className={`
|
|
||||||
absolute bottom-0 h-6 w-full rounded-lg px-2 py-3.5
|
|
||||||
text-gray-200
|
|
||||||
dark:bg-olympus-900
|
|
||||||
`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{contextAction && (
|
|
||||||
<div
|
|
||||||
className={`
|
|
||||||
absolute left-[50%] top-16 flex translate-x-[calc(-50%+2rem)]
|
|
||||||
items-center gap-2 rounded-md bg-gray-200 p-4
|
|
||||||
dark:bg-olympus-800
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={contextAction.getIcon()}
|
|
||||||
className={`
|
|
||||||
mr-2 hidden text-xl text-blue-500
|
|
||||||
md:block
|
|
||||||
`}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={`text-gray-200`}
|
|
||||||
>
|
|
||||||
{contextAction.getDescription()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,7 @@ import { getApp, setupApp } from "../olympusapp";
|
|||||||
import { LoginModal } from "./modals/loginmodal";
|
import { LoginModal } from "./modals/loginmodal";
|
||||||
|
|
||||||
import { MiniMapPanel } from "./panels/minimappanel";
|
import { MiniMapPanel } from "./panels/minimappanel";
|
||||||
import { UnitControlBar } from "./panels/unitcontrolbar";
|
import { MapToolBar } from "./panels/maptoolbar";
|
||||||
import { DrawingMenu } from "./panels/drawingmenu";
|
import { DrawingMenu } from "./panels/drawingmenu";
|
||||||
import { ControlsPanel } from "./panels/controlspanel";
|
import { ControlsPanel } from "./panels/controlspanel";
|
||||||
import { MapContextMenu } from "./contextmenus/mapcontextmenu";
|
import { MapContextMenu } from "./contextmenus/mapcontextmenu";
|
||||||
@@ -105,7 +105,6 @@ export function UI() {
|
|||||||
<CoordinatesPanel />
|
<CoordinatesPanel />
|
||||||
<RadiosSummaryPanel />
|
<RadiosSummaryPanel />
|
||||||
|
|
||||||
<UnitControlBar />
|
|
||||||
<SideBar />
|
<SideBar />
|
||||||
<InfoBar />
|
<InfoBar />
|
||||||
<HotGroupBar />
|
<HotGroupBar />
|
||||||
|
|||||||
@@ -836,6 +836,9 @@ export abstract class Unit extends CustomMarker {
|
|||||||
contextActionSet.addContextAction(this, ContextActions.PATH);
|
contextActionSet.addContextAction(this, ContextActions.PATH);
|
||||||
contextActionSet.addContextAction(this, ContextActions.DELETE);
|
contextActionSet.addContextAction(this, ContextActions.DELETE);
|
||||||
contextActionSet.addContextAction(this, ContextActions.EXPLODE);
|
contextActionSet.addContextAction(this, ContextActions.EXPLODE);
|
||||||
|
contextActionSet.addContextAction(this, ContextActions.CENTER_MAP);
|
||||||
|
contextActionSet.addContextAction(this, ContextActions.CLONE);
|
||||||
|
contextActionSet.addContextAction(this, ContextActions.ATTACK);
|
||||||
|
|
||||||
contextActionSet.addDefaultContextAction(this, ContextActions.MOVE);
|
contextActionSet.addDefaultContextAction(this, ContextActions.MOVE);
|
||||||
}
|
}
|
||||||
@@ -1357,16 +1360,18 @@ export abstract class Unit extends CustomMarker {
|
|||||||
this.#debounceTimeout = window.setTimeout(() => {
|
this.#debounceTimeout = window.setTimeout(() => {
|
||||||
console.log(`Left short click on ${this.getUnitName()}`);
|
console.log(`Left short click on ${this.getUnitName()}`);
|
||||||
|
|
||||||
if (!e.originalEvent.ctrlKey) getApp().getUnitsManager().deselectAllUnits();
|
if (getApp().getState() === OlympusState.UNIT_CONTROL && getApp().getMap().getContextAction()) {
|
||||||
this.setSelected(!this.getSelected());
|
if (getApp().getMap().getContextAction()?.getTarget() === ContextActionTarget.UNIT) getApp().getMap().executeContextAction(this, null, e.originalEvent);
|
||||||
|
else getApp().getMap().executeContextAction(null, this.getPosition(), e.originalEvent);
|
||||||
|
} else {
|
||||||
|
if (!e.originalEvent.ctrlKey) getApp().getUnitsManager().deselectAllUnits();
|
||||||
|
this.setSelected(!this.getSelected());
|
||||||
|
}
|
||||||
}, SHORT_PRESS_MILLISECONDS);
|
}, SHORT_PRESS_MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
#onRightShortClick(e: any) {
|
#onRightShortClick(e: any) {
|
||||||
console.log(`Right short click on ${this.getUnitName()}`);
|
console.log(`Right short click on ${this.getUnitName()}`);
|
||||||
|
|
||||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && getApp().getMap().getContextAction()?.getTarget() === ContextActionTarget.UNIT)
|
|
||||||
getApp().getMap().executeContextAction(this, null, e.originalEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#onRightLongClick(e: any) {
|
#onRightLongClick(e: any) {
|
||||||
@@ -1848,7 +1853,7 @@ export abstract class AirUnit extends Unit {
|
|||||||
showAmmo: belongsToCommandedCoalition,
|
showAmmo: belongsToCommandedCoalition,
|
||||||
showSummary: belongsToCommandedCoalition || this.getDetectionMethods().some((value) => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value)),
|
showSummary: belongsToCommandedCoalition || this.getDetectionMethods().some((value) => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value)),
|
||||||
showCallsign: belongsToCommandedCoalition && (!getApp().getMap().getOptions().AWACSMode || this.getHuman()),
|
showCallsign: belongsToCommandedCoalition && (!getApp().getMap().getOptions().AWACSMode || this.getHuman()),
|
||||||
rotateToHeading: false
|
rotateToHeading: false,
|
||||||
} as ObjectIconOptions;
|
} as ObjectIconOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1857,10 +1862,8 @@ export abstract class AirUnit extends Unit {
|
|||||||
|
|
||||||
/* Context actions to be executed immediately */
|
/* Context actions to be executed immediately */
|
||||||
contextActionSet.addContextAction(this, ContextActions.REFUEL);
|
contextActionSet.addContextAction(this, ContextActions.REFUEL);
|
||||||
contextActionSet.addContextAction(this, ContextActions.CENTER_MAP);
|
|
||||||
|
|
||||||
/* Context actions that require a target unit */
|
/* Context actions that require a target unit */
|
||||||
contextActionSet.addContextAction(this, ContextActions.ATTACK);
|
|
||||||
contextActionSet.addContextAction(this, ContextActions.FOLLOW);
|
contextActionSet.addContextAction(this, ContextActions.FOLLOW);
|
||||||
contextActionSet.addContextAction(this, ContextActions.SET_AWACS_REFERENCE);
|
contextActionSet.addContextAction(this, ContextActions.SET_AWACS_REFERENCE);
|
||||||
|
|
||||||
@@ -1946,10 +1949,6 @@ export class GroundUnit extends Unit {
|
|||||||
|
|
||||||
/* Context actions to be executed immediately */
|
/* Context actions to be executed immediately */
|
||||||
contextActionSet.addContextAction(this, ContextActions.GROUP);
|
contextActionSet.addContextAction(this, ContextActions.GROUP);
|
||||||
contextActionSet.addContextAction(this, ContextActions.CENTER_MAP);
|
|
||||||
|
|
||||||
/* Context actions that require a target unit */
|
|
||||||
contextActionSet.addContextAction(this, ContextActions.ATTACK);
|
|
||||||
|
|
||||||
/* Context actions that require a target position */
|
/* Context actions that require a target position */
|
||||||
if (this.canTargetPoint()) {
|
if (this.canTargetPoint()) {
|
||||||
@@ -2015,10 +2014,6 @@ export class NavyUnit extends Unit {
|
|||||||
|
|
||||||
/* Context actions to be executed immediately */
|
/* Context actions to be executed immediately */
|
||||||
contextActionSet.addContextAction(this, ContextActions.GROUP);
|
contextActionSet.addContextAction(this, ContextActions.GROUP);
|
||||||
contextActionSet.addContextAction(this, ContextActions.CENTER_MAP);
|
|
||||||
|
|
||||||
/* Context actions that require a target unit */
|
|
||||||
contextActionSet.addContextAction(this, ContextActions.ATTACK);
|
|
||||||
|
|
||||||
/* Context actions that require a target position */
|
/* Context actions that require a target position */
|
||||||
contextActionSet.addContextAction(this, ContextActions.FIRE_AT_AREA);
|
contextActionSet.addContextAction(this, ContextActions.FIRE_AT_AREA);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
AWACSReferenceChangedEvent,
|
AWACSReferenceChangedEvent,
|
||||||
CommandModeOptionsChangedEvent,
|
CommandModeOptionsChangedEvent,
|
||||||
ContactsUpdatedEvent,
|
ContactsUpdatedEvent,
|
||||||
|
CopiedUnitsEvents,
|
||||||
HotgroupsChangedEvent,
|
HotgroupsChangedEvent,
|
||||||
SelectedUnitsChangedEvent,
|
SelectedUnitsChangedEvent,
|
||||||
SelectionClearedEvent,
|
SelectionClearedEvent,
|
||||||
@@ -1234,6 +1235,8 @@ export class UnitsManager {
|
|||||||
)
|
)
|
||||||
); /* Can be applied to humans too */
|
); /* Can be applied to humans too */
|
||||||
getApp().addInfoMessage(`${this.#copiedUnits.length} units copied`);
|
getApp().addInfoMessage(`${this.#copiedUnits.length} units copied`);
|
||||||
|
|
||||||
|
CopiedUnitsEvents.dispatch(this.#copiedUnits)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************** Unit manipulation functions ************************/
|
/*********************** Unit manipulation functions ************************/
|
||||||
@@ -1241,7 +1244,7 @@ export class UnitsManager {
|
|||||||
*
|
*
|
||||||
* @returns True if units were pasted successfully
|
* @returns True if units were pasted successfully
|
||||||
*/
|
*/
|
||||||
paste() {
|
paste(location?: LatLng) {
|
||||||
let spawnPoints = 0;
|
let spawnPoints = 0;
|
||||||
|
|
||||||
/* 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 */
|
||||||
@@ -1285,7 +1288,10 @@ export class UnitsManager {
|
|||||||
var units: { ID: number; location: LatLng }[] = [];
|
var units: { ID: number; location: LatLng }[] = [];
|
||||||
let markers: TemporaryUnitMarker[] = [];
|
let markers: TemporaryUnitMarker[] = [];
|
||||||
groups[groupName].forEach((unit: UnitData) => {
|
groups[groupName].forEach((unit: UnitData) => {
|
||||||
var position = new LatLng(
|
var position = location ? new LatLng(
|
||||||
|
location.lat + unit.position.lat - avgLat,
|
||||||
|
location.lng + unit.position.lng - avgLng
|
||||||
|
) : new LatLng(
|
||||||
getApp().getMap().getMouseCoordinates().lat + unit.position.lat - avgLat,
|
getApp().getMap().getMouseCoordinates().lat + unit.position.lat - avgLat,
|
||||||
getApp().getMap().getMouseCoordinates().lng + unit.position.lng - avgLng
|
getApp().getMap().getMouseCoordinates().lng + unit.position.lng - avgLng
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user